diff --git a/lib/ansible/modules/source_control/git.py b/lib/ansible/modules/source_control/git.py index c4099d856db..902339172f8 100644 --- a/lib/ansible/modules/source_control/git.py +++ b/lib/ansible/modules/source_control/git.py @@ -186,6 +186,15 @@ options: to be installed. The commit MUST be signed and the public key MUST be trusted in the GPG trustdb. + archive: + required: false + version_added: "2.4" + description: + - Specify archive file path with extension. If specified, creates an + archive file of the specified format containing the tree structure + for the source tree. + Allowed archive formats ["zip", "tar.gz", "tar", "tgz"] + requirements: - git>=1.7.1 (the command line tool) @@ -228,6 +237,13 @@ EXAMPLES = ''' repo: https://github.com/ansible/ansible-examples.git dest: /src/ansible-examples refspec: '+refs/pull/*:refs/heads/*' + +# Example Create git archive from repo +- git: + repo: https://github.com/ansible/ansible-examples.git + dest: /src/ansible-examples + archive: /tmp/ansible-examples.zip + ''' RETURN = ''' @@ -253,15 +269,18 @@ warnings: sample: Your git version is too old to fully support the depth argument. Falling back to full checkouts. ''' +import filecmp import os import re import shlex import stat import sys +import shutil import tempfile from distutils.version import LooseVersion from ansible.module_utils.basic import AnsibleModule, get_module_path +from ansible.module_utils.basic import get_exception from ansible.module_utils.known_hosts import add_git_host_key from ansible.module_utils.six import b, string_types from ansible.module_utils._text import to_native @@ -901,6 +920,68 @@ def git_version(git_path, module): return LooseVersion(rematch.groups()[0]) +def git_archive(git_path, module, dest, archive, archive_fmt, version): + """ Create git archive in given source directory """ + cmd = "%s archive --format=%s --output=%s %s" \ + % (git_path, archive_fmt, archive, version) + (rc, out, err) = module.run_command(cmd, cwd=dest) + if rc != 0: + module.fail_json(msg="Failed to perform archive operation", + details="Git archive command failed to create " + "archive %s using %s directory." + "Error: %s" % (archive, dest, err)) + return rc, out, err + + +def create_archive(git_path, module, dest, archive, version, repo, result): + """ Helper function for creating archive using git_archive """ + all_archive_fmt = {'.zip': 'zip', '.gz': 'tar.gz', '.tar': 'tar', + '.tgz': 'tgz'} + _, archive_ext = os.path.splitext(archive) + archive_fmt = all_archive_fmt.get(archive_ext, None) + if archive_fmt is None: + module.fail_json(msg="Unable to get file extension from " + "archive file name : %s" % archive, + details="Please specify archive as filename with " + "extension. File extension can be one " + "of ['tar', 'tar.gz', 'zip', 'tgz']") + + repo_name = repo.split("/")[-1].replace(".git", "") + + if os.path.exists(archive): + # If git archive file exists, then compare it with new git archive file. + # if match, do nothing + # if does not match, then replace existing with temp archive file. + tempdir = tempfile.mkdtemp() + new_archive_dest = os.path.join(tempdir, repo_name) + new_archive = new_archive_dest + '.' + archive_fmt + git_archive(git_path, module, dest, new_archive, archive_fmt, version) + + # filecmp is supposed to be efficient than md5sum checksum + if filecmp.cmp(new_archive, archive): + result.update(changed=False) + # Cleanup before exiting + try: + shutil.remove(tempdir) + except OSError: + pass + else: + try: + shutil.move(new_archive, archive) + shutil.remove(tempdir) + result.update(changed=True) + except OSError: + exception = get_exception() + module.fail_json(msg="Failed to move %s to %s" % + (new_archive, archive), + details="Error occured while moving : %s" + % exception) + else: + # Perform archive from local directory + git_archive(git_path, module, dest, archive, archive_fmt, version) + result.update(changed=True) + + # =========================================== def main(): @@ -925,6 +1006,7 @@ def main(): recursive=dict(default='yes', type='bool'), track_submodules=dict(default='no', type='bool'), umask=dict(default=None, type='raw'), + archive=dict(type='path'), ), supports_check_mode=True ) @@ -945,6 +1027,7 @@ def main(): key_file = module.params['key_file'] ssh_opts = module.params['ssh_opts'] umask = module.params['umask'] + archive = module.params['archive'] result = dict(changed=False, warnings=list()) @@ -1003,6 +1086,7 @@ def main(): track_submodules = module.params['track_submodules'] result.update(before=None) + local_mods = False need_fetch = True if (dest and not os.path.exists(gitconfig)) or (not dest and not allow_clone): @@ -1092,6 +1176,15 @@ def main(): if diff: result['diff'] = diff + if archive: + # Git archive is not supported by all git servers, so + # we will first clone and perform git archive from local directory + if module.check_mode: + result.update(changed=True) + module.exit_json(**result) + + create_archive(git_path, module, dest, archive, version, repo, result) + # cleanup the wrapper script if ssh_wrapper: try: