1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

archive - Adding exclusion_patterns option (#2616)

* Adding exclusion_patterns option

* Adding changelog fragment and Python 2.6 compatability

* Minor refactoring for readability

* Removing unneccessary conditional

* Applying initial review suggestions

* Adding missed review suggestion
This commit is contained in:
Ajpantuso 2021-05-31 01:51:29 -04:00 committed by GitHub
parent bef3c04d1c
commit b6c0cc0b61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 20 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- archive - added ``exclusion_patterns`` option to exclude files or subdirectories from archives (https://github.com/ansible-collections/community.general/pull/2616).

View file

@ -41,8 +41,16 @@ options:
exclude_path: exclude_path:
description: description:
- Remote absolute path, glob, or list of paths or globs for the file or files to exclude from I(path) list and glob expansion. - Remote absolute path, glob, or list of paths or globs for the file or files to exclude from I(path) list and glob expansion.
- Use I(exclusion_patterns) to instead exclude files or subdirectories below any of the paths from the I(path) list.
type: list type: list
elements: path elements: path
exclusion_patterns:
description:
- Glob style patterns to exclude files or directories from the resulting archive.
- This differs from I(exclude_path) which applies only to the source paths from I(path).
type: list
elements: path
version_added: 3.2.0
force_archive: force_archive:
description: description:
- Allows you to force the module to treat this as an archive even if only a single file is specified. - Allows you to force the module to treat this as an archive even if only a single file is specified.
@ -163,6 +171,8 @@ import re
import shutil import shutil
import tarfile import tarfile
import zipfile import zipfile
from fnmatch import fnmatch
from sys import version_info
from traceback import format_exc from traceback import format_exc
from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.basic import AnsibleModule, missing_required_lib
@ -186,6 +196,8 @@ else:
LZMA_IMP_ERR = format_exc() LZMA_IMP_ERR = format_exc()
HAS_LZMA = False HAS_LZMA = False
PY27 = version_info[0:2] >= (2, 7)
def to_b(s): def to_b(s):
return to_bytes(s, errors='surrogate_or_strict') return to_bytes(s, errors='surrogate_or_strict')
@ -214,6 +226,59 @@ def expand_paths(paths):
return expanded_path, is_globby return expanded_path, is_globby
def matches_exclusion_patterns(path, exclusion_patterns):
return any(fnmatch(path, p) for p in exclusion_patterns)
def get_filter(exclusion_patterns, format):
def zip_filter(path):
return matches_exclusion_patterns(path, exclusion_patterns)
def tar_filter(tarinfo):
return None if matches_exclusion_patterns(tarinfo.name, exclusion_patterns) else tarinfo
return zip_filter if format == 'zip' or not PY27 else tar_filter
def get_archive_contains(format):
def archive_contains(archive, name):
try:
if format == 'zip':
archive.getinfo(name)
else:
archive.getmember(name)
except KeyError:
return False
return True
return archive_contains
def get_add_to_archive(format, filter):
def add_to_zip_archive(archive_file, path, archive_name):
try:
if not filter(path):
archive_file.write(path, archive_name)
except Exception as e:
return e
return None
def add_to_tar_archive(archive_file, path, archive_name):
try:
if PY27:
archive_file.add(path, archive_name, recursive=False, filter=filter)
else:
archive_file.add(path, archive_name, recursive=False, exclude=filter)
except Exception as e:
return e
return None
return add_to_zip_archive if format == 'zip' else add_to_tar_archive
def main(): def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec=dict( argument_spec=dict(
@ -221,6 +286,7 @@ def main():
format=dict(type='str', default='gz', choices=['bz2', 'gz', 'tar', 'xz', 'zip']), format=dict(type='str', default='gz', choices=['bz2', 'gz', 'tar', 'xz', 'zip']),
dest=dict(type='path'), dest=dict(type='path'),
exclude_path=dict(type='list', elements='path'), exclude_path=dict(type='list', elements='path'),
exclusion_patterns=dict(type='list', elements='path'),
force_archive=dict(type='bool', default=False), force_archive=dict(type='bool', default=False),
remove=dict(type='bool', default=False), remove=dict(type='bool', default=False),
), ),
@ -242,6 +308,8 @@ def main():
changed = False changed = False
state = 'absent' state = 'absent'
exclusion_patterns = params['exclusion_patterns'] or []
# Simple or archive file compression (inapplicable with 'zip' since it's always an archive) # Simple or archive file compression (inapplicable with 'zip' since it's always an archive)
b_successes = [] b_successes = []
@ -262,6 +330,10 @@ def main():
# Only attempt to expand the exclude paths if it exists # Only attempt to expand the exclude paths if it exists
b_expanded_exclude_paths = expand_paths(exclude_paths)[0] if exclude_paths else [] b_expanded_exclude_paths = expand_paths(exclude_paths)[0] if exclude_paths else []
filter = get_filter(exclusion_patterns, fmt)
archive_contains = get_archive_contains(fmt)
add_to_archive = get_add_to_archive(fmt, filter)
# Only try to determine if we are working with an archive or not if we haven't set archive to true # Only try to determine if we are working with an archive or not if we haven't set archive to true
if not force_archive: if not force_archive:
# If we actually matched multiple files or TRIED to, then # If we actually matched multiple files or TRIED to, then
@ -384,38 +456,31 @@ def main():
n_fullpath = to_na(b_fullpath) n_fullpath = to_na(b_fullpath)
n_arcname = to_native(b_match_root.sub(b'', b_fullpath), errors='surrogate_or_strict') n_arcname = to_native(b_match_root.sub(b'', b_fullpath), errors='surrogate_or_strict')
try: err = add_to_archive(arcfile, n_fullpath, n_arcname)
if fmt == 'zip': if err:
arcfile.write(n_fullpath, n_arcname) errors.append('%s: %s' % (n_fullpath, to_native(err)))
else:
arcfile.add(n_fullpath, n_arcname, recursive=False)
except Exception as e:
errors.append('%s: %s' % (n_fullpath, to_native(e)))
for b_filename in b_filenames: for b_filename in b_filenames:
b_fullpath = b_dirpath + b_filename b_fullpath = b_dirpath + b_filename
n_fullpath = to_na(b_fullpath) n_fullpath = to_na(b_fullpath)
n_arcname = to_n(b_match_root.sub(b'', b_fullpath)) n_arcname = to_n(b_match_root.sub(b'', b_fullpath))
try: err = add_to_archive(arcfile, n_fullpath, n_arcname)
if fmt == 'zip': if err:
arcfile.write(n_fullpath, n_arcname) errors.append('Adding %s: %s' % (to_native(b_path), to_native(err)))
else:
arcfile.add(n_fullpath, n_arcname, recursive=False)
if archive_contains(arcfile, n_arcname):
b_successes.append(b_fullpath) b_successes.append(b_fullpath)
except Exception as e:
errors.append('Adding %s: %s' % (to_native(b_path), to_native(e)))
else: else:
path = to_na(b_path) path = to_na(b_path)
arcname = to_n(b_match_root.sub(b'', b_path)) arcname = to_n(b_match_root.sub(b'', b_path))
if fmt == 'zip':
arcfile.write(path, arcname)
else:
arcfile.add(path, arcname, recursive=False)
b_successes.append(b_path) err = add_to_archive(arcfile, path, arcname)
if err:
errors.append('Adding %s: %s' % (to_native(b_path), to_native(err)))
if archive_contains(arcfile, arcname):
b_successes.append(b_path)
except Exception as e: except Exception as e:
expanded_fmt = 'zip' if fmt == 'zip' else ('tar.' + fmt) expanded_fmt = 'zip' if fmt == 'zip' else ('tar.' + fmt)

View file

@ -363,6 +363,19 @@
- name: remove nonascii test - name: remove nonascii test
file: path="{{ output_dir }}/test-archive-nonascii-くらとみ.zip" state=absent file: path="{{ output_dir }}/test-archive-nonascii-くらとみ.zip" state=absent
- name: Test exclusion_patterns option
archive:
path: "{{ output_dir }}/*.txt"
dest: "{{ output_dir }}/test-archive-exclustion-patterns.tgz"
exclusion_patterns: b?r.*
register: exclusion_patterns_result
- name: Assert that exclusion_patterns only archives included files
assert:
that:
- exclusion_patterns_result is changed
- "'bar.txt' not in exclusion_patterns_result.archived"
- name: Remove backports.lzma if previously installed (pip) - name: Remove backports.lzma if previously installed (pip)
pip: name=backports.lzma state=absent pip: name=backports.lzma state=absent
when: backports_lzma_pip is changed when: backports_lzma_pip is changed