diff --git a/changelogs/fragments/win_unzip-paths.yaml b/changelogs/fragments/win_unzip-paths.yaml new file mode 100644 index 00000000000000..76e4b9acaf2461 --- /dev/null +++ b/changelogs/fragments/win_unzip-paths.yaml @@ -0,0 +1,2 @@ +bugfixes: +- win_unzip - Fix support for paths with square brackets not being detected properly diff --git a/lib/ansible/modules/windows/win_unzip.ps1 b/lib/ansible/modules/windows/win_unzip.ps1 index a5c194415b7324..234c774c3a6cbd 100644 --- a/lib/ansible/modules/windows/win_unzip.ps1 +++ b/lib/ansible/modules/windows/win_unzip.ps1 @@ -40,7 +40,7 @@ Function Extract-Zip($src, $dest) { $entry_target_path = [System.IO.Path]::Combine($dest, $archive_name) $entry_dir = [System.IO.Path]::GetDirectoryName($entry_target_path) - if (-not (Test-Path -Path $entry_dir)) { + if (-not (Test-Path -LiteralPath $entry_dir)) { New-Item -Path $entry_dir -ItemType Directory -WhatIf:$check_mode | Out-Null $result.changed = $true } @@ -142,14 +142,14 @@ If ($ext -eq ".zip" -And $recurse -eq $false) { } If ($recurse) { - Get-ChildItem $dest -recurse | Where-Object {$pcx_extensions -contains $_.extension} | ForEach-Object { + Get-ChildItem -LiteralPath $dest -recurse | Where-Object {$pcx_extensions -contains $_.extension} | ForEach-Object { Try { Expand-Archive $_.FullName -OutputPath $dest -Force -WhatIf:$check_mode } Catch { Fail-Json -obj $result -message "Error recursively expanding '$src' to '$dest'! Msg: $($_.Exception.Message)" } If ($delete_archive) { - Remove-Item $_.FullName -Force -WhatIf:$check_mode + Remove-Item -LiteralPath $_.FullName -Force -WhatIf:$check_mode $result.removed = $true } } @@ -160,7 +160,7 @@ If ($ext -eq ".zip" -And $recurse -eq $false) { If ($delete_archive){ try { - Remove-Item $src -Recurse -Force -WhatIf:$check_mode + Remove-Item -LiteralPath $src -Recurse -Force -WhatIf:$check_mode } catch { Fail-Json -obj $result -message "failed to delete archive at '$src': $($_.Exception.Message)" } diff --git a/test/integration/targets/win_unzip/defaults/main.yml b/test/integration/targets/win_unzip/defaults/main.yml new file mode 100644 index 00000000000000..52d808d88c0638 --- /dev/null +++ b/test/integration/targets/win_unzip/defaults/main.yml @@ -0,0 +1 @@ +win_unzip_dir: '{{ remote_tmp_dir }}\win_unzip .ÅÑŚÌβŁÈ [$!@^&test(;)]' diff --git a/test/integration/targets/win_unzip/files/create_zip.py b/test/integration/targets/win_unzip/files/create_zip.py new file mode 100644 index 00000000000000..41b6ff068b45fa --- /dev/null +++ b/test/integration/targets/win_unzip/files/create_zip.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import tempfile +import zipfile + + +def main(): + filename = b"caf\xc3\xa9.txt" + + with tempfile.NamedTemporaryFile() as temp: + with open(temp.name, mode="wb") as fd: + fd.write(filename) + + with open(sys.argv[1], mode="wb") as fd: + with zipfile.ZipFile(fd, "w") as zip: + zip.write(temp.name, filename.decode('utf-8')) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/win_unzip/meta/main.yml b/test/integration/targets/win_unzip/meta/main.yml new file mode 100644 index 00000000000000..9f37e96cd90108 --- /dev/null +++ b/test/integration/targets/win_unzip/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: +- setup_remote_tmp_dir diff --git a/test/integration/targets/win_unzip/tasks/clean.yml b/test/integration/targets/win_unzip/tasks/clean.yml deleted file mode 100644 index 64ac0d3fbf82d7..00000000000000 --- a/test/integration/targets/win_unzip/tasks/clean.yml +++ /dev/null @@ -1,15 +0,0 @@ -- name: Remove leftover directory - win_file: - path: C:\Program Files\sysinternals - state: absent - -- name: Create new directory - win_file: - path: C:\Program Files\sysinternals - state: directory - -- name: Download sysinternals archive - win_get_url: - url: https://download.sysinternals.com/files/SysinternalsSuite.zip - dest: C:\Windows\Temp\SysinternalsSuite.zip - validate_certs: no diff --git a/test/integration/targets/win_unzip/tasks/main.yml b/test/integration/targets/win_unzip/tasks/main.yml index ed4dc228776e29..2dab84be563b01 100644 --- a/test/integration/targets/win_unzip/tasks/main.yml +++ b/test/integration/targets/win_unzip/tasks/main.yml @@ -1,16 +1,116 @@ -- name: Clean slate - import_tasks: clean.yml +--- +- name: create test directory + win_file: + path: '{{ win_unzip_dir }}\output' + state: directory -- name: Test in normal mode - import_tasks: tests.yml - vars: - in_check_mode: no +- name: create local zip file with non-ascii chars + script: create_zip.py {{ output_dir + '/win_unzip.zip' | quote }} + delegate_to: localhost -- name: Clean slate - import_tasks: clean.yml +- name: copy across zip to Windows host + win_copy: + src: '{{ output_dir }}/win_unzip.zip' + dest: '{{ win_unzip_dir }}\win_unzip.zip' -- name: Test in check-mode - import_tasks: tests.yml - vars: - in_check_mode: yes +- name: unarchive zip (check) + win_unzip: + src: '{{ win_unzip_dir }}\win_unzip.zip' + dest: '{{ win_unzip_dir }}\output' + register: unzip_check check_mode: yes + +- name: get result of unarchive zip (check) + win_stat: + path: '{{ win_unzip_dir }}\output\café.txt' + register: unzip_actual_check + +- name: assert result of unarchive zip (check) + assert: + that: + - unzip_check is changed + - not unzip_check.removed + - not unzip_actual_check.stat.exists + +- name: unarchive zip + win_unzip: + src: '{{ win_unzip_dir }}\win_unzip.zip' + dest: '{{ win_unzip_dir }}\output' + register: unzip + +- name: get result of unarchive zip + slurp: + path: '{{ win_unzip_dir }}\output\café.txt' + register: unzip_actual + +- name: assert result of unarchive zip + assert: + that: + - unzip is changed + - not unzip.removed + - unzip_actual.content | b64decode == 'café.txt' + +# Module is not idempotent, will always change without creates +- name: unarchive zip again without creates + win_unzip: + src: '{{ win_unzip_dir }}\win_unzip.zip' + dest: '{{ win_unzip_dir }}\output' + register: unzip_again + +- name: assert unarchive zip again without creates + assert: + that: + - unzip_again is changed + - not unzip_again.removed + +- name: unarchive zip with creates + win_unzip: + src: '{{ win_unzip_dir }}\win_unzip.zip' + dest: '{{ win_unzip_dir }}\outout' + creates: '{{ win_unzip_dir }}\output\café.txt' + register: unzip_again_creates + +- name: assert unarchive zip with creates + assert: + that: + - not unzip_again_creates is changed + - not unzip_again_creates.removed + +- name: unarchive zip with delete (check) + win_unzip: + src: '{{ win_unzip_dir }}\win_unzip.zip' + dest: '{{ win_unzip_dir }}\output' + delete_archive: yes + register: unzip_delete_check + check_mode: yes + +- name: get result of unarchive zip with delete (check) + win_stat: + path: '{{ win_unzip_dir }}\win_unzip.zip' + register: unzip_delete_actual_check + +- name: assert unarchive zip with delete (check) + assert: + that: + - unzip_delete_check is changed + - unzip_delete_check.removed + - unzip_delete_actual_check.stat.exists + +- name: unarchive zip with delete + win_unzip: + src: '{{ win_unzip_dir }}\win_unzip.zip' + dest: '{{ win_unzip_dir }}\output' + delete_archive: yes + register: unzip_delete + +- name: get result of unarchive zip with delete + win_stat: + path: '{{ win_unzip_dir }}\win_unzip.zip' + register: unzip_delete_actual + +- name: assert unarchive zip with delete + assert: + that: + - unzip_delete is changed + - unzip_delete.removed + - not unzip_delete_actual.stat.exists diff --git a/test/integration/targets/win_unzip/tasks/tests.yml b/test/integration/targets/win_unzip/tasks/tests.yml deleted file mode 100644 index 46ec1e97ec3c1c..00000000000000 --- a/test/integration/targets/win_unzip/tasks/tests.yml +++ /dev/null @@ -1,99 +0,0 @@ -- name: Unarchive sysinternals archive - win_unzip: - src: C:\Windows\Temp\SysinternalsSuite.zip - dest: C:\Program Files\sysinternals - register: unzip_archive - -- name: get stat of an extracted file - win_stat: - path: C:\Program Files\sysinternals\procexp.exe - register: unzip_archive_file - -- name: Test unzip_archive (check-mode) - assert: - that: - - unzip_archive is changed == true - - unzip_archive.removed == false - - unzip_archive_file.stat.exists == false - when: in_check_mode - -- name: Test unzip_archive (normal mode) - assert: - that: - - unzip_archive is changed == true - - unzip_archive.removed == false - - unzip_archive_file.stat.exists == true - when: not in_check_mode - -- name: Unarchive sysinternals archive again, use creates - win_unzip: - src: C:\Windows\Temp\SysinternalsSuite.zip - dest: C:\Program Files\sysinternals - creates: C:\Program Files\sysinternals\procexp.exe - register: unzip_archive_again_creates - -# NOTE: This module is not idempotent, it always extracts, except if we use creates ! -- name: Test unzip_archive_again_creates (normal mode) - assert: - that: - - unzip_archive_again_creates is changed == false - - unzip_archive_again_creates.removed == false - when: not in_check_mode - -- name: Test unzip_archive_again_creates (check-mode) - assert: - that: - - unzip_archive_again_creates is changed == true - - unzip_archive_again_creates.removed == false - when: in_check_mode - - -- name: Unarchive sysinternals archive again - win_unzip: - src: C:\Windows\Temp\SysinternalsSuite.zip - dest: C:\Program Files\sysinternals - delete_archive: yes - register: unzip_archive_again - -# NOTE/ This module is not idempotent, it always extracts -- name: Test unzip_archive_again - assert: - that: - - unzip_archive_again is changed == true - - unzip_archive_again.removed == true - - -- name: Test whether archive is removed - win_stat: - path: C:\Windows\Temp\SysinternalsSuite.zip - register: stat_archive - -- name: Test stat_archive (normal mode) - assert: - that: - - stat_archive.stat.exists == false - when: not in_check_mode - -- name: Test stat_archive (check-mode) - assert: - that: - - stat_archive.stat.exists == true - when: in_check_mode - - -- name: Test extracted files - win_stat: - path: C:\Program Files\sysinternals\procexp.exe - register: stat_procexp - -- name: Test stat_procexp (normal mode) - assert: - that: - - stat_procexp.stat.exists == true - when: not in_check_mode - -- name: Test stat_procexp (check-mode) - assert: - that: - - stat_procexp.stat.exists == false - when: in_check_mode diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index be886aaf277ca0..30a6894317f448 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -7643,7 +7643,6 @@ lib/ansible/modules/windows/win_share.ps1 pslint:PSCustomUseLiteralPath lib/ansible/modules/windows/win_shell.ps1 pslint:PSUseApprovedVerbs lib/ansible/modules/windows/win_shortcut.ps1 pslint:PSCustomUseLiteralPath lib/ansible/modules/windows/win_snmp.ps1 pslint:PSCustomUseLiteralPath -lib/ansible/modules/windows/win_unzip.ps1 pslint:PSCustomUseLiteralPath lib/ansible/modules/windows/win_unzip.ps1 pslint:PSUseApprovedVerbs lib/ansible/modules/windows/win_updates.ps1 pslint:PSCustomUseLiteralPath lib/ansible/modules/windows/win_uri.ps1 pslint:PSAvoidUsingEmptyCatchBlock # Keep