Skip to content

Instantly share code, notes, and snippets.

@alex-deref
Last active September 8, 2016 19:02
Show Gist options
  • Save alex-deref/dc66a0ca7e1904c30508 to your computer and use it in GitHub Desktop.
Save alex-deref/dc66a0ca7e1904c30508 to your computer and use it in GitHub Desktop.

Revisions

  1. Alexander Taler revised this gist Aug 25, 2015. 1 changed file with 20 additions and 1 deletion.
    21 changes: 20 additions & 1 deletion hostlocalcopy.py
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,18 @@
    #
    # Copyright (C) 2015 Alexander Taler
    #
    # This is free software: you can redistribute it and/or modify
    # it under the terms of the GNU General Public License as published by
    # the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.
    #
    # This is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License
    # along with this. If not, see <http://www.gnu.org/licenses/>.

    import datetime
    import filecmp
    @@ -48,6 +60,11 @@
    directories which are created. If not specified system defaults
    are used.
    required: no
    fastcompare:
    description:
    - True to do file comparisons using modification time, false to
    compare contents. Default is true.
    required: no
    extends_documentation_fragment:
    - files
    '''
    @@ -110,6 +127,7 @@ def run(self):
    srcp = self.module.params['src']
    src = glob.iglob(os.path.expanduser(srcp))
    dest = os.path.expanduser(self.module.params['dest'])
    self.fastcompare = self.module.boolean(self.module.params["fastcompare"])

    self.load_common_file_args()

    @@ -174,7 +192,7 @@ def recursive_copy(self, src, dest):
    # Copy files
    elif os.path.isfile(src):
    # Remove anything in the way and copy the file if required
    if (not os.path.isfile(dest) or not filecmp.cmp(src, dest)):
    if (not os.path.isfile(dest) or not filecmp.cmp(src, dest, self.fastcompare)):
    if self.check_and_remove(dest):
    shutil.copy2(src, dest)
    # Ensure file attributes are correct
    @@ -252,6 +270,7 @@ def main():
    src = dict(required=True),
    dest = dict(required=True),
    directory_mode = dict(required=False),
    fastcompare = dict(required=False, default=True, choices=BOOLEANS),
    ),
    add_file_common_args=True,
    supports_check_mode=True,
  2. Alexander Taler revised this gist Aug 16, 2015. 1 changed file with 49 additions and 7 deletions.
    56 changes: 49 additions & 7 deletions hostlocalcopy.py
    Original file line number Diff line number Diff line change
    @@ -42,6 +42,14 @@
    parent directories will be created.
    required: yes
    version_added: null
    directory_mode:
    description:
    - The mode for directories which are copied, and missing parent
    directories which are created. If not specified system defaults
    are used.
    required: no
    extends_documentation_fragment:
    - files
    '''

    EXAMPLES = '''
    @@ -50,7 +58,6 @@
    '''

    # - Improvements to do
    # - Setting Ownership and permissions for destination
    # - Optional error on non-supported file type
    # - Verbose option to include list of changed files
    # - complete -not quick- file comparison
    @@ -104,6 +111,8 @@ def run(self):
    src = glob.iglob(os.path.expanduser(srcp))
    dest = os.path.expanduser(self.module.params['dest'])

    self.load_common_file_args()

    # In the first operation mode, dest has a trailing separator, so files
    # matched by src preserve their names.
    if dest.endswith(os.sep):
    @@ -164,16 +173,20 @@ def recursive_copy(self, src, dest):

    # Copy files
    elif os.path.isfile(src):
    if os.path.isfile(dest) and filecmp.cmp(src, dest):
    return # Already correct file
    elif self.check_and_remove(dest):
    shutil.copy2(src, dest)
    # Remove anything in the way and copy the file if required
    if (not os.path.isfile(dest) or not filecmp.cmp(src, dest)):
    if self.check_and_remove(dest):
    shutil.copy2(src, dest)
    # Ensure file attributes are correct
    self.set_attributes(dest, self.file_args)

    # Recursive directory copy
    elif os.path.isdir(src):
    if not os.path.isdir(dest):
    if self.check_and_remove(dest):
    os.mkdir(dest)
    # Ensure directory attributes are correct
    self.set_attributes(dest, self.dir_args)

    # Recursive copy if destination exists
    if os.path.isdir(dest):
    @@ -185,6 +198,11 @@ def recursive_copy(self, src, dest):
    else:
    pass


    # TODO: This code is similar to a part of copy module from Ansible core
    # files modules, which can be found by the comment:
    # Special handling for recursive copy - create intermediate dirs
    #
    # Recursively create parent directories to receive the copy.
    # Return false in check mode if the directory doesn't exist
    # Fail the module if the directory couldn't be created
    @@ -204,14 +222,38 @@ def create_parent_dirs(self, pdir):
    if self.check_mode:
    return False
    os.mkdir(pdir)
    self.set_attributes(pdir, self.dir_args)
    return True


    # Load the common file args defined in the common file modules (ownership,
    # permissions, SELinux context)
    def load_common_file_args(self):
    self.file_args = self.module.load_file_common_arguments(self.module.params)
    self.dir_args = self.module.load_file_common_arguments(self.module.params)

    directory_mode = self.module.params["directory_mode"]
    if directory_mode is not None:
    self.dir_args['mode'] = directory_mode
    else:
    self.dir_args['mode'] = None


    # Set the attributes on the path
    def set_attributes(self, path, args):
    args['path'] = path
    if self.module.set_fs_attributes_if_different(args, False):
    self.changed = True


    def main():
    module = AnsibleModule(
    argument_spec = dict(
    src = dict(required=True),
    dest = dict(required=True),
    src = dict(required=True),
    dest = dict(required=True),
    directory_mode = dict(required=False),
    ),
    add_file_common_args=True,
    supports_check_mode=True,
    )

  3. Alexander Taler revised this gist Aug 15, 2015. 1 changed file with 24 additions and 15 deletions.
    39 changes: 24 additions & 15 deletions hostlocalcopy.py
    Original file line number Diff line number Diff line change
    @@ -108,10 +108,15 @@ def run(self):
    # matched by src preserve their names.
    if dest.endswith(os.sep):

    # Copy each matched src to dest
    for srci in src:
    desti = os.path.join(dest, os.path.basename(srci))
    self.recursive_copy(srci, desti)
    # Strip the trailing separator
    dest = dest[0:-len(os.sep)]

    # dest is the parent dir, so make sure it exists
    if self.create_parent_dirs(dest):
    # Copy each matched src to dest
    for srci in src:
    desti = os.path.join(dest, os.path.basename(srci))
    self.recursive_copy(srci, desti)

    # In the second operation mode, dest does not have a trailing separator,
    # and src must specify exactly one file.
    @@ -127,7 +132,7 @@ def run(self):
    pass

    # Copy src to dest, creating parent directories if required
    if self.create_parents(dest):
    if self.create_parent_dirs(os.path.dirname(dest)):
    self.recursive_copy(srci, dest)

    self.module.exit_json(changed=self.changed)
    @@ -180,28 +185,32 @@ def recursive_copy(self, src, dest):
    else:
    pass

    def create_parents(self, dest):
    parent = os.path.dirname(dest)
    if (os.path.isdir(parent)):
    # Recursively create parent directories to receive the copy.
    # Return false in check mode if the directory doesn't exist
    # Fail the module if the directory couldn't be created
    # Return True if the directory existed or was created
    def create_parent_dirs(self, pdir):
    # Check if directory already exists
    if (os.path.isdir(pdir)):
    return True
    # Recursive call to create parents
    if not create_parents(parent):
    return False
    if (os.path.exists(parent)):
    self.changed = True
    # Check if something's in the way
    if (os.path.exists(pdir)):
    self.module.fail_json(msg="Parent is not a directory: %s" % pdir)
    # Create parents recursively
    if not self.create_parent_dirs(os.path.dirname(pdir)):
    return False
    # Create the directory
    self.changed = True
    if self.check_mode:
    return False
    os.mkdir(parent)
    os.mkdir(pdir)
    return True

    def main():
    module = AnsibleModule(
    argument_spec = dict(
    src = dict(required=True),
    dest = dict(required=True),
    verbose = dict(required=False, default=False, type='bool'),
    ),
    supports_check_mode=True,
    )
  4. Alexander Taler revised this gist Aug 15, 2015. 1 changed file with 5 additions and 24 deletions.
    29 changes: 5 additions & 24 deletions hostlocalcopy.py
    Original file line number Diff line number Diff line change
    @@ -42,15 +42,6 @@
    parent directories will be created.
    required: yes
    version_added: null
    verbose:
    description:
    - If true, then all copied files will be listed, otherwise only
    files which generated errors are listed. Probably not suitable
    for copying large numbers of files.
    required: no
    default: "False"
    choices: [True, False]
    version_added: null
    '''

    EXAMPLES = '''
    @@ -94,9 +85,6 @@
    # * As directory
    # Creation of non-existing parent directory
    #
    # Options:
    # verbose vs. not verbose
    #
    # Error Scenarios:
    # wrong type of file in parent
    # lack of permission to create destination or parents
    @@ -108,8 +96,6 @@
    class HostLocalCopier:
    def __init__(self, module):
    self.module = module
    self.verbose = module.params['verbose']
    self.results = self.verbose and list() or None
    self.changed = False
    self.check_mode = module.check_mode

    @@ -146,18 +132,13 @@ def run(self):

    self.module.exit_json(changed=self.changed)


    def add_result(self, dest):
    if self.results is not None:
    self.results.append(dest)
    self.changed = True

    def check_and_remove(self, dest):
    '''
    Check if changes should be made, and remove something at the
    destination. Return True if replacement should go ahead.
    '''
    self.add_result(dest)
    self.changed = True
    if self.check_mode:
    return False

    @@ -195,9 +176,9 @@ def recursive_copy(self, src, dest):
    self.recursive_copy(os.path.join(src, srce),
    os.path.join(dest, srce))

    # Special files
    # Special files are ignored
    else:
    self.add_result("Skipping special file: " + src)
    pass

    def create_parents(self, dest):
    parent = os.path.dirname(dest)
    @@ -207,9 +188,9 @@ def create_parents(self, dest):
    if not create_parents(parent):
    return False
    if (os.path.exists(parent)):
    self.add_result("Non-directory parent: " + parent)
    self.changed = True
    return False
    self.add_result(parent)
    self.changed = True
    if self.check_mode:
    return False
    os.mkdir(parent)
  5. alex-deref created this gist Aug 4, 2015.
    234 changes: 234 additions & 0 deletions hostlocalcopy.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,234 @@
    #!/usr/bin/env python
    #
    # Copyright (C) 2015 Alexander Taler
    #

    import datetime
    import filecmp
    import glob
    import json
    import os
    import shutil
    import sys

    DOCUMENTATION = '''
    ---
    module: host local copy
    short_description: Recursively copy files locally within the host
    description:
    - Recursively copy files and directories locally on the host. Supports
    Ansible C(--check) mode and change reporting by checking if files differ
    before copying them.
    - Links are copied as links. Things other than files, directories and links
    are not copied.
    author: Alexander Taler
    requirements:
    - Only works on UNIX hosts
    version_added: null
    options:
    src:
    description:
    - Host local file path or glob pattern naming the files which will
    be copied. If more than one item is matched then destination must
    be a directory. If nothing is matched, then nothing is copied.
    required: yes
    version_added: null
    dest:
    description:
    - Host local file path or name where the named files will be
    copied. If it ends with a / then it is a directory and source
    files will preserve their names, otherwise it specifies a new
    name and the source must be a single file or directory. Required
    parent directories will be created.
    required: yes
    version_added: null
    verbose:
    description:
    - If true, then all copied files will be listed, otherwise only
    files which generated errors are listed. Probably not suitable
    for copying large numbers of files.
    required: no
    default: "False"
    choices: [True, False]
    version_added: null
    '''

    EXAMPLES = '''
    # Copy the named file into a directory, preserving its name
    - hostlocalcopy: src=/tmp/fund.conf dest=/etc
    '''

    # - Improvements to do
    # - Setting Ownership and permissions for destination
    # - Optional error on non-supported file type
    # - Verbose option to include list of changed files
    # - complete -not quick- file comparison
    # - handling permissions errors - see copy for example of x permission missing
    # - Optional removal of non-matching stuff from destination
    # - Behavior change if glob matches nothing
    # - Improvements not to do:
    # - Not option to copy symlinks as files instead of links


    # Test cases
    #
    # Files: (check expected behaviour and reported results)
    # Copy File
    # Copy Symlink
    # Copy Directory - recursively
    # Copy 3 levels of directory recursively
    # Not changed if File already exists
    # Not changed if Symlink already exists
    # Not changed if Directory already exists
    # Remove File in the way
    # Remove SYmlink in the way
    # Remove Directory in the way
    # Remove special stuff in the way (device, character special, ...)
    #
    # Invocation
    # Glob pattern matching
    # Copy to a directory preserving names (trailing /)
    # Copy to destination with new name (no trailing /)
    # * As file
    # * As symlink
    # * As directory
    # Creation of non-existing parent directory
    #
    # Options:
    # verbose vs. not verbose
    #
    # Error Scenarios:
    # wrong type of file in parent
    # lack of permission to create destination or parents
    # lack of permission to remove something in the way
    # glob matches multiple to new name
    # glob matchines nothing to new name


    class HostLocalCopier:
    def __init__(self, module):
    self.module = module
    self.verbose = module.params['verbose']
    self.results = self.verbose and list() or None
    self.changed = False
    self.check_mode = module.check_mode

    def run(self):
    srcp = self.module.params['src']
    src = glob.iglob(os.path.expanduser(srcp))
    dest = os.path.expanduser(self.module.params['dest'])

    # In the first operation mode, dest has a trailing separator, so files
    # matched by src preserve their names.
    if dest.endswith(os.sep):

    # Copy each matched src to dest
    for srci in src:
    desti = os.path.join(dest, os.path.basename(srci))
    self.recursive_copy(srci, desti)

    # In the second operation mode, dest does not have a trailing separator,
    # and src must specify exactly one file.
    else:
    try:
    srci = src.next()
    except (StopIteration), e:
    self.module.fail_json(msg="No match for src: %s" % srcp)
    try:
    src.next()
    self.module.fail_json(msg="Too many matches for src: %s" % srcp)
    except (StopIteration), e:
    pass

    # Copy src to dest, creating parent directories if required
    if self.create_parents(dest):
    self.recursive_copy(srci, dest)

    self.module.exit_json(changed=self.changed)


    def add_result(self, dest):
    if self.results is not None:
    self.results.append(dest)
    self.changed = True

    def check_and_remove(self, dest):
    '''
    Check if changes should be made, and remove something at the
    destination. Return True if replacement should go ahead.
    '''
    self.add_result(dest)
    if self.check_mode:
    return False

    # Clear whatever's in the way
    if os.path.isdir(dest):
    shutil.rmtree(dest)
    elif os.path.exists(dest):
    os.remove(dest)
    return True

    def recursive_copy(self, src, dest):
    # Recreate Links
    if os.path.islink(src):
    if os.path.islink(dest) and os.readlink(dest) == os.readlink(src):
    return # Already correct link
    elif self.check_and_remove(dest):
    os.symlink(os.readlink(src), dest)

    # Copy files
    elif os.path.isfile(src):
    if os.path.isfile(dest) and filecmp.cmp(src, dest):
    return # Already correct file
    elif self.check_and_remove(dest):
    shutil.copy2(src, dest)

    # Recursive directory copy
    elif os.path.isdir(src):
    if not os.path.isdir(dest):
    if self.check_and_remove(dest):
    os.mkdir(dest)

    # Recursive copy if destination exists
    if os.path.isdir(dest):
    for srce in os.listdir(src):
    self.recursive_copy(os.path.join(src, srce),
    os.path.join(dest, srce))

    # Special files
    else:
    self.add_result("Skipping special file: " + src)

    def create_parents(self, dest):
    parent = os.path.dirname(dest)
    if (os.path.isdir(parent)):
    return True
    # Recursive call to create parents
    if not create_parents(parent):
    return False
    if (os.path.exists(parent)):
    self.add_result("Non-directory parent: " + parent)
    return False
    self.add_result(parent)
    if self.check_mode:
    return False
    os.mkdir(parent)
    return True

    def main():
    module = AnsibleModule(
    argument_spec = dict(
    src = dict(required=True),
    dest = dict(required=True),
    verbose = dict(required=False, default=False, type='bool'),
    ),
    supports_check_mode=True,
    )

    hlcopier = HostLocalCopier(module)
    hlcopier.run()


    from ansible.module_utils.basic import *
    if __name__ == '__main__':
    main()