From 5b6195e61395996eac15ea961e2a96d21b75f2cd Mon Sep 17 00:00:00 2001 From: Jon Hawkesworth Date: Fri, 29 May 2015 02:57:13 +0100 Subject: [PATCH] Fix win_copy problems described here: https://github.com/ansible/ansible-modules-core/issues/1404 and update documentation. --- lib/ansible/modules/windows/win_copy.ps1 | 86 +++++++++++++-------- lib/ansible/modules/windows/win_copy.py | 49 +++++++++++- lib/ansible/modules/windows/win_file.ps1 | 53 ++++++++----- lib/ansible/modules/windows/win_file.py | 4 +- lib/ansible/modules/windows/win_template.py | 22 +++--- 5 files changed, 144 insertions(+), 70 deletions(-) diff --git a/lib/ansible/modules/windows/win_copy.ps1 b/lib/ansible/modules/windows/win_copy.ps1 index 9ffdab85f03..4a83e091c56 100644 --- a/lib/ansible/modules/windows/win_copy.ps1 +++ b/lib/ansible/modules/windows/win_copy.ps1 @@ -17,68 +17,88 @@ # WANT_JSON # POWERSHELL_COMMON -$params = Parse-Args $args; +$params = Parse-Args $args -$src= Get-Attr $params "src" $FALSE; +$src= Get-Attr $params "src" $FALSE If ($src -eq $FALSE) { - Fail-Json (New-Object psobject) "missing required argument: src"; + Fail-Json (New-Object psobject) "missing required argument: src" } -$dest= Get-Attr $params "dest" $FALSE; +$dest= Get-Attr $params "dest" $FALSE If ($dest -eq $FALSE) { - Fail-Json (New-Object psobject) "missing required argument: dest"; + Fail-Json (New-Object psobject) "missing required argument: dest" } -# seems to be supplied by the calling environment, but -# probably shouldn't be a test for it existing in the params. -# TODO investigate. -$original_basename = Get-Attr $params "original_basename" $FALSE; +$original_basename = Get-Attr $params "original_basename" $FALSE If ($original_basename -eq $FALSE) { - Fail-Json (New-Object psobject) "missing required argument: original_basename "; + Fail-Json (New-Object psobject) "missing required argument: original_basename " } $result = New-Object psobject @{ changed = $FALSE -}; + original_basename = $original_basename +} + +# original_basename gets set if src and dest are dirs +# but includes subdir if the source folder contains sub folders +# e.g. you could get subdir/foo.txt + +# detect if doing recursive folder copy and create any non-existent destination sub folder +$parent = Split-Path -Path $original_basename -Parent +if ($parent.length -gt 0) +{ + $dest_folder = Join-Path $dest $parent + New-Item -Force $dest_folder -Type directory +} # if $dest is a dir, append $original_basename so the file gets copied with its intended name. if (Test-Path $dest -PathType Container) { - $dest = Join-Path $dest $original_basename; + $dest = Join-Path $dest $original_basename } -If (Test-Path $dest) -{ - $dest_checksum = Get-FileChecksum ($dest); - $src_checksum = Get-FileChecksum ($src); +$dest_checksum = Get-FileChecksum ($dest) +$src_checksum = Get-FileChecksum ($src) - If (! $src_checksum.CompareTo($dest_checksum)) +If ($src_checksum.Equals($dest_checksum)) +{ + # if both are "3" then both are folders, ok to copy + If ($src_checksum.Equals("3")) { - # New-Item -Force creates subdirs for recursive copies - New-Item -Force $dest -Type file; - Copy-Item -Path $src -Destination $dest -Force; + # New-Item -Force creates subdirs for recursive copies + New-Item -Force $dest -Type file + Copy-Item -Path $src -Destination $dest -Force + $result.operation = "folder_copy" } - $dest_checksum = Get-FileChecksum ($dest); - If ( $src_checksum.CompareTo($dest_checksum)) + +} +ElseIf (! $src_checksum.Equals($dest_checksum)) +{ + If ($src_checksum.Equals("3")) { - $result.changed = $TRUE; - } - Else - { - Fail-Json (New-Object psobject) "Failed to place file"; + Fail-Json (New-Object psobject) "If src is a folder, dest must also be a folder" } + # The checksums don't match, there's something to do + Copy-Item -Path $src -Destination $dest -Force + $result.operation = "file_copy" +} + +# verify before we return that the file has changed +$dest_checksum = Get-FileChecksum ($dest) +If ( $src_checksum.Equals($dest_checksum)) +{ + $result.changed = $TRUE } Else { - New-Item -Force $dest -Type file; - Copy-Item -Path $src -Destination $dest; - $result.changed = $TRUE; + Fail-Json (New-Object psobject) "src checksum $src_checksum did not match dest_checksum $dest_checksum Failed to place file $original_basename in $dest" } +# generate return values -$dest_checksum = Get-FileChecksum($dest); -$result.checksum = $dest_checksum; +$info = Get-Item $dest +$result.size = $info.Length -Exit-Json $result; +Exit-Json $result diff --git a/lib/ansible/modules/windows/win_copy.py b/lib/ansible/modules/windows/win_copy.py index 16b6859488f..d77f37f64d0 100644 --- a/lib/ansible/modules/windows/win_copy.py +++ b/lib/ansible/modules/windows/win_copy.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# (c) 2012, Michael DeHaan +# (c) 2015, Jon Hawkesworth (@jhawkesworth) # # This file is part of Ansible # @@ -45,16 +45,57 @@ options: this must be a directory too. Use \\ for path separators. required: true default: null -author: Michael DeHaan +author: "Jon Hawkesworth (@jhawkesworth)" notes: - The "win_copy" module recursively copy facility does not scale to lots (>hundreds) of files. Instead, you may find it better to create files locally, perhaps using win_template, and - then use win_get_url to put them in the correct location. + then use win_get_url to fetch them from your managed hosts into the correct location. ''' EXAMPLES = ''' -# Example from Ansible Playbooks +# Copy a single file - win_copy: src=/srv/myfiles/foo.conf dest=c:\\TEMP\\foo.conf +# Copy the contents of files/temp_files dir into c:\temp\. Includes any sub dirs under files/temp_files +# Note the use of unix style path in the dest. +# This is necessary because \ is yaml escape sequence +- win_copy: src=files/temp_files/ dest=c:/temp/ + +# Copy the files/temp_files dir and any files or sub dirs into c:\temp +# Copies the folder because there is no trailing / on 'files/temp_files' +- win_copy: src=files/temp_files dest=c:/temp/ + +''' +RETURN = ''' +dest: + description: destination file/path + returned: changed + type: string + sample: "c:/temp/" +src: + description: source file used for the copy on the target machine + returned: changed + type: string + sample: "/home/httpd/.ansible/tmp/ansible-tmp-1423796390.97-147729857856000/source" +checksum: + description: checksum of the file after running copy + returned: success + type: string + sample: "6e642bb8dd5c2e027bf21dd923337cbb4214f827" +size: + description: size of the target, after execution + returned: changed (single files only) + type: int + sample: 1220 +operation: + description: whether a single file copy took place or a folder copy + returned: changed (single files only) + type: string + sample: "file_copy" +original_basename: + description: basename of the copied file + returned: changed (single files only) + type: string + sample: "foo.txt" ''' diff --git a/lib/ansible/modules/windows/win_file.ps1 b/lib/ansible/modules/windows/win_file.ps1 index 62ac81fc1ee..0f3c20ec8e3 100644 --- a/lib/ansible/modules/windows/win_file.ps1 +++ b/lib/ansible/modules/windows/win_file.ps1 @@ -17,19 +17,19 @@ # WANT_JSON # POWERSHELL_COMMON -$params = Parse-Args $args; +$params = Parse-Args $args # path -$path = Get-Attr $params "path" $FALSE; +$path = Get-Attr $params "path" $FALSE If ($path -eq $FALSE) { - $path = Get-Attr $params "dest" $FALSE; + $path = Get-Attr $params "dest" $FALSE If ($path -eq $FALSE) { - $path = Get-Attr $params "name" $FALSE; + $path = Get-Attr $params "name" $FALSE If ($path -eq $FALSE) { - Fail-Json (New-Object psobject) "missing required argument: path"; + Fail-Json (New-Object psobject) "missing required argument: path" } } } @@ -39,17 +39,14 @@ If ($path -eq $FALSE) # state - file, directory, touch, absent # (originally was: state - file, link, directory, hard, touch, absent) -$state = Get-Attr $params "state" "file"; - -#$recurse = Get-Attr $params "recurse" "no"; - -# force - yes, no -# $force = Get-Attr $params "force" "no"; +$state = Get-Attr $params "state" "unspecified" +# if state is not supplied, test the $path to see if it looks like +# a file or a folder and set state to file or folder # result $result = New-Object psobject @{ changed = $FALSE -}; +} If ( $state -eq "touch" ) { @@ -61,45 +58,59 @@ If ( $state -eq "touch" ) { echo $null > $file } - $result.changed = $TRUE; + $result.changed = $TRUE } If (Test-Path $path) { - $fileinfo = Get-Item $path; + $fileinfo = Get-Item $path If ( $state -eq "absent" ) { - Remove-Item -Recurse -Force $fileinfo; - $result.changed = $TRUE; + Remove-Item -Recurse -Force $fileinfo + $result.changed = $TRUE } Else { # Only files have the .Directory attribute. If ( $state -eq "directory" -and $fileinfo.Directory ) { - Fail-Json (New-Object psobject) "path is not a directory"; + Fail-Json (New-Object psobject) "path is not a directory" } # Only files have the .Directory attribute. If ( $state -eq "file" -and -not $fileinfo.Directory ) { - Fail-Json (New-Object psobject) "path is not a file"; + Fail-Json (New-Object psobject) "path is not a file" } } } Else +# doesn't yet exist { + If ( $state -eq "unspecified" ) + { + $basename = Split-Path -Path $path -Leaf + If ($basename.length -gt 0) + { + $state = "file" + } + Else + { + $state = "directory" + } + } + If ( $state -eq "directory" ) { New-Item -ItemType directory -Path $path - $result.changed = $TRUE; + $result.changed = $TRUE } If ( $state -eq "file" ) { - Fail-Json (New-Object psobject) "path will not be created"; + Fail-Json (New-Object psobject) "path will not be created" } } -Exit-Json $result; +Exit-Json $result diff --git a/lib/ansible/modules/windows/win_file.py b/lib/ansible/modules/windows/win_file.py index 6a218216617..4953dd9363b 100644 --- a/lib/ansible/modules/windows/win_file.py +++ b/lib/ansible/modules/windows/win_file.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# (c) 2012, Michael DeHaan +# (c) 2015, Jon Hawkesworth (@jhawkesworth) # # This file is part of Ansible # @@ -32,7 +32,7 @@ description: notes: - See also M(win_copy), M(win_template), M(copy), M(template), M(assemble) requirements: [ ] -author: Michael DeHaan +author: "Jon Hawkesworth (@jhawkesworth)" options: path: description: diff --git a/lib/ansible/modules/windows/win_template.py b/lib/ansible/modules/windows/win_template.py index d95d1125fcc..7f981c33daf 100644 --- a/lib/ansible/modules/windows/win_template.py +++ b/lib/ansible/modules/windows/win_template.py @@ -31,22 +31,24 @@ options: - Location to render the template to on the remote machine. required: true default: null - backup: - description: - - Create a backup file including the timestamp information so you can get - the original file back if you somehow clobbered it incorrectly. - required: false - choices: [ "yes", "no" ] - default: "no" notes: - "templates are loaded with C(trim_blocks=True)." + - By default, windows line endings are not created in the generated file. + - In order to ensure windows line endings are in the generated file, + add the following header as the first line of your template: + "#jinja2: newline_sequence:'\r\n'" + and ensure each line of the template ends with \r\n + - Beware fetching files from windows machines when creating templates + because certain tools, such as Powershell ISE, and regedit's export facility + add a Byte Order Mark as the first character of the file, which can cause tracebacks. + - Use "od -cx" to examine your templates for Byte Order Marks. requirements: [] -author: Michael DeHaan +author: "Jon Hawkesworth (@jhawkesworth)" ''' EXAMPLES = ''' -# Example -- win_template: src=/mytemplates/foo.j2 dest=C:\\temp\\file.conf +# Playbook Example (win_template can only be run inside a playbook) +- win_template: src=/mytemplates/file.conf.j2 dest=C:\\temp\\file.conf '''