From e692212a36c1c94759df750fd01a0e71926d295c Mon Sep 17 00:00:00 2001
From: Timothy Appnel <tim@appnel.com>
Date: Fri, 13 Sep 2013 23:20:00 -0400
Subject: [PATCH 1/7] Return of sychronize module code for take 3

---
 files/synchronize | 147 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 147 insertions(+)
 create mode 100644 files/synchronize

diff --git a/files/synchronize b/files/synchronize
new file mode 100644
index 00000000000..5e2a038d01e
--- /dev/null
+++ b/files/synchronize
@@ -0,0 +1,147 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2012-2013, Timothy Appnel <tim@appnel.com>
+#
+# Ansible 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.
+#
+# Ansible 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 Ansible.  If not, see <http://www.gnu.org/licenses/>.
+
+import subprocess
+
+DOCUMENTATION = '''
+---
+module: synchronize
+version_added: "1.3"
+short_description: Uses rsync to make synchronizing file paths in your playbooks quick and easy.
+description:
+    - This is a wrapper around rsync. Of course you could just use the command action to call rsync yourself, but you also have to add a fair number of boilerplate options and host facts. You still may need to call rsync directly via C(command) or C(shell) depending on your use case. The synchronize action is meant to do common things with C(rsync) easily. It does not provide access to the full power of rsync, but does make most invocations easier to follow.
+options:
+  src:
+    description:
+      - Path on the source machine that will be synchronized to the destination; The path can be absolute or relative.
+    required: true
+  dest:
+    description:
+      - Path on the destination machine that will be synchronized from the source; The path can be absolute or relative.
+    required: true
+  mode:
+    description:
+      - Specify the direction of the synchroniztion. In push mode the localhost or delgate is the  source; In pull mode the remote host in context is the source.
+    required: false
+    choices: [ 'push', 'pull' ]
+    default: 'push'
+  verbosity:
+    description:
+      - An integer controlling the amount of information returned during processing. See the C(-v, --verbose) option of the rsync man page for details. If verbosity is not defined or a value of 0 is assumed, the C(--quiet) option is passed and information is supressed.
+    required: false
+    default: 0
+  delete:
+    description:
+      - Delete files that don't exist (after transfer, not before) in the C(src) path.
+    choices: [ 'yes', 'no' ]
+    default: 'no'
+    required: false
+  rsync_path:
+    description:
+      - Specify the rsync command to run on the remote machine. See C(--rsync-path) on the rsync man page.
+    required: false
+author: Timothy Appnel
+'''
+
+EXAMPLES = '''
+# Synchronization of src on the control machien to dest on the remote hosts
+synchronize: src=some/relative/path dest=/some/absolute/path
+
+# Synchronization of two paths both on the control machine
+local_action: synchronize src=some/relative/path dest=/some/absolute/path
+
+# Synchronization of src on the inventory host to the dest on the localhost in
+pull mode
+synchronize: mode=pull src=some/relative/path dest=/some/absolute/path
+
+# Synchronization of src on delegate host to dest on the current inventory host
+synchronize: >
+    src=some/relative/path dest=/some/absolute/path
+    delegate_to: delegate.host
+
+# Synchronize and delete files in dest on the remote host that are not found in src of localhost.
+synchronize: src=some/relative/path dest=/some/absolute/path delete=yes
+
+# Synchronize and return verbose information from the rsync transfer.
+synchronize: src=some/relative/path dest=/some/absolute/path verbosity=1
+
+# Synchronize using an alternate rsync command
+synchronize: src=some/relative/path dest=/some/absolute/path rsync_path="sudo rsync"
+'''
+
+
+def main():
+    module = AnsibleModule(
+        argument_spec = dict(
+            src = dict(required=True),
+            dest = dict(required=True),
+            verbosity = dict(default=0),
+            tmp_dir = dict(default=None),
+            delete = dict(default='no', type='bool'),
+            private_key = dict(default=None),
+            rsync_path = dict(default=None),
+        ),
+        supports_check_mode = True
+    )
+
+    source = module.params['src']
+    dest = module.params['dest']
+    verbosity = module.params['verbosity']
+    delete = module.params['delete']
+    private_key = module.params['private_key']
+    rsync_path = module.params['rsync_path']
+    rsync = module.params.get('local_rsync_path', 'rsync')
+    temp = os.path.dirname(os.path.realpath(__file__))
+
+    cmd = '%s --archive --delay-updates --compress' % rsync
+    if module.check_mode:
+        cmd = cmd + ' --dry-run'
+    if verbosity:
+        cmd = '%s -%s' % (cmd, 'v' * int(verbosity))
+    else:
+        cmd = cmd + ' --quiet'
+    if temp:
+        cmd = cmd + ' --temp-dir ' + temp
+    if delete:
+        cmd = cmd + ' --delete-after'
+    if private_key is None:
+        private_key = ''
+    else:
+        private_key = '-i '+ private_key 
+    cmd = cmd + " --rsh '%s %s -o %s'" % ('ssh', private_key,
+                'StrictHostKeyChecking=no')  # need ssh param
+    if rsync_path:
+        cmd = cmd + ' --rsync-path ' + rsync_path
+
+    cmd = ' '.join([cmd, source, dest])
+    cmdstr = cmd
+    cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
+                           stderr=subprocess.PIPE)
+    (out, err) = cmd.communicate()
+    if cmd.returncode:
+        return module.fail_json(msg=err, rc=cmd.returncode, cmd=cmdstr)
+    else:
+        return module.exit_json(changed=True, msg=out,
+                                rc=cmd.returncode, cmd=cmdstr, check=module.check_mode)
+
+
+# include magic from lib/ansible/module_common.py
+#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
+
+main()
+

