1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

win_lineinfile: Clean up and check-mode support (#21503)

* win_lineinfile: Clean up and check-mode and diff support

Changes include:
- Use Get-AnsibleParam with -type support
- Replace $result PSObject with normal hash
- Remove trailing semi-colons
- Fix indentation (majority is tabs, few lines using spaces)
- Add check-mode support
- Support `r and `n for CR and LF
- Add diff support

* Implement -WhatIf:$check_mode support

* Keep original formatting as requested
This commit is contained in:
Dag Wieers 2017-02-24 08:29:11 +01:00 committed by Matt Davis
parent d239adb5e5
commit cb78262c18

View file

@ -18,68 +18,22 @@
# POWERSHELL_COMMON # POWERSHELL_COMMON
# Parse the parameters file dropped by the Ansible machinery
$params = Parse-Args $args;
# Initialize defaults for input parameters.
$path= Get-Attr $params "path" $FALSE;
$regexp = Get-Attr $params "regexp" $FALSE;
$state = Get-Attr $params "state" "present";
$line = Get-Attr $params "line" $FALSE;
$backrefs = Get-Attr -obj $params -name "backrefs" -default "no" -type "bool"
$insertafter = Get-Attr $params "insertafter" $FALSE;
$insertbefore = Get-Attr $params "insertbefore" $FALSE;
$create = Get-Attr $params -name "create" -default "no" -type "bool";
$backup = Get-Attr $params -name "backup" -default "no" -type "bool";
$validate = Get-Attr $params "validate" $FALSE;
$encoding = Get-Attr $params "encoding" "auto";
$newline = Get-Attr $params "newline" "windows";
# Parse path / dest / destfile / name param aliases for compatibility with lineinfile
# and fail if at least one spelling of the parameter is not provided.
If ($path -eq $FALSE) {
$path = Get-Attr $params "dest" $FALSE;
If ($path -eq $FALSE) {
$path = Get-Attr $params "destfile" $FALSE;
If ($path -eq $FALSE) {
$path = Get-Attr $params "name" $FALSE;
If ($path -eq $FALSE) {
Fail-Json (New-Object psobject) "missing required argument: path";
}
}
}
}
# Fail if the path is not a file
If (Test-Path $path -pathType container) {
Fail-Json (New-Object psobject) "Path $path is a directory";
}
# Write lines to a file using the specified line separator and encoding, # Write lines to a file using the specified line separator and encoding,
# performing validation if a validation command was specified. # performing validation if a validation command was specified.
function WriteLines($outlines, $path, $linesep, $encodingobj, $validate, $check_mode) {
function WriteLines($outlines, $path, $linesep, $encodingobj, $validate) {
Try { Try {
$temppath = [System.IO.Path]::GetTempFileName(); $temppath = [System.IO.Path]::GetTempFileName();
} }
Catch { Catch {
Fail-Json ("Cannot create temporary file! (" + $_.Exception.Message + ")") Fail-Json @{} "Cannot create temporary file! ($($_.Exception.Message))";
} }
$joined = $outlines -join $linesep; $joined = $outlines -join $linesep;
[System.IO.File]::WriteAllText($temppath, $joined, $encodingobj); [System.IO.File]::WriteAllText($temppath, $joined, $encodingobj);
If ($validate -ne $FALSE) { If ($validate) {
If (!($validate -like "*%s*")) { If (-not ($validate -like "*%s*")) {
Fail-Json (New-Object psobject) "validate must contain %s: $validate"; Fail-Json @{} "validate must contain %s: $validate";
} }
$validate = $validate.Replace("%s", $temppath); $validate = $validate.Replace("%s", $temppath);
@ -96,7 +50,7 @@ function WriteLines($outlines, $path, $linesep, $encodingobj, $validate) {
[string] $output = $process.StandardOutput.ReadToEnd(); [string] $output = $process.StandardOutput.ReadToEnd();
[string] $error = $process.StandardError.ReadToEnd(); [string] $error = $process.StandardError.ReadToEnd();
Remove-Item $temppath -force; Remove-Item $temppath -force;
Fail-Json (New-Object psobject) "failed to validate $cmdname $cmdargs with error: $output $error"; Fail-Json @{} "failed to validate $cmdname $cmdargs with error: $output $error";
} }
} }
@ -104,35 +58,39 @@ function WriteLines($outlines, $path, $linesep, $encodingobj, $validate) {
# Commit changes to the path # Commit changes to the path
$cleanpath = $path.Replace("/", "\"); $cleanpath = $path.Replace("/", "\");
Try { Try {
Copy-Item $temppath $cleanpath -force -ErrorAction Stop; Copy-Item $temppath $cleanpath -force -ErrorAction Stop -WhatIf:$check_mode;
} }
Catch { Catch {
Fail-Json ("Cannot write to: $cleanpath (" + $_.Exception.Message + ")") Fail-Json @{} "Cannot write to: $cleanpath ($($_.Exception.Message))";
} }
Try { Try {
Remove-Item $temppath -force -ErrorAction Stop; Remove-Item $temppath -force -ErrorAction Stop -WhatIf:$check_mode;
} }
Catch { Catch {
Fail-Json ("Cannot remove temporary file: $temppath (" + $_.Exception.Message + ")") Fail-Json @{} "Cannot remove temporary file: $temppath ($($_.Exception.Message))";
} }
return $joined;
} }
# Backup the file specified with a date/time filename # Backup the file specified with a date/time filename
function BackupFile($path, $check_mode) {
function BackupFile($path) {
$backuppath = $path + "." + [DateTime]::Now.ToString("yyyyMMdd-HHmmss"); $backuppath = $path + "." + [DateTime]::Now.ToString("yyyyMMdd-HHmmss");
Copy-Item $path $backuppath; Try {
Copy-Item $path $backuppath -WhatIf:$check_mode;
}
Catch {
Fail-Json @{} "Cannot copy backup file! ($($_.Exception.Message))";
}
return $backuppath; return $backuppath;
} }
# Implement the functionality for state == 'present' # Implement the functionality for state == 'present'
function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $backup, $backrefs, $validate, $encodingobj, $linesep, $check_mode, $diff_support) {
function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $backup, $backrefs, $validate, $encodingobj, $linesep) {
# Note that we have to clean up the path because ansible wants to treat / and \ as # Note that we have to clean up the path because ansible wants to treat / and \ as
# interchangeable in windows pathnames, but .NET framework internals do not support that. # interchangeable in windows pathnames, but .NET framework internals do not support that.
@ -140,36 +98,48 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b
# Check if path exists. If it does not exist, either create it if create == "yes" # Check if path exists. If it does not exist, either create it if create == "yes"
# was specified or fail with a reasonable error message. # was specified or fail with a reasonable error message.
If (!(Test-Path $path)) { If (-not (Test-Path -Path $path)) {
If (-not $create) { If (-not $create) {
Fail-Json (New-Object psobject) "Path $path does not exist !"; Fail-Json @{} "Path $path does not exist !";
} }
# Create new empty file, using the specified encoding to write correct BOM # Create new empty file, using the specified encoding to write correct BOM
[System.IO.File]::WriteAllLines($cleanpath, "", $encodingobj); [System.IO.File]::WriteAllLines($cleanpath, "", $encodingobj);
} }
# Initialize result information
$result = @{
backup = "";
changed = $false;
msg = "";
}
# Read the dest file lines using the indicated encoding into a mutable ArrayList. # Read the dest file lines using the indicated encoding into a mutable ArrayList.
$content = [System.IO.File]::ReadAllLines($cleanpath, $encodingobj); $before = [System.IO.File]::ReadAllLines($cleanpath, $encodingobj)
If ($content -eq $null) { If ($before -eq $null) {
$lines = New-Object System.Collections.ArrayList; $lines = New-Object System.Collections.ArrayList;
} }
Else { Else {
$lines = [System.Collections.ArrayList] $content; $lines = [System.Collections.ArrayList] $before;
}
if ($diff_support) {
$result.diff = @{
before = $before -join $linesep;
}
} }
# Compile the regex specified, if provided # Compile the regex specified, if provided
$mre = $FALSE; $mre = $null;
If ($regexp -ne $FALSE) { If ($regexp) {
$mre = New-Object Regex $regexp, 'Compiled'; $mre = New-Object Regex $regexp, 'Compiled';
} }
# Compile the regex for insertafter or insertbefore, if provided # Compile the regex for insertafter or insertbefore, if provided
$insre = $FALSE; $insre = $null;
If ($insertafter -and $insertafter -ne "BOF" -and $insertafter -ne "EOF") {
If ($insertafter -ne $FALSE -and $insertafter -ne "BOF" -and $insertafter -ne "EOF") {
$insre = New-Object Regex $insertafter, 'Compiled'; $insre = New-Object Regex $insertafter, 'Compiled';
} }
ElseIf ($insertbefore -ne $FALSE -and $insertbefore -ne "BOF") { ElseIf ($insertbefore -and $insertbefore -ne "BOF") {
$insre = New-Object Regex $insertbefore, 'Compiled'; $insre = New-Object Regex $insertbefore, 'Compiled';
} }
@ -180,11 +150,10 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b
# The latest match object and matched line # The latest match object and matched line
$matched_line = ""; $matched_line = "";
$m = $FALSE;
# Iterate through the lines in the file looking for matches # Iterate through the lines in the file looking for matches
Foreach ($cur_line in $lines) { Foreach ($cur_line in $lines) {
If ($regexp -ne $FALSE) { If ($regexp) {
$m = $mre.Match($cur_line); $m = $mre.Match($cur_line);
$match_found = $m.Success; $match_found = $m.Success;
If ($match_found) { If ($match_found) {
@ -197,20 +166,17 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b
If ($match_found) { If ($match_found) {
$index[0] = $lineno; $index[0] = $lineno;
} }
ElseIf ($insre -ne $FALSE -and $insre.Match($cur_line).Success) { ElseIf ($insre -and $insre.Match($cur_line).Success) {
If ($insertafter -ne $FALSE) { If ($insertafter) {
$index[1] = $lineno + 1; $index[1] = $lineno + 1;
} }
If ($insertbefore -ne $FALSE) { If ($insertbefore) {
$index[1] = $lineno; $index[1] = $lineno;
} }
} }
$lineno = $lineno + 1; $lineno = $lineno + 1;
} }
$changed = $FALSE;
$msg = "";
If ($index[0] -ne -1) { If ($index[0] -ne -1) {
If ($backrefs) { If ($backrefs) {
$new_line = [regex]::Replace($matched_line, $regexp, $line); $new_line = [regex]::Replace($matched_line, $regexp, $line);
@ -220,8 +186,8 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b
} }
If ($lines[$index[0]] -cne $new_line) { If ($lines[$index[0]] -cne $new_line) {
$lines[$index[0]] = $new_line; $lines[$index[0]] = $new_line;
$msg = "line replaced"; $result.changed = $true;
$changed = $TRUE; $result.msg = "line replaced";
} }
} }
ElseIf ($backrefs) { ElseIf ($backrefs) {
@ -229,83 +195,85 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b
} }
ElseIf ($insertbefore -eq "BOF" -or $insertafter -eq "BOF") { ElseIf ($insertbefore -eq "BOF" -or $insertafter -eq "BOF") {
$lines.Insert(0, $line); $lines.Insert(0, $line);
$msg = "line added"; $result.changed = $true;
$changed = $TRUE; $result.msg = "line added";
} }
ElseIf ($insertafter -eq "EOF" -or $index[1] -eq -1) { ElseIf ($insertafter -eq "EOF" -or $index[1] -eq -1) {
$lines.Add($line); $lines.Add($line);
$msg = "line added"; $result.changed = $true;
$changed = $TRUE; $result.msg = "line added";
} }
Else { Else {
$lines.Insert($index[1], $line); $lines.Insert($index[1], $line);
$msg = "line added"; $result.changed = $true;
$changed = $TRUE; $result.msg = "line added";
}
# Write backup file if backup == "yes"
$backuppath = "";
If ($changed -eq $TRUE -and $backup -eq $TRUE) {
$backuppath = BackupFile $path;
} }
# Write changes to the path if changes were made # Write changes to the path if changes were made
If ($changed) { If ($result.changed) {
WriteLines $lines $path $linesep $encodingobj $validate;
# Write backup file if backup == "yes"
If ($backup) {
$result.backup = BackupFile $path $check_mode;
} }
$encodingstr = $encodingobj.WebName; $after = WriteLines $lines $path $linesep $encodingobj $validate $check_mode;
# Return result information if ($diff_support) {
$result = New-Object psobject @{ $result.diff.after = $after;
changed = $changed
msg = $msg
backup = $backuppath
encoding = $encodingstr
} }
}
$result.encoding = $encodingobj.WebName;
Exit-Json $result; Exit-Json $result;
} }
# Implement the functionality for state == 'absent' # Implement the functionality for state == 'absent'
function Absent($path, $regexp, $line, $backup, $validate, $encodingobj, $linesep, $check_mode, $diff_support) {
function Absent($path, $regexp, $line, $backup, $validate, $encodingobj, $linesep) {
# Check if path exists. If it does not exist, fail with a reasonable error message. # Check if path exists. If it does not exist, fail with a reasonable error message.
If (!(Test-Path $path)) { If (-not (Test-Path -Path $path)) {
Fail-Json (New-Object psobject) "Path $path does not exist !"; Fail-Json @{} "Path $path does not exist !";
}
# Initialize result information
$result = @{
backup = "";
changed = $false;
msg = "";
} }
# Read the dest file lines using the indicated encoding into a mutable ArrayList. Note # Read the dest file lines using the indicated encoding into a mutable ArrayList. Note
# that we have to clean up the path because ansible wants to treat / and \ as # that we have to clean up the path because ansible wants to treat / and \ as
# interchangeable in windows pathnames, but .NET framework internals do not support that. # interchangeable in windows pathnames, but .NET framework internals do not support that.
$cleanpath = $path.Replace("/", "\"); $cleanpath = $path.Replace("/", "\");
$content = [System.IO.File]::ReadAllLines($cleanpath, $encodingobj); $before = [System.IO.File]::ReadAllLines($cleanpath, $encodingobj);
If ($content -eq $null) { If ($before -eq $null) {
$lines = New-Object System.Collections.ArrayList; $lines = New-Object System.Collections.ArrayList;
} }
Else { Else {
$lines = [System.Collections.ArrayList] $content; $lines = [System.Collections.ArrayList] $before;
} }
# Initialize message to be returned on success if ($diff_support) {
$msg = ""; $result.diff = @{
before = $before -join $linesep;
}
}
# Compile the regex specified, if provided # Compile the regex specified, if provided
$cre = $FALSE; $cre = $null;
If ($regexp -ne $FALSE) { If ($regexp) {
$cre = New-Object Regex $regexp, 'Compiled'; $cre = New-Object Regex $regexp, 'Compiled';
} }
$found = New-Object System.Collections.ArrayList; $found = New-Object System.Collections.ArrayList;
$left = New-Object System.Collections.ArrayList; $left = New-Object System.Collections.ArrayList;
$changed = $FALSE;
Foreach ($cur_line in $lines) { Foreach ($cur_line in $lines) {
If ($cre -ne $FALSE) { If ($regexp) {
$m = $cre.Match($cur_line); $m = $cre.Match($cur_line);
$match_found = $m.Success; $match_found = $m.Success;
} }
@ -314,64 +282,79 @@ function Absent($path, $regexp, $line, $backup, $validate, $encodingobj, $linese
} }
If ($match_found) { If ($match_found) {
$found.Add($cur_line); $found.Add($cur_line);
$changed = $TRUE; $result.changed = $true;
} }
Else { Else {
$left.Add($cur_line); $left.Add($cur_line);
} }
} }
# Write backup file if backup == "yes"
$backuppath = "";
If ($changed -eq $TRUE -and $backup -eq $TRUE) {
$backuppath = BackupFile $path;
}
# Write changes to the path if changes were made # Write changes to the path if changes were made
If ($changed) { If ($result.changed) {
WriteLines $left $path $linesep $encodingobj $validate;
# Write backup file if backup == "yes"
If ($backup) {
$result.backup = BackupFile $path $check_mode;
} }
# Return result information $after = WriteLines $left $path $linesep $encodingobj $validate $check_mode;
$fcount = $found.Count;
$msg = "$fcount line(s) removed";
$encodingstr = $encodingobj.WebName;
$result = New-Object psobject @{ if ($diff_support) {
changed = $changed $result.diff.after = $after;
msg = $msg
backup = $backuppath
found = $fcount
encoding = $encodingstr
} }
}
$result.encoding = $encodingobj.WebName;
$result.found = $found.Count;
$result.msg = "$($found.Count) line(s) removed";
Exit-Json $result; Exit-Json $result;
} }
# Parse the parameters file dropped by the Ansible machinery
$params = Parse-Args $args -supports_check_mode $true;
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false;
$diff_support = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false;
# Initialize defaults for input parameters.
$path = Get-AnsibleParam -obj $params -name "path" -type "path" -failifempty $true -aliases "dest","destfile","name";
$regexp = Get-AnsibleParam -obj $params -name "regexp" -type "str";
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent";
$line = Get-AnsibleParam -obj $params -name "line" -type "str";
$backrefs = Get-AnsibleParam -obj $params -name "backrefs" -type "bool" -default $false;
$insertafter = Get-AnsibleParam -obj $params -name "insertafter" -type "str";
$insertbefore = Get-AnsibleParam -obj $params -name "insertbefore" -type "str";
$create = Get-AnsibleParam -obj $params -name "create" -type "bool" -default $false;
$backup = Get-AnsibleParam -obj $params -name "backup" -type "bool" -default $false;
$validate = Get-AnsibleParam -obj $params -name "validate" -type "str";
$encoding = Get-AnsibleParam -obj $params -name "encoding" -type "str" -default "auto";
$newline = Get-AnsibleParam -obj $params -name "newline" -type "str" -default "windows" -validateset "unix","windows";
# Fail if the path is not a file
If (Test-Path -Path $path -PathType "container") {
Fail-Json @{} "Path $path is a directory";
}
# Default to windows line separator - probably most common # Default to windows line separator - probably most common
$linesep = "`r`n"
$linesep = "`r`n"; If ($newline -eq "unix") {
If ($newline -ne "windows") {
$linesep = "`n"; $linesep = "`n";
} }
# Fix any CR/LF literals in the line argument. PS will not recognize either backslash # Fix any CR/LF literals in the line argument. PS will not recognize either backslash
# or backtick literals in the incoming string argument without this bit of black magic. # or backtick literals in the incoming string argument without this bit of black magic.
If ($line) {
If ($line -ne $FALSE) {
$line = $line.Replace("\r", "`r"); $line = $line.Replace("\r", "`r");
$line = $line.Replace("\n", "`n"); $line = $line.Replace("\n", "`n");
$line = $line.Replace("``r", "`r");
$line = $line.Replace("``n", "`n");
} }
# Figure out the proper encoding to use for reading / writing the target file. # Figure out the proper encoding to use for reading / writing the target file.
# The default encoding is UTF-8 without BOM # The default encoding is UTF-8 without BOM
$encodingobj = [System.Text.UTF8Encoding] $FALSE; $encodingobj = [System.Text.UTF8Encoding] $false;
# If an explicit encoding is specified, use that instead # If an explicit encoding is specified, use that instead
If ($encoding -ne "auto") { If ($encoding -ne "auto") {
@ -381,10 +364,9 @@ If ($encoding -ne "auto") {
# Otherwise see if we can determine the current encoding of the target file. # Otherwise see if we can determine the current encoding of the target file.
# If the file doesn't exist yet (create == 'yes') we use the default or # If the file doesn't exist yet (create == 'yes') we use the default or
# explicitly specified encoding set above. # explicitly specified encoding set above.
Elseif (Test-Path $path) { ElseIf (Test-Path -Path $path) {
# Get a sorted list of encodings with preambles, longest first # Get a sorted list of encodings with preambles, longest first
$max_preamble_len = 0; $max_preamble_len = 0;
$sortedlist = New-Object System.Collections.SortedList; $sortedlist = New-Object System.Collections.SortedList;
Foreach ($encodinginfo in [System.Text.Encoding]::GetEncodings()) { Foreach ($encodinginfo in [System.Text.Encoding]::GetEncodings()) {
@ -399,12 +381,10 @@ Elseif (Test-Path $path) {
} }
# Get the first N bytes from the file, where N is the max preamble length we saw # Get the first N bytes from the file, where N is the max preamble length we saw
[Byte[]]$bom = Get-Content -Encoding Byte -ReadCount $max_preamble_len -TotalCount $max_preamble_len -Path $path; [Byte[]]$bom = Get-Content -Encoding Byte -ReadCount $max_preamble_len -TotalCount $max_preamble_len -Path $path;
# Iterate through the sorted encodings, looking for a full match. # Iterate through the sorted encodings, looking for a full match.
$found = $false;
$found = $FALSE;
Foreach ($encoding in $sortedlist.GetValueList()) { Foreach ($encoding in $sortedlist.GetValueList()) {
$preamble = $encoding.GetPreamble(); $preamble = $encoding.GetPreamble();
If ($preamble -and $bom) { If ($preamble -and $bom) {
@ -415,9 +395,9 @@ Elseif (Test-Path $path) {
If ($preamble[$i] -ne $bom[$i]) { If ($preamble[$i] -ne $bom[$i]) {
break; break;
} }
Elseif ($i + 1 -eq $preamble.Length) { ElseIf ($i + 1 -eq $preamble.Length) {
$encodingobj = $encoding; $encodingobj = $encoding;
$found = $TRUE; $found = $true;
} }
} }
If ($found) { If ($found) {
@ -430,29 +410,32 @@ Elseif (Test-Path $path) {
# Main dispatch - based on the value of 'state', perform argument validation and # Main dispatch - based on the value of 'state', perform argument validation and
# call the appropriate handler function. # call the appropriate handler function.
If ($state -eq "present") { If ($state -eq "present") {
If ( $backrefs -and $regexp -eq $FALSE ) { If ($backrefs -and -not $regexp) {
Fail-Json (New-Object psobject) "regexp= is required with backrefs=true"; Fail-Json @{} "regexp= is required with backrefs=true";
} }
If ($line -eq $FALSE) { If (-not $line) {
Fail-Json (New-Object psobject) "line= is required with state=present"; Fail-Json @{} "line= is required with state=present";
} }
If ($insertbefore -eq $FALSE -and $insertafter -eq $FALSE) { If ($insertbefore -and $insertafter) {
Add-Warning('Both insertbefore and insertafter parameters found, ignoring "insertafter=$insertafter"');
}
If (-not $insertbefore -and -not $insertafter) {
$insertafter = "EOF"; $insertafter = "EOF";
} }
Present $path $regexp $line $insertafter $insertbefore $create $backup $backrefs $validate $encodingobj $linesep; Present $path $regexp $line $insertafter $insertbefore $create $backup $backrefs $validate $encodingobj $linesep $check_mode $diff_support;
} }
Else { ElseIf ($state -eq "absent") {
If ($regexp -eq $FALSE -and $line -eq $FALSE) { If (-not $regexp -and -not $line) {
Fail-Json (New-Object psobject) "one of line= or regexp= is required with state=absent"; Fail-Json @{} "one of line= or regexp= is required with state=absent";
} }
Absent $path $regexp $line $backup $validate $encodingobj $linesep; Absent $path $regexp $line $backup $validate $encodingobj $linesep $check_mode $diff_support;
} }