From 54de41309f358fe16127adaffcb321261abfbdcc Mon Sep 17 00:00:00 2001 From: johnnysheppard-isode Date: Thu, 16 Feb 2017 01:09:30 +0000 Subject: [PATCH] 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. --- .../modules/windows/win_lineinfile.ps1 | 103 ++++++++++-------- lib/ansible/modules/windows/win_lineinfile.py | 7 ++ 2 files changed, 67 insertions(+), 43 deletions(-) diff --git a/lib/ansible/modules/windows/win_lineinfile.ps1 b/lib/ansible/modules/windows/win_lineinfile.ps1 index 8a41790810..d133a82808 100644 --- a/lib/ansible/modules/windows/win_lineinfile.ps1 +++ b/lib/ansible/modules/windows/win_lineinfile.ps1 @@ -29,11 +29,11 @@ $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 $params "backrefs" "no"; +$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 "create" "no"; -$backup = Get-Attr $params "backup" "no"; +$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"; @@ -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. 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; [System.IO.File]::WriteAllText($temppath, $joined, $encodingobj); - + If ($validate -ne $FALSE) { - + If (!($validate -like "*%s*")) { 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(" "); $cmdname = $parts[0]; - + $cmdargs = $validate.Substring($cmdname.Length + 1); - + $process = [Diagnostics.Process]::Start($cmdname, $cmdargs); $process.WaitForExit(); - + If ($process.ExitCode -ne 0) { [string] $output = $process.StandardOutput.ReadToEnd(); [string] $error = $process.StandardError.ReadToEnd(); @@ -95,11 +100,23 @@ function WriteLines($outlines, $path, $linesep, $encodingobj, $validate) { } } - + # Commit changes to the path $cleanpath = $path.Replace("/", "\"); - Copy-Item $temppath $cleanpath -force; - Remove-Item $temppath -force; + Try { + 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) { - # 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. $cleanpath = $path.Replace("/", "\"); # Check if path exists. If it does not exist, either create it if create == "yes" # was specified or fail with a reasonable error message. If (!(Test-Path $path)) { - If ($create -eq "no") { + If (-not $create) { Fail-Json (New-Object psobject) "Path $path does not exist !"; } # 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 { $lines = [System.Collections.ArrayList] $content; } - + # Compile the regex specified, if provided $mre = $FALSE; If ($regexp -ne $FALSE) { $mre = New-Object Regex $regexp, 'Compiled'; } - + # Compile the regex for insertafter or insertbefore, if provided $insre = $FALSE; - + If ($insertafter -ne $FALSE -and $insertafter -ne "BOF" -and $insertafter -ne "EOF") { $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, -1; $lineno = 0; - + # The latest match object and matched line $matched_line = ""; $m = $FALSE; @@ -186,16 +203,16 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b } If ($insertbefore -ne $FALSE) { $index[1] = $lineno; - } + } } $lineno = $lineno + 1; } - + $changed = $FALSE; $msg = ""; If ($index[0] -ne -1) { - If ($backrefs -ne "no") { + If ($backrefs) { $new_line = [regex]::Replace($matched_line, $regexp, $line); } Else { @@ -207,7 +224,7 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b $changed = $TRUE; } } - ElseIf ($backrefs -ne "no") { + ElseIf ($backrefs) { # No matches - no-op } 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" $backuppath = ""; - If ($changed -eq $TRUE -and $backup -eq "yes") { + If ($changed -eq $TRUE -and $backup -eq $TRUE) { $backuppath = BackupFile $path; } - + # Write changes to the path if changes were made If ($changed) { WriteLines $lines $path $linesep $encodingobj $validate; @@ -247,7 +264,7 @@ function Present($path, $regexp, $line, $insertafter, $insertbefore, $create, $b backup = $backuppath encoding = $encodingstr } - + 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 - # 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. - + $cleanpath = $path.Replace("/", "\"); $content = [System.IO.File]::ReadAllLines($cleanpath, $encodingobj); If ($content -eq $null) { @@ -273,7 +290,7 @@ function Absent($path, $regexp, $line, $backup, $validate, $encodingobj, $linese Else { $lines = [System.Collections.ArrayList] $content; } - + # Initialize message to be returned on success $msg = ""; @@ -307,10 +324,10 @@ function Absent($path, $regexp, $line, $backup, $validate, $encodingobj, $linese # Write backup file if backup == "yes" $backuppath = ""; - If ($changed -eq $TRUE -and $backup -eq "yes") { + If ($changed -eq $TRUE -and $backup -eq $TRUE) { $backuppath = BackupFile $path; } - + # Write changes to the path if changes were made If ($changed) { WriteLines $left $path $linesep $encodingobj $validate; @@ -328,7 +345,7 @@ function Absent($path, $regexp, $line, $backup, $validate, $encodingobj, $linese found = $fcount encoding = $encodingstr } - + Exit-Json $result; } @@ -362,7 +379,7 @@ If ($encoding -ne "auto") { } # 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. 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 - + [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. - + $found = $FALSE; Foreach ($encoding in $sortedlist.GetValueList()) { $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. 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"; } - + If ($line -eq $FALSE) { Fail-Json (New-Object psobject) "line= is required with state=present"; } - + If ($insertbefore -eq $FALSE -and $insertafter -eq $FALSE) { $insertafter = "EOF"; } @@ -436,6 +453,6 @@ Else { If ($regexp -eq $FALSE -and $line -eq $FALSE) { Fail-Json (New-Object psobject) "one of line= or regexp= is required with state=absent"; } - + Absent $path $regexp $line $backup $validate $encodingobj $linesep; } diff --git a/lib/ansible/modules/windows/win_lineinfile.py b/lib/ansible/modules/windows/win_lineinfile.py index 3d15343372..feb9054bdf 100644 --- a/lib/ansible/modules/windows/win_lineinfile.py +++ b/lib/ansible/modules/windows/win_lineinfile.py @@ -147,4 +147,11 @@ EXAMPLES = r''' path: C:\temp\testfile.txt line: Line added to file newline: unix + +# Update a line using backrefs +- win_lineinfile: + path: C:\temp\example.conf + backrefs: yes + regexp: '(^name=)' + line: '$1JohnDoe' '''