From 26585aae46131c973482ad23c9d52435091b513c Mon Sep 17 00:00:00 2001
From: Timothy Appnel <tim@appnel.com>
Date: Thu, 19 Sep 2013 00:07:05 -0400
Subject: [PATCH 2/7] Updated version_added in sychronize module

---
 files/synchronize | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/files/synchronize b/files/synchronize
index 5e2a038d01e..cf4768970bb 100644
--- a/files/synchronize
+++ b/files/synchronize
@@ -21,7 +21,7 @@ import subprocess
 DOCUMENTATION = '''
 ---
 module: synchronize
-version_added: "1.3"
+version_added: "1.4"
 short_description: Uses rsync to make synchronizing file paths in your playbooks quick and easy.
 description:
     - This is a wrapper around rsync. Of course you could just use the command action to call rsync yourself, but you also have to add a fair number of boilerplate options and host facts. You still may need to call rsync directly via C(command) or C(shell) depending on your use case. The synchronize action is meant to do common things with C(rsync) easily. It does not provide access to the full power of rsync, but does make most invocations easier to follow.

From 35068527db97b089e915625935e2341984eed591 Mon Sep 17 00:00:00 2001
From: Timothy Appnel <tim@appnel.com>
Date: Sat, 14 Sep 2013 00:01:17 -0400
Subject: [PATCH 3/7] Removed busted tmp_dir option that was a bad idea to
 begin with.

---
 files/synchronize | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/files/synchronize b/files/synchronize
index cf4768970bb..1362de5530b 100644
--- a/files/synchronize
+++ b/files/synchronize
@@ -91,7 +91,6 @@ def main():
             src = dict(required=True),
             dest = dict(required=True),
             verbosity = dict(default=0),
-            tmp_dir = dict(default=None),
             delete = dict(default='no', type='bool'),
             private_key = dict(default=None),
             rsync_path = dict(default=None),
@@ -106,7 +105,6 @@ def main():
     private_key = module.params['private_key']
     rsync_path = module.params['rsync_path']
     rsync = module.params.get('local_rsync_path', 'rsync')
-    temp = os.path.dirname(os.path.realpath(__file__))
 
     cmd = '%s --archive --delay-updates --compress' % rsync
     if module.check_mode:
@@ -115,8 +113,6 @@ def main():
         cmd = '%s -%s' % (cmd, 'v' * int(verbosity))
     else:
         cmd = cmd + ' --quiet'
-    if temp:
-        cmd = cmd + ' --temp-dir ' + temp
     if delete:
         cmd = cmd + ' --delete-after'
     if private_key is None:

From 10f336a97c3c09253bb1c026b34613f0aebc5c8f Mon Sep 17 00:00:00 2001
From: Timothy Appnel <tim@appnel.com>
Date: Mon, 16 Sep 2013 00:08:10 -0400
Subject: [PATCH 4/7] Added ingenious change detection trick from @smoothify
 though at the cost of verbosity controls. (Quiet or verbosity > 1 breaks
 change detection. Also added better use of module_common methods.

---
 files/synchronize | 33 +++++++++------------------------
 1 file changed, 9 insertions(+), 24 deletions(-)

diff --git a/files/synchronize b/files/synchronize
index 1362de5530b..7bed56d3c1c 100644
--- a/files/synchronize
+++ b/files/synchronize
@@ -40,11 +40,6 @@ options:
     required: false
     choices: [ 'push', 'pull' ]
     default: 'push'
-  verbosity:
-    description:
-      - An integer controlling the amount of information returned during processing. See the C(-v, --verbose) option of the rsync man page for details. If verbosity is not defined or a value of 0 is assumed, the C(--quiet) option is passed and information is supressed.
-    required: false
-    default: 0
   delete:
     description:
       - Delete files that don't exist (after transfer, not before) in the C(src) path.
@@ -77,9 +72,6 @@ synchronize: >
 # Synchronize and delete files in dest on the remote host that are not found in src of localhost.
 synchronize: src=some/relative/path dest=/some/absolute/path delete=yes
 
-# Synchronize and return verbose information from the rsync transfer.
-synchronize: src=some/relative/path dest=/some/absolute/path verbosity=1
-
 # Synchronize using an alternate rsync command
 synchronize: src=some/relative/path dest=/some/absolute/path rsync_path="sudo rsync"
 '''
