win_lineinfile backrefs bug fix and updated examples. (#20926)

* Bug Fix for win_lineinfile and updated examples.

- changed $backrefs to a bool so it works with true/false/yes/no. This also fixes idempotency.
- Updated Docs with an example of using backrefs.

* Made suggested updates and converted two more parameters to "bool"

* Updated the Exception message

- Now contains the Windows Exception message as well as a custom message to help point in the right direction of a failed write.

* Updated Exception Handling

- Added Exception checks for Creating and removing the temporary files.
- Changed the ErrorAction on the copy tmpfile and remove tmp file to "Stop" to
  cause the exception handler to catch all errors so we can fail gracefully
  every time.
This commit is contained in:
johnnysheppard-isode 2017-02-16 01:09:30 +00:00 committed by Brian Coca
commit 54de41309f
2 changed files with 67 additions and 43 deletions

View file

@ -29,11 +29,11 @@ $path= Get-Attr $params "path" $FALSE;
$regexp = Get-Attr $params "regexp" $FALSE; $regexp = Get-Attr $params "regexp" $FALSE;
$state = Get-Attr $params "state" "present"; $state = Get-Attr $params "state" "present";
$line = Get-Attr $params "line" $FALSE; $line = Get-Attr $params "line" $FALSE;
$backrefs = Get-Attr $params "backrefs" "no"; $backrefs = Get-Attr -obj $params -name "backrefs" -default "no" -type "bool"
$insertafter = Get-Attr $params "insertafter" $FALSE; $insertafter = Get-Attr $params "insertafter" $FALSE;
$insertbefore = Get-Attr $params "insertbefore" $FALSE; $insertbefore = Get-Attr $params "insertbefore" $FALSE;
$create = Get-Attr $params "create" "no"; $create = Get-Attr $params -name "create" -default "no" -type "bool";
$backup = Get-Attr $params "backup" "no"; $backup = Get-Attr $params -name "backup" -default "no" -type "bool";
$validate = Get-Attr $params "validate" $FALSE; $validate = Get-Attr $params "validate" $FALSE;
$encoding = Get-Attr $params "encoding" "auto"; $encoding = Get-Attr $params "encoding" "auto";
$newline = Get-Attr $params "newline" "windows"; $newline = Get-Attr $params "newline" "windows";
@ -63,30 +63,35 @@ If (Test-Path $path -pathType container) {
} }
# 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) { function WriteLines($outlines, $path, $linesep, $encodingobj, $validate) {
$temppath = [System.IO.Path]::GetTempFileName(); Try {
$temppath = [System.IO.Path]::GetTempFileName();
}
Catch {
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 -ne $FALSE) {
If (!($validate -like "*%s*")) { If (!($validate -like "*%s*")) {
Fail-Json (New-Object psobject) "validate must contain %s: $validate"; Fail-Json (New-Object psobject) "validate must contain %s: $validate";
} }
$validate = $validate.Replace("%s", $temppath); $validate = $validate.Replace("%s", $temppath);
$parts = [System.Collections.ArrayList] $validate.Split(" "); $parts = [System.Collections.ArrayList] $validate.Split(" ");
$cmdname = $parts[0]; $cmdname = $parts[0];
$cmdargs = $validate.Substring($cmdname.Length + 1); $cmdargs = $validate.Substring($cmdname.Length + 1);
$process = [Diagnostics.Process]::Start($cmdname, $cmdargs); $process = [Diagnostics.Process]::Start($cmdname, $cmdargs);
$process.WaitForExit(); $process.WaitForExit();
If ($process.ExitCode -ne 0) { If ($process.ExitCode -ne 0) {
[string] $output = $process.StandardOutput.ReadToEnd(); [string] $output = $process.StandardOutput.ReadToEnd();
[string] $error = $process.StandardError.ReadToEnd(); [string] $error = $process.StandardError.ReadToEnd();
@ -95,11 +100,23 @@ function WriteLines($outlines, $path, $linesep, $encodingobj, $validate) {
} }
} }
# Commit changes to the path # Commit changes to the path
$cleanpath = $path.Replace("/", "\"); $cleanpath = $path.Replace("/", "\");
Copy-Item $temppath $cleanpath -force; Try {
Remove-Item $temppath -force; Copy-Item $temppath $cleanpath -force -ErrorAction Stop;
}
Catch {
Fail-Json ("Cannot write to: $cleanpath (" + $_.Exception.Message + ")")
}
Try {
Remove-Item $temppath -force -ErrorAction Stop;
}
Catch {
Fail-Json ("Cannot remove temporary file: $temppath (" + $_.Exception.Message + ")")
}
} }
@ -117,14 +134,14 @@ function BackupFile($path) {
function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $backup, $backrefs, $validate, $encodingobj, $linesep) { 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.
$cleanpath = $path.Replace("/", "\"); $cleanpath = $path.Replace("/", "\");
# 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 (!(Test-Path $path)) {
If ($create -eq "no") { If (-not $create) {
Fail-Json (New-Object psobject) "Path $path does not exist !"; Fail-Json (New-Object psobject) "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
@ -139,16 +156,16 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b
Else { Else {
$lines = [System.Collections.ArrayList] $content; $lines = [System.Collections.ArrayList] $content;
} }
# Compile the regex specified, if provided # Compile the regex specified, if provided
$mre = $FALSE; $mre = $FALSE;
If ($regexp -ne $FALSE) { If ($regexp -ne $FALSE) {
$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 = $FALSE;
If ($insertafter -ne $FALSE -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';
} }
@ -160,7 +177,7 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b
# index[1] is the line num where insertafter/inserbefore has been found # index[1] is the line num where insertafter/inserbefore has been found
$index = -1, -1; $index = -1, -1;
$lineno = 0; $lineno = 0;
# The latest match object and matched line # The latest match object and matched line
$matched_line = ""; $matched_line = "";
$m = $FALSE; $m = $FALSE;
@ -186,16 +203,16 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b
} }
If ($insertbefore -ne $FALSE) { If ($insertbefore -ne $FALSE) {
$index[1] = $lineno; $index[1] = $lineno;
} }
} }
$lineno = $lineno + 1; $lineno = $lineno + 1;
} }
$changed = $FALSE; $changed = $FALSE;
$msg = ""; $msg = "";
If ($index[0] -ne -1) { If ($index[0] -ne -1) {
If ($backrefs -ne "no") { If ($backrefs) {
$new_line = [regex]::Replace($matched_line, $regexp, $line); $new_line = [regex]::Replace($matched_line, $regexp, $line);
} }
Else { Else {
@ -207,7 +224,7 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b
$changed = $TRUE; $changed = $TRUE;
} }
} }
ElseIf ($backrefs -ne "no") { ElseIf ($backrefs) {
# No matches - no-op # No matches - no-op
} }
ElseIf ($insertbefore -eq "BOF" -or $insertafter -eq "BOF") { ElseIf ($insertbefore -eq "BOF" -or $insertafter -eq "BOF") {
@ -229,10 +246,10 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b
# Write backup file if backup == "yes" # Write backup file if backup == "yes"
$backuppath = ""; $backuppath = "";
If ($changed -eq $TRUE -and $backup -eq "yes") { If ($changed -eq $TRUE -and $backup -eq $TRUE) {
$backuppath = BackupFile $path; $backuppath = BackupFile $path;
} }
# Write changes to the path if changes were made # Write changes to the path if changes were made
If ($changed) { If ($changed) {
WriteLines $lines $path $linesep $encodingobj $validate; WriteLines $lines $path $linesep $encodingobj $validate;
@ -247,7 +264,7 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b
backup = $backuppath backup = $backuppath
encoding = $encodingstr encoding = $encodingstr
} }
Exit-Json $result; Exit-Json $result;
} }
@ -262,9 +279,9 @@ function Absent($path, $regexp, $line, $backup, $validate, $encodingobj, $linese
} }
# 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); $content = [System.IO.File]::ReadAllLines($cleanpath, $encodingobj);
If ($content -eq $null) { If ($content -eq $null) {
@ -273,7 +290,7 @@ function Absent($path, $regexp, $line, $backup, $validate, $encodingobj, $linese
Else { Else {
$lines = [System.Collections.ArrayList] $content; $lines = [System.Collections.ArrayList] $content;
} }
# Initialize message to be returned on success # Initialize message to be returned on success
$msg = ""; $msg = "";
@ -307,10 +324,10 @@ function Absent($path, $regexp, $line, $backup, $validate, $encodingobj, $linese
# Write backup file if backup == "yes" # Write backup file if backup == "yes"
$backuppath = ""; $backuppath = "";
If ($changed -eq $TRUE -and $backup -eq "yes") { If ($changed -eq $TRUE -and $backup -eq $TRUE) {
$backuppath = BackupFile $path; $backuppath = BackupFile $path;
} }
# Write changes to the path if changes were made # Write changes to the path if changes were made
If ($changed) { If ($changed) {
WriteLines $left $path $linesep $encodingobj $validate; WriteLines $left $path $linesep $encodingobj $validate;
@ -328,7 +345,7 @@ function Absent($path, $regexp, $line, $backup, $validate, $encodingobj, $linese
found = $fcount found = $fcount
encoding = $encodingstr encoding = $encodingstr
} }
Exit-Json $result; Exit-Json $result;
} }
@ -362,7 +379,7 @@ 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) {
@ -382,11 +399,11 @@ 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();
@ -411,19 +428,19 @@ 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 -ne "no" -and $regexp -eq $FALSE ) { If ( $backrefs -and $regexp -eq $FALSE ) {
Fail-Json (New-Object psobject) "regexp= is required with backrefs=true"; Fail-Json (New-Object psobject) "regexp= is required with backrefs=true";
} }
If ($line -eq $FALSE) { If ($line -eq $FALSE) {
Fail-Json (New-Object psobject) "line= is required with state=present"; Fail-Json (New-Object psobject) "line= is required with state=present";
} }
If ($insertbefore -eq $FALSE -and $insertafter -eq $FALSE) { If ($insertbefore -eq $FALSE -and $insertafter -eq $FALSE) {
$insertafter = "EOF"; $insertafter = "EOF";
} }
@ -436,6 +453,6 @@ Else {
If ($regexp -eq $FALSE -and $line -eq $FALSE) { If ($regexp -eq $FALSE -and $line -eq $FALSE) {
Fail-Json (New-Object psobject) "one of line= or regexp= is required with state=absent"; Fail-Json (New-Object psobject) "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;
} }

View file

@ -147,4 +147,11 @@ EXAMPLES = r'''
path: C:\temp\testfile.txt path: C:\temp\testfile.txt
line: Line added to file line: Line added to file
newline: unix newline: unix
# Update a line using backrefs
- win_lineinfile:
path: C:\temp\example.conf
backrefs: yes
regexp: '(^name=)'
line: '$1JohnDoe'
''' '''