mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
win_copy: changes that were missed (#28533)
This commit is contained in:
parent
e631676546
commit
4382216e10
2 changed files with 122 additions and 85 deletions
|
@ -158,10 +158,90 @@ Function Get-FileSize($path) {
|
||||||
$size
|
$size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Function Extract-Zip($src, $dest) {
|
||||||
|
$archive = [System.IO.Compression.ZipFile]::Open($src, [System.IO.Compression.ZipArchiveMode]::Read, [System.Text.Encoding]::UTF8)
|
||||||
|
foreach ($entry in $archive.Entries) {
|
||||||
|
$archive_name = $entry.FullName
|
||||||
|
|
||||||
|
# FullName may be appended with / or \, determine if it is padded and remove it
|
||||||
|
$padding_length = $archive_name.Length % 4
|
||||||
|
if ($padding_length -eq 0) {
|
||||||
|
$is_dir = $false
|
||||||
|
$base64_name = $archive_name
|
||||||
|
} elseif ($padding_length -eq 1) {
|
||||||
|
$is_dir = $true
|
||||||
|
if ($archive_name.EndsWith("/") -or $archive_name.EndsWith("`\")) {
|
||||||
|
$base64_name = $archive_name.Substring(0, $archive_name.Length - 1)
|
||||||
|
} else {
|
||||||
|
throw "invalid base64 archive name $archive_name"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw "invalid base64 length $archive_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
# to handle unicode character, win_copy action plugin has encoded the filename
|
||||||
|
$decoded_archive_name = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64_name))
|
||||||
|
# re-add the / to the entry full name if it was a directory
|
||||||
|
if ($is_dir) {
|
||||||
|
$decoded_archive_name = "$decoded_archive_name/"
|
||||||
|
}
|
||||||
|
$entry_target_path = [System.IO.Path]::Combine($dest, $decoded_archive_name)
|
||||||
|
$entry_dir = [System.IO.Path]::GetDirectoryName($entry_target_path)
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path $entry_dir)) {
|
||||||
|
New-Item -Path $entry_dir -ItemType Directory -WhatIf:$check_mode | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_dir -eq $false) {
|
||||||
|
if (-not $check_mode) {
|
||||||
|
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $entry_target_path, $true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Function Extract-ZipLegacy($src, $dest) {
|
||||||
|
if (-not (Test-Path -Path $dest)) {
|
||||||
|
New-Item -Path $dest -ItemType Directory -WhatIf:$check_mode | Out-Null
|
||||||
|
}
|
||||||
|
$shell = New-Object -ComObject Shell.Application
|
||||||
|
$zip = $shell.NameSpace($src)
|
||||||
|
$dest_path = $shell.NameSpace($dest)
|
||||||
|
|
||||||
|
foreach ($entry in $zip.Items()) {
|
||||||
|
$is_dir = $entry.IsFolder
|
||||||
|
$encoded_archive_entry = $entry.Name
|
||||||
|
# to handle unicode character, win_copy action plugin has encoded the filename
|
||||||
|
$decoded_archive_entry = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encoded_archive_entry))
|
||||||
|
if ($is_dir) {
|
||||||
|
$decoded_archive_entry = "$decoded_archive_entry/"
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry_target_path = [System.IO.Path]::Combine($dest, $decoded_archive_entry)
|
||||||
|
$entry_dir = [System.IO.Path]::GetDirectoryName($entry_target_path)
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path $entry_dir)) {
|
||||||
|
New-Item -Path $entry_dir -ItemType Directory -WhatIf:$check_mode | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_dir -eq $false -and (-not $check_mode)) {
|
||||||
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/bb787866.aspx
|
||||||
|
# From Folder.CopyHere documentation, 1044 means:
|
||||||
|
# - 1024: do not display a user interface if an error occurs
|
||||||
|
# - 16: respond with "yes to all" for any dialog box that is displayed
|
||||||
|
# - 4: do not display a progress dialog box
|
||||||
|
$dest_path.CopyHere($entry, 1044)
|
||||||
|
|
||||||
|
# once file is extraced, we need to rename it with non base64 name
|
||||||
|
$combined_encoded_path = [System.IO.Path]::Combine($dest, $encoded_archive_entry)
|
||||||
|
Move-Item -Path $combined_encoded_path -Destination $entry_target_path -Force | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($mode -eq "query") {
|
if ($mode -eq "query") {
|
||||||
# we only return a list of files/directories that need to be copied over
|
# we only return a list of files/directories that need to be copied over
|
||||||
# the source of the local file will be the key used
|
# the source of the local file will be the key used
|
||||||
$will_change = $false
|
|
||||||
$changed_files = @()
|
$changed_files = @()
|
||||||
$changed_directories = @()
|
$changed_directories = @()
|
||||||
$changed_symlinks = @()
|
$changed_symlinks = @()
|
||||||
|
@ -182,7 +262,6 @@ if ($mode -eq "query") {
|
||||||
} elseif (Test-Path -Path $filepath -PathType Container) {
|
} elseif (Test-Path -Path $filepath -PathType Container) {
|
||||||
Fail-Json -obj $result -message "cannot copy file to dest $($filepath): object at path is already a directory"
|
Fail-Json -obj $result -message "cannot copy file to dest $($filepath): object at path is already a directory"
|
||||||
} else {
|
} else {
|
||||||
$will_change = $true
|
|
||||||
$changed_files += $file
|
$changed_files += $file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,24 +277,12 @@ if ($mode -eq "query") {
|
||||||
if (Test-Path -Path $dirpath -PathType Leaf) {
|
if (Test-Path -Path $dirpath -PathType Leaf) {
|
||||||
Fail-Json -obj $result -message "cannot copy folder to dest $($dirpath): object at path is already a file"
|
Fail-Json -obj $result -message "cannot copy folder to dest $($dirpath): object at path is already a file"
|
||||||
} elseif (-not (Test-Path -Path $dirpath -PathType Container)) {
|
} elseif (-not (Test-Path -Path $dirpath -PathType Container)) {
|
||||||
$will_change = $true
|
|
||||||
$changed_directories += $directory
|
$changed_directories += $directory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: Handle symlinks
|
# TODO: Handle symlinks
|
||||||
|
|
||||||
# Detect if the PS zip assemblies are available, this will control whether
|
|
||||||
# the win_copy plugin will use explode as the mode or single
|
|
||||||
try {
|
|
||||||
Add-Type -Assembly System.IO.Compression.FileSystem | Out-Null
|
|
||||||
Add-Type -Assembly System.IO.Compression | Out-Null
|
|
||||||
$result.zip_available = $true
|
|
||||||
} catch {
|
|
||||||
$result.zip_available = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
$result.will_change = $will_change
|
|
||||||
$result.files = $changed_files
|
$result.files = $changed_files
|
||||||
$result.directories = $changed_directories
|
$result.directories = $changed_directories
|
||||||
$result.symlinks = $changed_symlinks
|
$result.symlinks = $changed_symlinks
|
||||||
|
@ -227,23 +294,18 @@ if ($mode -eq "query") {
|
||||||
Fail-Json -obj $result -message "Cannot expand src zip file file: $src as it does not exist"
|
Fail-Json -obj $result -message "Cannot expand src zip file file: $src as it does not exist"
|
||||||
}
|
}
|
||||||
|
|
||||||
Add-Type -Assembly System.IO.Compression.FileSystem | Out-Null
|
# Detect if the PS zip assemblies are available or whether to use Shell
|
||||||
Add-Type -Assembly System.IO.Compression | Out-Null
|
$use_legacy = $false
|
||||||
|
try {
|
||||||
$archive = [System.IO.Compression.ZipFile]::Open($src, [System.IO.Compression.ZipArchiveMode]::Read, [System.Text.Encoding]::UTF8)
|
Add-Type -AssemblyName System.IO.Compression.FileSystem | Out-Null
|
||||||
foreach ($entry in $archive.Entries) {
|
Add-Type -AssemblyName System.IO.Compression | Out-Null
|
||||||
$entry_target_path = [System.IO.Path]::Combine($dest, $entry.FullName)
|
} catch {
|
||||||
$entry_dir = [System.IO.Path]::GetDirectoryName($entry_target_path)
|
$use_legacy = $true
|
||||||
|
}
|
||||||
if (-not (Test-Path -Path $entry_dir)) {
|
if ($use_legacy) {
|
||||||
New-Item -Path $entry_dir -ItemType Directory -WhatIf:$check_mode | Out-Null
|
Extract-ZipLegacy -src $src -dest $dest
|
||||||
}
|
} else {
|
||||||
|
Extract-Zip -src $src -dest $dest
|
||||||
if (-not ($entry_target_path.EndsWith("`\") -or $entry_target_path.EndsWith("/"))) {
|
|
||||||
if (-not $check_mode) {
|
|
||||||
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $entry_target_path, $true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$result.changed = $true
|
$result.changed = $true
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
from __future__ import (absolute_import, division, print_function)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import base64
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
|
@ -231,17 +232,22 @@ class ActionModule(ActionBase):
|
||||||
zip_file_path = os.path.join(tmpdir, "win_copy.zip")
|
zip_file_path = os.path.join(tmpdir, "win_copy.zip")
|
||||||
zip_file = zipfile.ZipFile(zip_file_path, "w")
|
zip_file = zipfile.ZipFile(zip_file_path, "w")
|
||||||
|
|
||||||
# need to write in byte string with utf-8 encoding to support unicode
|
# encoding the file/dir name with base64 so Windows can unzip a unicode
|
||||||
# characters in the filename.
|
# filename and get the right name, Windows doesn't handle unicode names
|
||||||
|
# very well
|
||||||
for directory in directories:
|
for directory in directories:
|
||||||
directory_path = to_bytes(directory['src'], errors='surrogate_or_strict')
|
directory_path = to_bytes(directory['src'], errors='surrogate_or_strict')
|
||||||
archive_path = to_bytes(directory['dest'], errors='surrogate_or_strict')
|
archive_path = to_bytes(directory['dest'], errors='surrogate_or_strict')
|
||||||
zip_file.write(directory_path, archive_path, zipfile.ZIP_DEFLATED)
|
|
||||||
|
encoded_path = to_text(base64.b64encode(archive_path), errors='surrogate_or_strict')
|
||||||
|
zip_file.write(directory_path, encoded_path, zipfile.ZIP_DEFLATED)
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
file_path = to_bytes(file['src'], errors='surrogate_or_strict')
|
file_path = to_bytes(file['src'], errors='surrogate_or_strict')
|
||||||
archive_path = to_bytes(file['dest'], errors='surrogate_or_strict')
|
archive_path = to_bytes(file['dest'], errors='surrogate_or_strict')
|
||||||
zip_file.write(file_path, archive_path, zipfile.ZIP_DEFLATED)
|
|
||||||
|
encoded_path = to_text(base64.b64encode(archive_path), errors='surrogate_or_strict')
|
||||||
|
zip_file.write(file_path, encoded_path, zipfile.ZIP_DEFLATED)
|
||||||
|
|
||||||
return zip_file_path
|
return zip_file_path
|
||||||
|
|
||||||
|
@ -249,20 +255,6 @@ class ActionModule(ActionBase):
|
||||||
if content is not None:
|
if content is not None:
|
||||||
os.remove(content_tempfile)
|
os.remove(content_tempfile)
|
||||||
|
|
||||||
def _create_directory(self, dest, source_rel, task_vars):
|
|
||||||
dest_path = self._connection._shell.join_path(dest, source_rel)
|
|
||||||
file_args = self._task.args.copy()
|
|
||||||
file_args.update(
|
|
||||||
dict(
|
|
||||||
path=dest_path,
|
|
||||||
state="directory"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
file_args.pop('content', None)
|
|
||||||
|
|
||||||
file_result = self._execute_module(module_name='file', module_args=file_args, task_vars=task_vars)
|
|
||||||
return file_result
|
|
||||||
|
|
||||||
def _copy_single_file(self, local_file, dest, source_rel, task_vars):
|
def _copy_single_file(self, local_file, dest, source_rel, task_vars):
|
||||||
if self._play_context.check_mode:
|
if self._play_context.check_mode:
|
||||||
module_return = dict(changed=True)
|
module_return = dict(changed=True)
|
||||||
|
@ -311,9 +303,9 @@ class ActionModule(ActionBase):
|
||||||
os.removedirs(os.path.dirname(zip_path))
|
os.removedirs(os.path.dirname(zip_path))
|
||||||
return module_return
|
return module_return
|
||||||
|
|
||||||
# send zip file to remote
|
# send zip file to remote, file must end in .zip so Com Shell.Application works
|
||||||
tmp_path = self._make_tmp_path()
|
tmp_path = self._make_tmp_path()
|
||||||
tmp_src = self._connection._shell.join_path(tmp_path, 'source')
|
tmp_src = self._connection._shell.join_path(tmp_path, 'source.zip')
|
||||||
self._transfer_file(zip_path, tmp_src)
|
self._transfer_file(zip_path, tmp_src)
|
||||||
|
|
||||||
# run the explode operation of win_copy on remote
|
# run the explode operation of win_copy on remote
|
||||||
|
@ -475,47 +467,30 @@ class ActionModule(ActionBase):
|
||||||
query_args.pop('content', None)
|
query_args.pop('content', None)
|
||||||
query_return = self._execute_module(module_args=query_args, task_vars=task_vars)
|
query_return = self._execute_module(module_args=query_args, task_vars=task_vars)
|
||||||
|
|
||||||
if query_return.get('failed', False) is True:
|
if query_return.get('failed') is True:
|
||||||
result.update(query_return)
|
result.update(query_return)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
if query_return.get('will_change') is False:
|
if len(query_return['files']) == 1 and len(query_return['directories']) == 0:
|
||||||
# no changes need to occur
|
# we only need to copy 1 file, don't mess around with zips
|
||||||
result['failed'] = False
|
file_src = query_return['files'][0]['src']
|
||||||
result['changed'] = False
|
file_dest = query_return['files'][0]['dest']
|
||||||
return result
|
copy_result = self._copy_single_file(file_src, dest, file_dest, task_vars)
|
||||||
|
|
||||||
if query_return.get('zip_available') is True and result['operation'] != 'file_copy':
|
result['changed'] = True
|
||||||
# if the PS zip utils are available and we need to copy more than a
|
if copy_result.get('failed') is True:
|
||||||
# single file/folder, create a local zip file of all the changed
|
result['failed'] = True
|
||||||
# files and send that to the server to be expanded
|
result['msg'] = "failed to copy file %s: %s" % (file_src, copy_result['msg'])
|
||||||
|
elif len(query_return['files']) > 0 or len(query_return['directories']) > 0:
|
||||||
|
# either multiple files or directories need to be copied, compress
|
||||||
|
# to a zip and 'explode' the zip on the server
|
||||||
# TODO: handle symlinks
|
# TODO: handle symlinks
|
||||||
result.update(self._copy_zip_file(dest, source_files['files'], source_files['directories'], task_vars))
|
result.update(self._copy_zip_file(dest, source_files['files'], source_files['directories'], task_vars))
|
||||||
|
result['changed'] = True
|
||||||
else:
|
else:
|
||||||
# the PS zip assemblies are not available or only a single file
|
# no operations need to occur
|
||||||
# needs to be copied. Instead of zipping up into one task this
|
result['failed'] = False
|
||||||
# will handle each file/folder as an individual task
|
result['changed'] = False
|
||||||
# TODO: Handle symlinks
|
|
||||||
|
|
||||||
for directory in query_return['directories']:
|
|
||||||
file_result = self._create_directory(dest, directory['dest'], task_vars)
|
|
||||||
|
|
||||||
result['changed'] = file_result.get('changed', False)
|
|
||||||
if file_result.get('failed', False) is True:
|
|
||||||
self._remove_tempfile_if_content_defined(content, content_tempfile)
|
|
||||||
result['failed'] = True
|
|
||||||
result['msg'] = "failed to create directory %s" % file_result['msg']
|
|
||||||
return result
|
|
||||||
|
|
||||||
for file in query_return['files']:
|
|
||||||
copy_result = self._copy_single_file(file['src'], dest, file['dest'], task_vars)
|
|
||||||
|
|
||||||
result['changed'] = copy_result.get('changed', False)
|
|
||||||
if copy_result.get('failed', False) is True:
|
|
||||||
self._remove_tempfile_if_content_defined(content, content_tempfile)
|
|
||||||
result['failed'] = True
|
|
||||||
result['msg'] = "failed to copy file %s: %s" % (file['src'], copy_result['msg'])
|
|
||||||
return result
|
|
||||||
|
|
||||||
# remove the content temp file if it was created
|
# remove the content temp file if it was created
|
||||||
self._remove_tempfile_if_content_defined(content, content_tempfile)
|
self._remove_tempfile_if_content_defined(content, content_tempfile)
|
||||||
|
|
Loading…
Add table
Reference in a new issue