@@ -90,7 +82,6 @@ def main():
         argument_spec = dict(
             src = dict(required=True),
             dest = dict(required=True),
-            verbosity = dict(default=0),
             delete = dict(default='no', type='bool'),
             private_key = dict(default=None),
             rsync_path = dict(default=None),
@@ -100,7 +91,6 @@ def main():
 
     source = module.params['src']
     dest = module.params['dest']
-    verbosity = module.params['verbosity']
     delete = module.params['delete']
     private_key = module.params['private_key']
     rsync_path = module.params['rsync_path']
@@ -109,10 +99,6 @@ def main():
     cmd = '%s --archive --delay-updates --compress' % rsync
     if module.check_mode:
         cmd = cmd + ' --dry-run'
-    if verbosity:
-        cmd = '%s -%s' % (cmd, 'v' * int(verbosity))
-    else:
-        cmd = cmd + ' --quiet'
     if delete:
         cmd = cmd + ' --delete-after'
     if private_key is None:
@@ -122,19 +108,18 @@ def main():
     cmd = cmd + " --rsh '%s %s -o %s'" % ('ssh', private_key,
                 'StrictHostKeyChecking=no')  # need ssh param
     if rsync_path:
-        cmd = cmd + ' --rsync-path ' + rsync_path
-
+        cmd = cmd + " --rsync-path '%s'" %(rsync_path)
+    changed_marker = '<<changed>>'
+    cmd = cmd + " --out-format='" + changed_marker + "%i %n%L'"
     cmd = ' '.join([cmd, source, dest])
     cmdstr = cmd
-    cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
-                           stderr=subprocess.PIPE)
-    (out, err) = cmd.communicate()
-    if cmd.returncode:
-        return module.fail_json(msg=err, rc=cmd.returncode, cmd=cmdstr)
+    (rc, out, err) = module.run_command(cmd)
+    if rc:
+        return module.fail_json(msg=err, rc=rc, cmd=cmdstr)
     else:
