Repository: ansible-collections/community.general

Issue:
git_config changes remote on another repository
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
<!--- Explain the problem briefly below -->
When using `git_config` module to change remote URL, the URL is changed on repository from where the ansible is lauched, usually the repository with my ansible project itself.

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->
git_config

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```paste below
ansible 2.9.10
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/vzima/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/vzima/.local/lib/python3.8/site-packages/ansible
  executable location = /home/vzima/.local/bin/ansible
  python version = 3.8.3 (default, May 14 2020, 11:03:12) [GCC 9.3.0]
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```paste below

```

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->
Debian testing

##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->
1. Create empty git repository: `cd /tmp && git init git_config_tests`.
2. Create playbook `/tmp/git_config_tests/playbook.yml` with contents below.
3. Run ansible: `cd /tmp/git_config_tests && ansible-playbook playbook.yml`.
4. Check the remote of `git_config_tests` repository: `cd /tmp/git_config_tests && git remote -v`
<!--- Paste example playbooks or commands between quotes below -->
```yaml
- hosts: localhost
  tasks:
    - name: "Update another git remote"
      git_config:
        scope: local
        state: present
        repo: "/tmp/another/"
        name: remote.origin.url
        value: "git@example.org:another.git"
```

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- Describe what you expected to happen when running the steps above -->
* `git_config` should complain that `/tmp/another/` does not exist.
* `git_config` shouldn't change remote on another repository.

##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->
The remote is changed in `/tmp/git_config_tests` instead.

<!--- Paste verbatim command output between quotes -->
```paste below
TASK [Update another git remote] **********************************************************************************************************************************************************************************
task path: /tmp/git_config_tests/playbook.yml:3
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: vzima
<127.0.0.1> EXEC /bin/sh -c 'echo ~vzima && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/vzima/.ansible/tmp `"&& mkdir /home/vzima/.ansible/tmp/ansible-tmp-1594121028.9610343-45689-73943075504516 && echo ansible-tmp-1594121028.9610343-45689-73943075504516="` echo /home/vzima/.ansible/tmp/ansible-tmp-1594121028.9610343-45689-73943075504516 `" ) && sleep 0'
Using module file /home/vzima/.local/lib/python3.8/site-packages/ansible/modules/source_control/git_config.py
<127.0.0.1> PUT /home/vzima/.ansible/tmp/ansible-local-45618030o64_6/tmpz4kwnrux TO /home/vzima/.ansible/tmp/ansible-tmp-1594121028.9610343-45689-73943075504516/AnsiballZ_git_config.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/vzima/.ansible/tmp/ansible-tmp-1594121028.9610343-45689-73943075504516/ /home/vzima/.ansible/tmp/ansible-tmp-1594121028.9610343-45689-73943075504516/AnsiballZ_git_config.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python3 /home/vzima/.ansible/tmp/ansible-tmp-1594121028.9610343-45689-73943075504516/AnsiballZ_git_config.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c 'rm -f -r /home/vzima/.ansible/tmp/ansible-tmp-1594121028.9610343-45689-73943075504516/ > /dev/null 2>&1 && sleep 0'
changed: [localhost] => {
    "changed": true,
    "diff": {
        "after": "git@example.org:another.git\n",
        "after_header": "/usr/bin/git config --includes --local remote.origin.url",
        "before": "\n",
        "before_header": "/usr/bin/git config --includes --local remote.origin.url"
    },
    "invocation": {
        "module_args": {
            "list_all": false,
            "name": "remote.origin.url",
            "repo": "/tmp/another/",
            "scope": "local",
            "state": "present",
            "value": "git@example.org:another.git"
        }
    },
    "msg": "setting changed"
}
META: ran handlers
META: ran handlers
```



Discussion:
Files identified in the description:
* [`plugins/modules/source_control/git_config.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/source_control/git_config.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @djmattyg007 @mgedmin
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@ziima I am not the maintainer for this module, and I have not used it with local directories before. That said, looking into the code, *it seems* that the the `repo` parameter is only used with the `list_all` parameter. Which looks quite confusing to me.

@djmattyg007 @mgedmin do you guys have this on the radar?
Actually, the problem seems to be in Ansible itself, in `AnsibleModule.run_command()`:

```python
        # make sure we're in the right working directory
        if cwd and os.path.isdir(cwd):
            cwd = to_bytes(os.path.abspath(os.path.expanduser(cwd)), errors='surrogate_or_strict')
            kwargs['cwd'] = cwd
            try:
                os.chdir(cwd)
            except (OSError, IOError) as e:
                self.fail_json(rc=e.errno, msg="Could not open %s, %s" % (cwd, to_native(e)),
                               exception=traceback.format_exc())

        old_umask = None
```
If `cmd` is not a directory (`os.path.isdir`), then it is utterly ignored.
> @ziima I am not the maintainer for this module, and I have not used it with local directories before. That said, looking into the code, _it seems_ that the the `repo` parameter is only used with the `list_all` parameter. Which looks quite confusing to me.

I correct myself: it *should* do exactly what you expected it to, but with the problem in `AnsibleModule`, that's failing miserably.

I have just submitted a PR to the main ansible repo, in that regard:
https://github.com/ansible/ansible/pull/72390

@ziima ansible's new release should be available next week - as that happens, we will be able to patch the module to use that extra parameter on `run_command()` and wrap this issue up.
@russoz two points:
1. ansible-base will be released in ~two weeks, so it will be included in the Ansible release in ~4 weeks.
2. we cannot use that extra parameter on `run_command` for a longer time since we also support older versions of ansible-base and ansible 2.9. You'll need to add some special logic to handle older versions for this.
@felixfontein eeer.... don't we have ansible facts stating ansible's version? :anguished:  I searched for it now and... nada.
@russoz there's a fact for the ansible-base/ansible-core version (`ansible_version`), but not for the Ansible version. How is such a fact related to this though?
What about doing an `if not os.path.isdir(dir): module.fail_json(msg="directory does not exist"):`?  It'll be not 100% reliable (someone might remove or rename the directory after the check but before `module.run_command()`), but it's better than the status quo.  And the ansible fix will close the race window.
If someone wants to create a PR, I'm happy to review it.

What the PR should do:
1. First do the check @mgedmin mentions.
2. Use a `try / except` around the `module.run_command` which uses the new option; if the except catches a `TypeError`, then call `module.run_command` again without the new option - that's compatibility for older ansible-base/ansible-core versions, and for Ansible 2.9.

Resolving patch:
diff --git a/plugins/modules/source_control/git_config.py b/plugins/modules/source_control/git_config.py
--- a/plugins/modules/source_control/git_config.py
+++ b/plugins/modules/source_control/git_config.py
@@ -148,6 +148,8 @@
     alias.diffc: "diff --cached"
     alias.remotev: "remote -v"
 '''
+import os
+
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.six.moves import shlex_quote
 
@@ -245,7 +247,13 @@ def main():
         else:
             new_value_quoted = shlex_quote(new_value)
             cmd = ' '.join(args + [new_value_quoted])
-        (rc, out, err) = module.run_command(cmd, cwd=dir)
+        try:  # try using extra parameter from ansible-base 2.10.4 onwards
+            (rc, out, err) = module.run_command(cmd, cwd=dir, ignore_invalid_cwd=False)
+        except TypeError:
+            # @TODO remove try/except when community.general drop support for 2.10.x
+            if not os.path.isdir(dir):
+                module.fail_json(msg="Cannot find directory '{0}'".format(dir))
+            (rc, out, err) = module.run_command(cmd, cwd=dir)
         if err:
             module.fail_json(rc=rc, msg=err, cmd=cmd)
 


Test patch: