Updates to archive
module based on code review (#2699)
* Use common file arguments on destination file * Rename 'compression' to 'format' h/t @abadger * Add support for plain 'tar' format * Ensure check_mode is respected
This commit is contained in:
parent
c07516bd4b
commit
c0d77be491
1 changed files with 90 additions and 74 deletions
164
files/archive.py
164
files/archive.py
|
@ -1,6 +1,10 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# import module snippets
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.pycompat24 import get_exception
|
||||||
|
|
||||||
"""
|
"""
|
||||||
(c) 2016, Ben Doherty <bendohmv@gmail.com>
|
(c) 2016, Ben Doherty <bendohmv@gmail.com>
|
||||||
Sponsored by Oomph, Inc. http://www.oomphinc.com
|
Sponsored by Oomph, Inc. http://www.oomphinc.com
|
||||||
|
@ -34,7 +38,7 @@ options:
|
||||||
description:
|
description:
|
||||||
- Remote absolute path, glob, or list of paths or globs for the file or files to compress or archive.
|
- Remote absolute path, glob, or list of paths or globs for the file or files to compress or archive.
|
||||||
required: true
|
required: true
|
||||||
compression:
|
format:
|
||||||
description:
|
description:
|
||||||
- The type of compression to use. Can be 'gz', 'bz2', or 'zip'.
|
- The type of compression to use. Can be 'gz', 'bz2', or 'zip'.
|
||||||
choices: [ 'gz', 'bz2', 'zip' ]
|
choices: [ 'gz', 'bz2', 'zip' ]
|
||||||
|
@ -65,7 +69,7 @@ EXAMPLES = '''
|
||||||
- archive: path=/path/to/foo remove=True
|
- archive: path=/path/to/foo remove=True
|
||||||
|
|
||||||
# Create a zip archive of /path/to/foo
|
# Create a zip archive of /path/to/foo
|
||||||
- archive: path=/path/to/foo compression=zip
|
- archive: path=/path/to/foo format=zip
|
||||||
|
|
||||||
# Create a bz2 archive of multiple files, rooted at /path
|
# Create a bz2 archive of multiple files, rooted at /path
|
||||||
- archive:
|
- archive:
|
||||||
|
@ -73,7 +77,7 @@ EXAMPLES = '''
|
||||||
- /path/to/foo
|
- /path/to/foo
|
||||||
- /path/wong/foo
|
- /path/wong/foo
|
||||||
dest: /path/file.tar.bz2
|
dest: /path/file.tar.bz2
|
||||||
compression: bz2
|
format: bz2
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = '''
|
RETURN = '''
|
||||||
|
@ -102,9 +106,8 @@ expanded_paths:
|
||||||
type: list
|
type: list
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import stat
|
|
||||||
import os
|
import os
|
||||||
import errno
|
import re
|
||||||
import glob
|
import glob
|
||||||
import shutil
|
import shutil
|
||||||
import gzip
|
import gzip
|
||||||
|
@ -117,8 +120,8 @@ def main():
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec = dict(
|
argument_spec = dict(
|
||||||
path = dict(type='list', required=True),
|
path = dict(type='list', required=True),
|
||||||
compression = dict(choices=['gz', 'bz2', 'zip'], default='gz', required=False),
|
format = dict(choices=['gz', 'bz2', 'zip', 'tar'], default='gz', required=False),
|
||||||
dest = dict(required=False),
|
dest = dict(required=False, type='path'),
|
||||||
remove = dict(required=False, default=False, type='bool'),
|
remove = dict(required=False, default=False, type='bool'),
|
||||||
),
|
),
|
||||||
add_file_common_args=True,
|
add_file_common_args=True,
|
||||||
|
@ -126,11 +129,13 @@ def main():
|
||||||
)
|
)
|
||||||
|
|
||||||
params = module.params
|
params = module.params
|
||||||
|
check_mode = module.check_mode
|
||||||
paths = params['path']
|
paths = params['path']
|
||||||
dest = params['dest']
|
dest = params['dest']
|
||||||
remove = params['remove']
|
remove = params['remove']
|
||||||
|
|
||||||
expanded_paths = []
|
expanded_paths = []
|
||||||
compression = params['compression']
|
format = params['format']
|
||||||
globby = False
|
globby = False
|
||||||
changed = False
|
changed = False
|
||||||
state = 'absent'
|
state = 'absent'
|
||||||
|
@ -140,11 +145,16 @@ def main():
|
||||||
successes = []
|
successes = []
|
||||||
|
|
||||||
for i, path in enumerate(paths):
|
for i, path in enumerate(paths):
|
||||||
path = os.path.expanduser(path)
|
path = os.path.expanduser(os.path.expandvars(path))
|
||||||
|
|
||||||
# Detect glob-like characters
|
# Expand any glob characters. If found, add the expanded glob to the
|
||||||
if any((c in set('*?')) for c in path):
|
# list of expanded_paths, which might be empty.
|
||||||
|
if ('*' in path or '?' in path):
|
||||||
expanded_paths = expanded_paths + glob.glob(path)
|
expanded_paths = expanded_paths + glob.glob(path)
|
||||||
|
globby = True
|
||||||
|
|
||||||
|
# If there are no glob characters the path is added to the expanded paths
|
||||||
|
# whether the path exists or not
|
||||||
else:
|
else:
|
||||||
expanded_paths.append(path)
|
expanded_paths.append(path)
|
||||||
|
|
||||||
|
@ -156,11 +166,9 @@ def main():
|
||||||
archive = globby or os.path.isdir(expanded_paths[0]) or len(expanded_paths) > 1
|
archive = globby or os.path.isdir(expanded_paths[0]) or len(expanded_paths) > 1
|
||||||
|
|
||||||
# Default created file name (for single-file archives) to
|
# Default created file name (for single-file archives) to
|
||||||
# <file>.<compression>
|
# <file>.<format>
|
||||||
if dest:
|
if not dest and not archive:
|
||||||
dest = os.path.expanduser(dest)
|
dest = '%s.%s' % (expanded_paths[0], format)
|
||||||
elif not archive:
|
|
||||||
dest = '%s.%s' % (expanded_paths[0], compression)
|
|
||||||
|
|
||||||
# Force archives to specify 'dest'
|
# Force archives to specify 'dest'
|
||||||
if archive and not dest:
|
if archive and not dest:
|
||||||
|
@ -168,7 +176,6 @@ def main():
|
||||||
|
|
||||||
archive_paths = []
|
archive_paths = []
|
||||||
missing = []
|
missing = []
|
||||||
exclude = []
|
|
||||||
arcroot = ''
|
arcroot = ''
|
||||||
|
|
||||||
for path in expanded_paths:
|
for path in expanded_paths:
|
||||||
|
@ -177,7 +184,7 @@ def main():
|
||||||
if arcroot == '':
|
if arcroot == '':
|
||||||
arcroot = os.path.dirname(path) + os.sep
|
arcroot = os.path.dirname(path) + os.sep
|
||||||
else:
|
else:
|
||||||
for i in xrange(len(arcroot)):
|
for i in range(len(arcroot)):
|
||||||
if path[i] != arcroot[i]:
|
if path[i] != arcroot[i]:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -198,7 +205,7 @@ def main():
|
||||||
# No source files were found but the named archive exists: are we 'compress' or 'archive' now?
|
# No source files were found but the named archive exists: are we 'compress' or 'archive' now?
|
||||||
if len(missing) == len(expanded_paths) and dest and os.path.exists(dest):
|
if len(missing) == len(expanded_paths) and dest and os.path.exists(dest):
|
||||||
# Just check the filename to know if it's an archive or simple compressed file
|
# Just check the filename to know if it's an archive or simple compressed file
|
||||||
if re.search(r'(\.tar\.gz|\.tgz|.tbz2|\.tar\.bz2|\.zip)$', os.path.basename(dest), re.IGNORECASE):
|
if re.search(r'(\.tar|\.tar\.gz|\.tgz|.tbz2|\.tar\.bz2|\.zip)$', os.path.basename(dest), re.IGNORECASE):
|
||||||
state = 'archive'
|
state = 'archive'
|
||||||
else:
|
else:
|
||||||
state = 'compress'
|
state = 'compress'
|
||||||
|
@ -221,77 +228,84 @@ def main():
|
||||||
size = os.path.getsize(dest)
|
size = os.path.getsize(dest)
|
||||||
|
|
||||||
if state != 'archive':
|
if state != 'archive':
|
||||||
try:
|
if check_mode:
|
||||||
|
changed = True
|
||||||
|
|
||||||
# Slightly more difficult (and less efficient!) compression using zipfile module
|
else:
|
||||||
if compression == 'zip':
|
try:
|
||||||
arcfile = zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED)
|
# Slightly more difficult (and less efficient!) compression using zipfile module
|
||||||
|
if format == 'zip':
|
||||||
|
arcfile = zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED)
|
||||||
|
|
||||||
# Easier compression using tarfile module
|
# Easier compression using tarfile module
|
||||||
elif compression == 'gz' or compression == 'bz2':
|
elif format == 'gz' or format == 'bz2':
|
||||||
arcfile = tarfile.open(dest, 'w|' + compression)
|
arcfile = tarfile.open(dest, 'w|' + format)
|
||||||
|
|
||||||
for path in archive_paths:
|
# Or plain tar archiving
|
||||||
if os.path.isdir(path):
|
elif format == 'tar':
|
||||||
# Recurse into directories
|
arcfile = tarfile.open(dest, 'w')
|
||||||
for dirpath, dirnames, filenames in os.walk(path, topdown=True):
|
|
||||||
if not dirpath.endswith(os.sep):
|
|
||||||
dirpath += os.sep
|
|
||||||
|
|
||||||
for dirname in dirnames:
|
for path in archive_paths:
|
||||||
fullpath = dirpath + dirname
|
if os.path.isdir(path):
|
||||||
arcname = fullpath[len(arcroot):]
|
# Recurse into directories
|
||||||
|
for dirpath, dirnames, filenames in os.walk(path, topdown=True):
|
||||||
|
if not dirpath.endswith(os.sep):
|
||||||
|
dirpath += os.sep
|
||||||
|
|
||||||
try:
|
for dirname in dirnames:
|
||||||
if compression == 'zip':
|
fullpath = dirpath + dirname
|
||||||
arcfile.write(fullpath, arcname)
|
arcname = fullpath[len(arcroot):]
|
||||||
else:
|
|
||||||
arcfile.add(fullpath, arcname, recursive=False)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
e = get_exception()
|
|
||||||
errors.append('%s: %s' % (fullpath, str(e)))
|
|
||||||
|
|
||||||
for filename in filenames:
|
|
||||||
fullpath = dirpath + filename
|
|
||||||
arcname = fullpath[len(arcroot):]
|
|
||||||
|
|
||||||
if not filecmp.cmp(fullpath, dest):
|
|
||||||
try:
|
try:
|
||||||
if compression == 'zip':
|
if format == 'zip':
|
||||||
arcfile.write(fullpath, arcname)
|
arcfile.write(fullpath, arcname)
|
||||||
else:
|
else:
|
||||||
arcfile.add(fullpath, arcname, recursive=False)
|
arcfile.add(fullpath, arcname, recursive=False)
|
||||||
|
|
||||||
successes.append(fullpath)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
e = get_exception()
|
e = get_exception()
|
||||||
errors.append('Adding %s: %s' % (path, str(e)))
|
errors.append('%s: %s' % (fullpath, str(e)))
|
||||||
else:
|
|
||||||
if compression == 'zip':
|
for filename in filenames:
|
||||||
arcfile.write(path, path[len(arcroot):])
|
fullpath = dirpath + filename
|
||||||
|
arcname = fullpath[len(arcroot):]
|
||||||
|
|
||||||
|
if not filecmp.cmp(fullpath, dest):
|
||||||
|
try:
|
||||||
|
if format == 'zip':
|
||||||
|
arcfile.write(fullpath, arcname)
|
||||||
|
else:
|
||||||
|
arcfile.add(fullpath, arcname, recursive=False)
|
||||||
|
|
||||||
|
successes.append(fullpath)
|
||||||
|
except Exception:
|
||||||
|
e = get_exception()
|
||||||
|
errors.append('Adding %s: %s' % (path, str(e)))
|
||||||
else:
|
else:
|
||||||
arcfile.add(path, path[len(arcroot):], recursive=False)
|
if format == 'zip':
|
||||||
|
arcfile.write(path, path[len(arcroot):])
|
||||||
|
else:
|
||||||
|
arcfile.add(path, path[len(arcroot):], recursive=False)
|
||||||
|
|
||||||
successes.append(path)
|
successes.append(path)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
e = get_exception()
|
e = get_exception()
|
||||||
return module.fail_json(msg='Error when writing %s archive at %s: %s' % (compression == 'zip' and 'zip' or ('tar.' + compression), dest, str(e)))
|
return module.fail_json(msg='Error when writing %s archive at %s: %s' % (format == 'zip' and 'zip' or ('tar.' + format), dest, str(e)))
|
||||||
|
|
||||||
if arcfile:
|
if arcfile:
|
||||||
arcfile.close()
|
arcfile.close()
|
||||||
state = 'archive'
|
state = 'archive'
|
||||||
|
|
||||||
if len(errors) > 0:
|
if len(errors) > 0:
|
||||||
module.fail_json(msg='Errors when writing archive at %s: %s' % (dest, '; '.join(errors)))
|
module.fail_json(msg='Errors when writing archive at %s: %s' % (dest, '; '.join(errors)))
|
||||||
|
|
||||||
if state in ['archive', 'incomplete'] and remove:
|
if state in ['archive', 'incomplete'] and remove:
|
||||||
for path in successes:
|
for path in successes:
|
||||||
try:
|
try:
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
shutil.rmtree(path)
|
shutil.rmtree(path)
|
||||||
else:
|
elif not check_mode:
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
except OSError:
|
except OSError:
|
||||||
e = get_exception()
|
e = get_exception()
|
||||||
|
@ -331,7 +345,7 @@ def main():
|
||||||
size = os.path.getsize(dest)
|
size = os.path.getsize(dest)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if compression == 'zip':
|
if format == 'zip':
|
||||||
arcfile = zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED)
|
arcfile = zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED)
|
||||||
arcfile.write(path, path[len(arcroot):])
|
arcfile.write(path, path[len(arcroot):])
|
||||||
arcfile.close()
|
arcfile.close()
|
||||||
|
@ -340,12 +354,12 @@ def main():
|
||||||
else:
|
else:
|
||||||
f_in = open(path, 'rb')
|
f_in = open(path, 'rb')
|
||||||
|
|
||||||
if compression == 'gz':
|
if format == 'gz':
|
||||||
f_out = gzip.open(dest, 'wb')
|
f_out = gzip.open(dest, 'wb')
|
||||||
elif compression == 'bz2':
|
elif format == 'bz2':
|
||||||
f_out = bz2.BZ2File(dest, 'wb')
|
f_out = bz2.BZ2File(dest, 'wb')
|
||||||
else:
|
else:
|
||||||
raise OSError("Invalid compression")
|
raise OSError("Invalid format")
|
||||||
|
|
||||||
shutil.copyfileobj(f_in, f_out)
|
shutil.copyfileobj(f_in, f_out)
|
||||||
|
|
||||||
|
@ -353,7 +367,6 @@ def main():
|
||||||
|
|
||||||
except OSError:
|
except OSError:
|
||||||
e = get_exception()
|
e = get_exception()
|
||||||
|
|
||||||
module.fail_json(path=path, dest=dest, msg='Unable to write to compressed file: %s' % str(e))
|
module.fail_json(path=path, dest=dest, msg='Unable to write to compressed file: %s' % str(e))
|
||||||
|
|
||||||
if arcfile:
|
if arcfile:
|
||||||
|
@ -369,7 +382,7 @@ def main():
|
||||||
|
|
||||||
state = 'compress'
|
state = 'compress'
|
||||||
|
|
||||||
if remove:
|
if remove and not check_mode:
|
||||||
try:
|
try:
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
|
@ -377,9 +390,12 @@ def main():
|
||||||
e = get_exception()
|
e = get_exception()
|
||||||
module.fail_json(path=path, msg='Unable to remove source file: %s' % str(e))
|
module.fail_json(path=path, msg='Unable to remove source file: %s' % str(e))
|
||||||
|
|
||||||
|
params['path'] = dest
|
||||||
|
file_args = module.load_file_common_arguments(params)
|
||||||
|
|
||||||
|
changed = module.set_fs_attributes_if_different(file_args, changed)
|
||||||
|
|
||||||
module.exit_json(archived=successes, dest=dest, changed=changed, state=state, arcroot=arcroot, missing=missing, expanded_paths=expanded_paths)
|
module.exit_json(archived=successes, dest=dest, changed=changed, state=state, arcroot=arcroot, missing=missing, expanded_paths=expanded_paths)
|
||||||
|
|
||||||
# import module snippets
|
|
||||||
from ansible.module_utils.basic import *
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Reference in a new issue