-        return module.exit_json(changed=True, msg=out,
-                                rc=cmd.returncode, cmd=cmdstr, check=module.check_mode)
-
+        changed = changed_marker in out
+        return module.exit_json(changed=changed, msg=out.replace(changed_marker,''),
+                                rc=rc, cmd=cmdstr)
 
 # include magic from lib/ansible/module_common.py
 #<<INCLUDE_ANSIBLE_MODULE_COMMON>>

From d7c737516e8fe236ecfffaf02d921299d2f59889 Mon Sep 17 00:00:00 2001
From: Timothy Appnel <tim@appnel.com>
Date: Wed, 18 Sep 2013 21:17:50 -0400
Subject: [PATCH 5/7] Added archive options to sychronize module as suggested
 by @smoothify but with a different default scheme to keep param definitions
 to a minimum.

---
 files/synchronize | 48 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 47 insertions(+), 1 deletion(-)

diff --git a/files/synchronize b/files/synchronize
index 7bed56d3c1c..988a4a97d66 100644
--- a/files/synchronize
+++ b/files/synchronize
@@ -85,6 +85,14 @@ def main():
             delete = dict(default='no', type='bool'),
             private_key = dict(default=None),
             rsync_path = dict(default=None),
+            archive = dict(default='yes', type='bool'),
+            dirs  = dict(type='bool'),
+            recursive = dict(type='bool'),
+            links = dict(type='bool'),
+            perms = dict(type='bool'),
+            times = dict(type='bool'),
+            owner = dict(type='bool'),
+            group = dict(type='bool'),
         ),
         supports_check_mode = True
     )
@@ -95,12 +103,50 @@ def main():
     private_key = module.params['private_key']
     rsync_path = module.params['rsync_path']
     rsync = module.params.get('local_rsync_path', 'rsync')
+    archive = module.params['archive']
+    dirs = module.params['dirs']
+    # the default of these params depends on the value of archive
+    recursive = module.params['recursive']
+    links = module.params['links']
+    perms = module.params['perms']
+    times = module.params['times']
+    owner = module.params['owner']
+    group = module.params['group']
 
-    cmd = '%s --archive --delay-updates --compress' % rsync
+    cmd = '%s --delay-updates --compress' % rsync
     if module.check_mode:
         cmd = cmd + ' --dry-run'
     if delete:
         cmd = cmd + ' --delete-after'
+    if archive:
+        cmd = cmd + ' --archive'
+        if recursive is False:
+            cmd = cmd + ' --no-recursive'
+        if links is False:
+            cmd = cmd + ' --no-links'
+        if perms is False:
+            cmd = cmd + ' --no-perms'
+        if times is False:
+            cmd = cmd + ' --no-times'
+        if owner is False:
+            cmd = cmd + ' --no-owner'
+        if group is False:
+            cmd = cmd + ' --no-group'
+    else:
+        if recursive is True:
+            cmd = cmd + ' --recursive'
+        if links is True:
+            cmd = cmd + ' --links'
+        if perms is True:
+            cmd = cmd + ' --perms'
+        if times is True:
+            cmd = cmd + ' --times'
+        if owner is True:
+            cmd = cmd + ' --owner'
+        if group is True:
+            cmd = cmd + ' --group'
+    if dirs:
+        cmd = cmd + ' --dirs'
     if private_key is None:
         private_key = ''
     else:

From 4cb2cb9bacc0f7b970b4b2394feddd8803695327 Mon Sep 17 00:00:00 2001
From: Timothy Appnel <tim@appnel.com>
Date: Thu, 19 Sep 2013 02:25:33 -0400
Subject: [PATCH 6/7] Modified changed marker in sychronize module

---
 files/synchronize | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/files/synchronize b/files/synchronize
