Repository: ansible/ansible

Issue:
synchronize uses remote_user to build local tmpdir
##### SUMMARY

When using a `remote_user` with `synchronize`, `ActionBase._make_tmp_path` will utilize `remote_user` when building the tmpdir for local tmpdirs.

This was introduced in https://github.com/ansible/ansible/pull/36755

POC fix:

```diff

diff --git a/lib/ansible/plugins/action/synchronize.py b/lib/ansible/plugins/action/synchronize.py
index 54b053cb0e..ab6e5bfe6f 100644
--- a/lib/ansible/plugins/action/synchronize.py
+++ b/lib/ansible/plugins/action/synchronize.py
@@ -301,6 +301,7 @@ class ActionModule(ActionBase):
 
             new_connection = connection_loader.get('local', self._play_context, new_stdin)
             self._connection = new_connection
+            self._connection._remote_is_local = True
             self._override_module_replaced_vars(task_vars)
 
         # SWITCH SRC AND DEST HOST PER MODE
```

Cross ref: https://bugzilla.redhat.com/show_bug.cgi?id=1580530

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
synchronize
##### ANSIBLE VERSION
<!--- Paste, BELOW THIS COMMENT, verbatim output from "ansible --version" between quotes below -->
```
2.5.3
2.6
devel
```

##### CONFIGURATION
<!--- If using Ansible 2.4 or above, paste, BELOW THIS COMMENT, the results of "ansible-config dump --only-changed"
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).-->
N/A

##### OS / ENVIRONMENT
<!--- Mention, BELOW THIS COMMENT, the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.-->
N/A

##### STEPS TO REPRODUCE
<!--- For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used. -->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- hosts: all
  gather_facts: false
  remote_user: sivel
  tasks:
  - name: Synchronize
    synchronize:
      src: /tmp/source
      dest: /tmp/dest
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
```
/Users/matt/.ansible/tmp/ansible-local-487032hivkudu/ansible-tmp-1527010893.751785-25473301101888/
```

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
~sivel/.ansible/tmp/ansible-tmp-1527010812.732762-53461373632378/
```


Discussion:
cc @jctanner @mkrizek 
cc @tima
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
`_remote_is_local` was recently added in https://github.com/ansible/ansible/pull/40259 to address a similar problem.

The only problem is due to how synchronize works, by swapping the connection plugin.

The POC above basically sets an instance attribute which will win out over the class attribute, preventing us from taking the problematic code path.
See also https://github.com/ansible/ansible/pull/40259 Don't rewrite remote paths when remote is secretly local
Please ensure that the Networking team review and approve the change to ensure this doesn't regress https://github.com/ansible/ansible/pull/40259

Resolving patch:
diff --git a/lib/ansible/plugins/action/synchronize.py b/lib/ansible/plugins/action/synchronize.py
--- a/lib/ansible/plugins/action/synchronize.py
+++ b/lib/ansible/plugins/action/synchronize.py
@@ -301,6 +301,9 @@ def run(self, tmp=None, task_vars=None):
 
             new_connection = connection_loader.get('local', self._play_context, new_stdin)
             self._connection = new_connection
+            # Override _remote_is_local as an instance attribute specifically for the synchronize use case
+            # ensuring we set local tmpdir correctly
+            self._connection._remote_is_local = True
             self._override_module_replaced_vars(task_vars)
 
         # SWITCH SRC AND DEST HOST PER MODE


Test patch:
diff --git a/test/units/plugins/action/test_synchronize.py b/test/units/plugins/action/test_synchronize.py
--- a/test/units/plugins/action/test_synchronize.py
+++ b/test/units/plugins/action/test_synchronize.py
@@ -42,6 +42,10 @@
 '''
 
 
+class BreakPoint(Exception):
+    pass
+
+
 class TaskMock(object):
     args = {'src': u'/tmp/deleteme',
             'dest': '/tmp/deleteme',
@@ -246,3 +250,15 @@ def test_delegate_remote_su(self):
         # delegate to other remote host with su enabled
         x = SynchronizeTester()
         x.runtest(fixturepath=os.path.join(self.fixturedir, 'delegate_remote_su'))
+
+    @patch.object(ActionModule, '_low_level_execute_command', side_effect=BreakPoint)
+    @patch.object(ActionModule, '_remote_expand_user', side_effect=ActionModule._remote_expand_user, autospec=True)
+    def test_remote_user_not_in_local_tmpdir(self, spy_remote_expand_user, ll_ec):
+        x = SynchronizeTester()
+        SAM = ActionModule(x.task, x.connection, x._play_context,
+                           x.loader, x.templar, x.shared_loader_obj)
+        try:
+            SAM.run(task_vars={'hostvars': {'foo': {}, 'localhost': {}}, 'inventory_hostname': 'foo'})
+        except BreakPoint:
+            pass
+        self.assertEqual(spy_remote_expand_user.call_count, 0)