From 4b57fa91d0bfc8a463ce61bfff62b6f4a4c6dc78 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 27 Apr 2018 07:57:16 +1000 Subject: [PATCH] win_file: Handle [] in paths, fix touch in check mode (#37901) * win_file: Handle [] in paths, fix touch in check mode * Fixed typo for p/invoke command --- lib/ansible/modules/windows/win_file.ps1 | 46 +++++--- .../targets/win_file/tasks/main.yml | 106 +++++++++++++++--- 2 files changed, 120 insertions(+), 32 deletions(-) diff --git a/lib/ansible/modules/windows/win_file.ps1 b/lib/ansible/modules/windows/win_file.ps1 index 7c08ac5ad62..3399b5e30bb 100644 --- a/lib/ansible/modules/windows/win_file.ps1 +++ b/lib/ansible/modules/windows/win_file.ps1 @@ -28,12 +28,19 @@ using System.Runtime.InteropServices; namespace Ansible.Command { public class SymLinkHelper { [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] - public static extern bool RemoveDirectory(string lpPathName); + public static extern bool DeleteFileW(string lpFileName); - public static void DeleteSymLink(string linkPathName) { - bool result = RemoveDirectory(linkPathName); - if (result == false) - throw new Exception(String.Format("Error deleting symlink: {0}", new Win32Exception(Marshal.GetLastWin32Error()).Message)); + [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] + public static extern bool RemoveDirectoryW(string lpPathName); + + public static void DeleteDirectory(string path) { + if (!RemoveDirectoryW(path)) + throw new Exception(String.Format("RemoveDirectoryW({0}) failed: {1}", path, new Win32Exception(Marshal.GetLastWin32Error()).Message)); + } + + public static void DeleteFile(string path) { + if (!DeleteFileW(path)) + throw new Exception(String.Format("DeleteFileW({0}) failed: {1}", path, new Win32Exception(Marshal.GetLastWin32Error()).Message)); } } } @@ -46,13 +53,19 @@ function Remove-File($file, $checkmode) { if ($file.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { # Bug with powershell, if you try and delete a symbolic link that is pointing # to an invalid path it will fail, using Win32 API to do this instead - if (-Not $checkmode) { - [Ansible.Command.SymLinkHelper]::DeleteSymLink($file.FullName) + if ($file.PSIsContainer) { + if (-not $checkmode) { + [Ansible.Command.SymLinkHelper]::DeleteDirectory($file.FullName) + } + } else { + if (-not $checkmode) { + [Ansible.Command.SymlinkHelper]::DeleteFile($file.FullName) + } } } elseif ($file.PSIsContainer) { Remove-Directory -directory $file -checkmode $checkmode } else { - Remove-Item -Path $file.FullName -Force -WhatIf:$checkmode + Remove-Item -LiteralPath $file.FullName -Force -WhatIf:$checkmode } } catch [Exception] { Fail-Json $result "Failed to delete $($file.FullName): $($_.Exception.Message)" @@ -63,21 +76,24 @@ function Remove-Directory($directory, $checkmode) { foreach ($file in Get-ChildItem $directory.FullName) { Remove-File -file $file -checkmode $checkmode } - Remove-Item -Path $directory.FullName -Force -Recurse -WhatIf:$checkmode + Remove-Item -LiteralPath $directory.FullName -Force -Recurse -WhatIf:$checkmode } if ($state -eq "touch") { - if (Test-Path -Path $path) { - (Get-ChildItem -Path $path).LastWriteTime = Get-Date + if (Test-Path -LiteralPath $path) { + if (-not $check_mode) { + (Get-ChildItem -LiteralPath $path).LastWriteTime = Get-Date + } + $result.changed = $true } else { - Write-Output $null | Out-File -FilePath $path -Encoding ASCII -WhatIf:$check_mode + Write-Output $null | Out-File -LiteralPath $path -Encoding ASCII -WhatIf:$check_mode $result.changed = $true } } -if (Test-Path -Path $path) { - $fileinfo = Get-Item -Path $path +if (Test-Path -LiteralPath $path) { + $fileinfo = Get-Item -LiteralPath $path if ($state -eq "absent") { Remove-File -file $fileinfo -checkmode $check_mode $result.changed = $true @@ -109,7 +125,7 @@ if (Test-Path -Path $path) { New-Item -Path $path -ItemType Directory -WhatIf:$check_mode | Out-Null } catch { if ($_.CategoryInfo.Category -eq "ResourceExists") { - $fileinfo = Get-Item $_.CategoryInfo.TargetName + $fileinfo = Get-Item -LiteralPath $_.CategoryInfo.TargetName if ($state -eq "directory" -and -not $fileinfo.PsIsContainer) { Fail-Json $result "path $path is not a directory" } diff --git a/test/integration/targets/win_file/tasks/main.yml b/test/integration/targets/win_file/tasks/main.yml index 4332d053fb1..fe77a22c777 100644 --- a/test/integration/targets/win_file/tasks/main.yml +++ b/test/integration/targets/win_file/tasks/main.yml @@ -41,27 +41,75 @@ - "file2_result.changed == false" # - "file2_result.state == 'absent'" +- name: verify we can touch a file (check) + win_file: + path: '{{win_output_dir}}\touch.txt' + state: touch + register: touch_file_check + check_mode: yes + +- name: get details of touched file (check) + win_stat: + path: '{{win_output_dir}}\touch.txt' + register: touch_file_actual_check + +- name: assert touch a file (check) + assert: + that: + - touch_file_check.changed + - not touch_file_actual_check.stat.exists + - name: verify we can touch a file - win_file: path={{win_output_dir}}/baz.txt state=touch - register: file3_result + win_file: path={{win_output_dir}}/touch.txt state=touch + register: touch_file -- name: verify that the file was marked as changed +- name: get details of touched file + win_stat: + path: '{{win_output_dir}}\touch.txt' + register: touch_file_actual + +- name: assert touch a file assert: that: - - "file3_result.changed == true" -# - "file3_result.state == 'file'" -# - "file3_result.mode == '0644'" + - touch_file.changed + - touch_file_actual.stat.exists + - touch_file_actual.stat.size == 0 -- name: stat the touched file - win_stat: path={{win_output_dir}}/baz.txt state=touch - register: file3_stat_result +- name: touch a file again + win_file: + path: '{{win_output_dir}}\touch.txt' + state: touch + register: touch_file_again + +- name: get details of touched file again + win_stat: + path: '{{win_output_dir}}\touch.txt' + register: touch_file_actual_again -- name: verify that the touched file exists and is size 0 +- name: assert touch a file again assert: that: - - "file3_stat_result.changed == false" - - "file3_stat_result.stat.size == 0" - - "file3_stat_result.stat.exists == true" + - touch_file_again.changed + - touch_file_actual_again.stat.lastwritetime > touch_file_actual.stat.lastwritetime + +- name: touch an existing file in check mode + win_file: + path: '{{win_output_dir}}\touch.txt' + state: touch + register: touch_file_again_check + check_mode: yes + +- name: get details of touched file in check mode + win_stat: + path: '{{win_output_dir}}\touch.txt' + register: touch_file_again_actual_check + +- name: assert touch an existing file in check mode + assert: + that: + - touch_file_again_check.changed + - touch_file_again_actual_check.stat.lastwritetime == touch_file_actual_again.stat.lastwritetime + #- name: change file mode # win_file: path={{win_output_dir}}/baz.txt mode=0600 # register: file4_result @@ -468,11 +516,14 @@ - "stat_result.stat.exists == False" - name: create dir with spaces and parens in the dir name - win_file: path="{{win_output_dir}}/dir with spaces (and parens)" state=directory + win_file: + path: '{{win_output_dir}}\dir with spaces (and parens) [block]' + state: directory register: file_result - name: stat the directory with spaces and parens - win_stat: path="{{win_output_dir}}/dir with spaces (and parens)" + win_stat: + path: '{{win_output_dir}}\dir with spaces (and parens) [block]' register: stat_result - name: check dir with spaces and parens in the dir name has been created @@ -482,12 +533,33 @@ - stat_result.stat.exists - stat_result.stat.isdir +- name: create file in dir with special char + win_file: + path: '{{win_output_dir}}\dir with spaces (and parens) [block]\file[1].txt' + state: touch + register: file_result + +- name: stat the file with spaces and parens + win_stat: + path: '{{win_output_dir}}\dir with spaces (and parens) [block]\file[1].txt' + register: stat_result + +- name: check file in dir with spaces and parens exist + assert: + that: + - file_result.changed + - stat_result.stat.exists + - stat_result.stat.isreg + - name: remove dir with spaces and parens in the dir name - win_file: path="{{win_output_dir}}/dir with spaces (and parens)" state=absent + win_file: + path: '{{win_output_dir}}/dir with spaces (and parens) [block]' + state: absent register: file_result - name: stat the dir with spaces and parens in the dir name - win_stat: path="{{win_output_dir}}/dir with spaces (and parens)" + win_stat: + path: '{{win_output_dir}}\dir with spaces (and parens) [block]' register: stat_result - name: assert dir with spaces and parens in the dir name was removed