index 988a4a97d66..201dd524cc7 100644
--- a/files/synchronize
+++ b/files/synchronize
@@ -155,7 +155,7 @@ def main():
                 'StrictHostKeyChecking=no')  # need ssh param
     if rsync_path:
         cmd = cmd + " --rsync-path '%s'" %(rsync_path)
-    changed_marker = '<<changed>>'
+    changed_marker = '<<CHANGED>>'
     cmd = cmd + " --out-format='" + changed_marker + "%i %n%L'"
     cmd = ' '.join([cmd, source, dest])
     cmdstr = cmd

From 4e8f5eaef5a0e9b644d475ef84da20b035ab692b Mon Sep 17 00:00:00 2001
From: Timothy Appnel <tim@appnel.com>
Date: Thu, 19 Sep 2013 03:09:17 -0400
Subject: [PATCH 7/7] Added docs for archive options in sychronize module.
 Added missing default value for dirs option.

---
 files/synchronize | 61 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 59 insertions(+), 2 deletions(-)

diff --git a/files/synchronize b/files/synchronize
index 201dd524cc7..73daf5962b0 100644
--- a/files/synchronize
+++ b/files/synchronize
@@ -40,12 +40,60 @@ options:
     required: false
     choices: [ 'push', 'pull' ]
     default: 'push'
+  archive:
+    description:
+      - Mirrors the rsync archive flag, enables recursive, links, perms, times, owner, group flags and -D.
+    choices: [ 'yes', 'no' ]
+    default: 'yes'
+    required: false
   delete:
     description:
       - Delete files that don't exist (after transfer, not before) in the C(src) path.
     choices: [ 'yes', 'no' ]
     default: 'no'
     required: false
+  dirs:
+    description:
+      - Transfer directories without recursing
+    choices: [ 'yes', 'no' ]
+    default: 'no'
+    required: false
+  recursive:
+    description:
+      - Recurse into directories.
+    choices: [ 'yes', 'no' ]
+    default: the value of the archive option
+    required: false
+  links:
+    description:
+      - Copy symlinks as symlinks.
+    choices: [ 'yes', 'no' ]
+    default: the value of the archive option
+    required: false
+  perms:
+    description:
+      - Preserve permissions.
+    choices: [ 'yes', 'no' ]
+    default: the value of the archive option
+    required: false
+  times:
+    description:
+      - Preserve modification times
+    choices: [ 'yes', 'no' ]
+    default: the value of the archive option
+    required: false
+  owner:
+    description:
+      - Preserve owner (super user only)
+    choices: [ 'yes', 'no' ]
+    default: the value of the archive option
+    required: false
+  group:
+    description:
+      - Preserve group
+    choices: [ 'yes', 'no' ]
+    default: the value of the archive option
+    required: false
   rsync_path:
     description:
       - Specify the rsync command to run on the remote machine. See C(--rsync-path) on the rsync man page.
@@ -54,9 +102,18 @@ author: Timothy Appnel
 '''
 
 EXAMPLES = '''
-# Synchronization of src on the control machien to dest on the remote hosts
+# Synchronization of src on the control machine to dest on the remote hosts
 synchronize: src=some/relative/path dest=/some/absolute/path
 
+# Synchronization without any --archive options enabled
+synchronize: src=some/relative/path dest=/some/absolute/path archive=no
+
+# Synchronization with --archive options enabled except for --recursive
+synchronize: src=some/relative/path dest=/some/absolute/path recursive=no
+
+# Synchronization without --archive options enabled except use --links
+synchronize: src=some/relative/path dest=/some/absolute/path archive=no links=yes
+
 # Synchronization of two paths both on the control machine
 local_action: synchronize src=some/relative/path dest=/some/absolute/path
 
@@ -86,7 +143,7 @@ def main():
             private_key = dict(default=None),
             rsync_path = dict(default=None),
             archive = dict(default='yes', type='bool'),
-            dirs  = dict(type='bool'),
+            dirs  = dict(default='no', type='bool'),
             recursive = dict(type='bool'),
             links = dict(type='bool'),
             perms = dict(type='bool'),