Skip to content

Commit

Permalink
unarchive - fall back to unzip -Z if zipinfo is not available (ansibl…
Browse files Browse the repository at this point in the history
…e#76971)

Add a new handler class ZipZArchive to use unzip -Z as an alternative to zipinfo

Run 'unzip -Z' in can_handle_archive so we fall back to the next handler if it's not available (failing in is_unarchived is too late)

* Add a test for unzip -Z when zipinfo is not available

* Update test for missing binary altogether by removing /usr/bin from the PATH
  • Loading branch information
s-hertel authored Jun 7, 2022
1 parent a431122 commit 9d6cc7b
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bugfixes:
- unarchive - if unzip is available but zipinfo is not, use unzip -Z instead of zipinfo (https://github.com/ansible/ansible/issues/76959).
41 changes: 34 additions & 7 deletions lib/ansible/modules/unarchive.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,11 @@ def __init__(self, src, b_dest, file_args, module):
self.zipinfo_cmd_path = None
self._files_in_archive = []
self._infodict = dict()
self.zipinfoflag = ''
self.binaries = (
('unzip', 'cmd_path'),
('zipinfo', 'zipinfo_cmd_path'),
)

def _permstr_to_octal(self, modestr, umask):
''' Convert a Unix permission string (rw-r--r--) into a mode (0644) '''
Expand Down Expand Up @@ -399,7 +404,10 @@ def files_in_archive(self):

def is_unarchived(self):
# BSD unzip doesn't support zipinfo listings with timestamp.
cmd = [self.zipinfo_cmd_path, '-T', '-s', self.src]
if self.zipinfoflag:
cmd = [self.zipinfo_cmd_path, self.zipinfoflag, '-T', '-s', self.src]
else:
cmd = [self.zipinfo_cmd_path, '-T', '-s', self.src]

if self.excludes:
cmd.extend(['-x', ] + self.excludes)
Expand Down Expand Up @@ -720,12 +728,8 @@ def unarchive(self):
return dict(cmd=cmd, rc=rc, out=out, err=err)

def can_handle_archive(self):
binaries = (
('unzip', 'cmd_path'),
('zipinfo', 'zipinfo_cmd_path'),
)
missing = []
for b in binaries:
for b in self.binaries:
try:
setattr(self, b[1], get_bin_path(b[0]))
except ValueError:
Expand Down Expand Up @@ -948,9 +952,32 @@ def __init__(self, src, b_dest, file_args, module):
self.zipflag = '--use-compress-program=zstd'


class ZipZArchive(ZipArchive):
def __init__(self, src, b_dest, file_args, module):
super(ZipZArchive, self).__init__(src, b_dest, file_args, module)
self.zipinfoflag = '-Z'
self.binaries = (
('unzip', 'cmd_path'),
('unzip', 'zipinfo_cmd_path'),
)

def can_handle_archive(self):
unzip_available, error_msg = super(ZipZArchive, self).can_handle_archive()

if not unzip_available:
return unzip_available, error_msg

# Ensure unzip -Z is available before we use it in is_unarchive
cmd = [self.zipinfo_cmd_path, self.zipinfoflag]
rc, out, err = self.module.run_command(cmd)
if 'zipinfo' in out.lower():
return True, None
return False, 'Command "unzip -Z" could not handle archive: %s' % err


# try handlers in order and return the one that works or bail if none work
def pick_handler(src, dest, file_args, module):
handlers = [ZipArchive, TgzArchive, TarArchive, TarBzipArchive, TarXzArchive, TarZstdArchive]
handlers = [ZipArchive, ZipZArchive, TgzArchive, TarArchive, TarBzipArchive, TarXzArchive, TarZstdArchive]
reasons = set()
for handler in handlers:
obj = handler(src, dest, file_args, module)
Expand Down
31 changes: 31 additions & 0 deletions test/integration/targets/unarchive/tasks/test_missing_binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
remote_src: yes
register: zip_fail
ignore_errors: yes
# FreeBSD does not have zipinfo, but does have a bootstrapped unzip in /usr/bin
# which alone is sufficient to run unarchive.
# Exclude /usr/bin from the PATH to test having no binary available.
environment:
PATH: "{{ ENV_PATH }}"
vars:
ENV_PATH: "{{ lookup('env', 'PATH') | regex_replace(re, '') }}"
re: "[^A-Za-z](\/usr\/bin:?)"

- name: Ensure tasks worked as expected
assert:
Expand All @@ -41,6 +49,29 @@
- zip_fail is failed
- zip_fail.msg is search('Unable to find required')

- name: unarchive a zip file using unzip without zipinfo
unarchive:
src: '{{remote_tmp_dir}}/test-unarchive.zip'
dest: '{{remote_tmp_dir}}/test-unarchive-zip'
list_files: True
remote_src: yes
register: zip_success
# FreeBSD does not have zipinfo, but does have a bootstrapped unzip in /usr/bin
# which alone is sufficient to run unarchive.
when: ansible_pkg_mgr == 'pkgng'

- assert:
that:
- zip_success is success
- zip_success.changed
# Verify that file list is generated
- "'files' in zip_success"
- "{{zip_success['files']| length}} == 3"
- "'foo-unarchive.txt' in zip_success['files']"
- "'foo-unarchive-777.txt' in zip_success['files']"
- "'FOO-UNAR.TXT' in zip_success['files']"
when: ansible_pkg_mgr == 'pkgng'

- name: Remove unarchive destinations
file:
path: '{{ remote_tmp_dir }}/test-unarchive-{{ item }}'
Expand Down

0 comments on commit 9d6cc7b

Please sign in to comment.