Repository: ansible-collections/community.general

Issue:
Calling ansible_galaxy_install generates "unsupported locale setting" error 
### Summary

Installing a module with `ansible-galaxy` works no problem, but when trying to install a collection with `community.general.ansible_galaxy_install` if generates the following error:
```
fatal: [localhost]: FAILED! => {
    "changed": false,
    "cmd": "/home/matt/.local/bin/ansible-galaxy --version",
    "invocation": {
        "module_args": {
            "ack_ansible29": false,
            "ack_min_ansiblecore211": false,
            "dest": null,
            "force": true,
            "name": "kubernetes.core",
            "no_deps": false,
            "requirements_file": null,
            "type": "collection"
        }
    },
    "msg": "ERROR: Ansible could not initialize the preferred locale: unsupported locale setting",
    "rc": 1,
    "stderr": "ERROR: Ansible could not initialize the preferred locale: unsupported locale setting\n",
    "stderr_lines": [
        "ERROR: Ansible could not initialize the preferred locale: unsupported locale setting"
    ],
    "stdout": "",
    "stdout_lines": []
}
```

### Issue Type

Bug Report

### Component Name

ansible_galaxy_install

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.14.0]
  config file = None
  configured module search path = ['/home/matt/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/matt/.local/lib/python3.10/site-packages/ansible
  ansible collection location = /home/matt/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/matt/.local/bin/ansible
  python version = 3.10.6 (main, Nov  2 2022, 18:53:38) [GCC 11.3.0] (/usr/bin/python3)
  jinja version = 3.0.3
  libyaml = True
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
# /home/matt/.ansible/collections/ansible_collections
Collection        Version
----------------- -------
community.general 6.0.1  

# /home/matt/.local/lib/python3.10/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 6.0.1 
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
CONFIG_FILE() = None
```


### OS / Environment

Ubuntu 22.04

### Steps to Reproduce


```yaml role task
- name: Install Ansible collection kubernetes.core
  community.general.ansible_galaxy_install:
    type: collection
    name: kubernetes.core
```

Locale is legal for the UK.
```sh
$ locale
LANG=en_GB.UTF-8
LANGUAGE=
LC_CTYPE="en_GB.UTF-8"
LC_NUMERIC="en_GB.UTF-8"
LC_TIME="en_GB.UTF-8"
LC_COLLATE="en_GB.UTF-8"
LC_MONETARY="en_GB.UTF-8"
LC_MESSAGES="en_GB.UTF-8"
LC_PAPER="en_GB.UTF-8"
LC_NAME="en_GB.UTF-8"
LC_ADDRESS="en_GB.UTF-8"
LC_TELEPHONE="en_GB.UTF-8"
LC_MEASUREMENT="en_GB.UTF-8"
LC_IDENTIFICATION="en_GB.UTF-8"
LC_ALL=en_GB.UTF-8
```


### Expected Results

It should just install or skip the module if already installed.

Installing via the command line works no problem

```sh
$ ansible-galaxy collection install kubernetes.core --force
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://galaxy.ansible.com/download/kubernetes-core-2.3.2.tar.gz to /home/matt/.ansible/tmp/ansible-local-54212cgr1gdmr/tmpkklx4jfb/kubernetes-core-2.3.2-351kys1s
Installing 'kubernetes.core:2.3.2' to '/home/matt/.ansible/collections/ansible_collections/kubernetes/core'
kubernetes.core:2.3.2 was installed successfully
```

`ansible-galaxy --version` also returns OK
```sh
$ /home/matt/.local/bin/ansible-galaxy --version
ansible-galaxy [core 2.14.0]
  config file = None
  configured module search path = ['/home/matt/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/matt/.local/lib/python3.10/site-packages/ansible
  ansible collection location = /home/matt/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/matt/.local/bin/ansible-galaxy
  python version = 3.10.6 (main, Nov  2 2022, 18:53:38) [GCC 11.3.0] (/usr/bin/python3)
  jinja version = 3.0.3
  libyaml = True
```

### Actual Results

```console (paste below)
fatal: [localhost]: FAILED! => {
    "changed": false,
    "cmd": "/home/matt/.local/bin/ansible-galaxy --version",
    "invocation": {
        "module_args": {
            "ack_ansible29": false,
            "ack_min_ansiblecore211": false,
            "dest": null,
            "force": true,
            "name": "kubernetes.core",
            "no_deps": false,
            "requirements_file": null,
            "type": "collection"
        }
    },
    "msg": "ERROR: Ansible could not initialize the preferred locale: unsupported locale setting",
    "rc": 1,
    "stderr": "ERROR: Ansible could not initialize the preferred locale: unsupported locale setting\n",
    "stderr_lines": [
        "ERROR: Ansible could not initialize the preferred locale: unsupported locale setting"
    ],
    "stdout": "",
    "stdout_lines": []
}
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


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

If these files are incorrect, 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/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @russoz
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Hi @laidbackware thanks for reporting.

I believe your problem happens because:
https://github.com/ansible-collections/community.general/blob/main/plugins/modules/ansible_galaxy_install.py#L229

The problem we have there is the need to parse the output of `ansible-galaxy` consistently, regardless of the user's locale.

I don't recall exactly why, but `ansible-galaxy` *requires* the locale to be UTF-8, and `C.UTF-8` did not work on some of the supported systems, so `en_US. UTF-8` was the best compromise we could make at time.

The module requires that locale to be installed in the target system. I will add this to the module's documentation.

@russoz From my googling I found that that used to be the case for `ansible-galaxy`, but seems that is no longer the case, as my machine didn't have `en_US.UTF-8`, but I was still able to use the CLI with issue. After installing the full en language pack I was able to get past the error. My machine was a standard Ubuntu 20.04 (upgraded to 22.04) server install, so it seems it's common to not get `en_US.UTF-8` OOB unless you set the locale of the US.

Thanks for clearing that up so fast.
Maybe we should adjust the code to look what the current locale is; if it is `somethingsomething.UTF-8`, use that one, otherwise use `en_US.UTF-8`.
> Maybe we should adjust the code to look what the current locale is; if it is `somethingsomething.UTF-8`, use that one, otherwise use `en_US.UTF-8`.

That would pose a problem. The whole point of forcing a specific locale is to get a textually consistent response from the command, so that the module can parse it. We usually go with `C`, but in this case there was something that *required* UTF-8, so we tried `C.UTF-8` but some of the systems in the CI would not cope with that.

If the module was to accept any locale ending in `. UTF-8`, it would have to parse text in different languages, which is not practical.

So far it looks like a documentation issue to me. 
If we could get `ansible-galaxy` to stop using UTF-8 in its output, we could go back to using the universal locale `C`.
> If the module was to accept any locale ending in `. UTF-8`, it would have to parse text in different languages, which is not practical.

Then maybe accept all English locales ending in `.UTF-8` :-) But yeah, this is also a not really perfect solution... Using `C` would be perfect, but ansible-galaxy doesn't do that...

Anyway, I agree that we should document this better.
Hi @laidbackware could you please give it a try on the changes in the PR, see if it works.
@russoz I've just checked and all looks good! Many thanks!

```sh
matt@harold:~/workspace/lab/tap-on-kind$ locale -a
C
C.utf8
en_GB.utf8
POSIX

matt@harold:~/workspace/lab/tap-on-kind$ ansible-playbook prepare.yml --tags galaxy-collections
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not
match 'all'

PLAY [localhost] **************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************
ok: [localhost]

TASK [prepare/ansible-galaxy-collections : Install Ansible collection kubernetes.core] ****************************
changed: [localhost]

PLAY RECAP ********************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
```
Awesome! 

Resolving patch:
diff --git a/plugins/modules/ansible_galaxy_install.py b/plugins/modules/ansible_galaxy_install.py
--- a/plugins/modules/ansible_galaxy_install.py
+++ b/plugins/modules/ansible_galaxy_install.py
@@ -20,6 +20,10 @@
   - >
     B(Ansible 2.9/2.10): The C(ansible-galaxy) command changed significantly between Ansible 2.9 and
     ansible-base 2.10 (later ansible-core 2.11). See comments in the parameters.
+  - >
+    The module will try and run using the C(C.UTF-8) locale.
+    If that fails, it will try C(en_US.UTF-8).
+    If that one also fails, the module will fail.
 requirements:
   - Ansible 2.9, ansible-base 2.10, or ansible-core 2.11 or newer
 options:
@@ -185,7 +189,7 @@
 import re
 
 from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt as fmt
-from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper
+from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper, ModuleHelperException
 
 
 class AnsibleGalaxyInstall(ModuleHelper):
@@ -226,11 +230,17 @@ class AnsibleGalaxyInstall(ModuleHelper):
         version=fmt.as_bool("--version"),
         name=fmt.as_list(),
     )
-    force_lang = "en_US.UTF-8"
-    check_rc = True
+
+    def _make_runner(self, lang):
+        return CmdRunner(self.module, command=self.command, arg_formats=self.command_args_formats, force_lang=lang, check_rc=True)
 
     def _get_ansible_galaxy_version(self):
+        class UnsupportedLocale(ModuleHelperException):
+            pass
+
         def process(rc, out, err):
+            if (rc != 0 and "unsupported locale setting" in err) or (rc == 0 and "cannot change locale" in err):
+                raise UnsupportedLocale(msg=err)
             line = out.splitlines()[0]
             match = self._RE_GALAXY_VERSION.match(line)
             if not match:
@@ -239,12 +249,18 @@ def process(rc, out, err):
             version = tuple(int(x) for x in version.split('.')[:3])
             return version
 
-        with self.runner("version", check_rc=True, output_process=process) as ctx:
-            return ctx.run(version=True)
+        try:
+            runner = self._make_runner("C.UTF-8")
+            with runner("version", check_rc=False, output_process=process) as ctx:
+                return runner, ctx.run(version=True)
+        except UnsupportedLocale as e:
+            runner = self._make_runner("en_US.UTF-8")
+            with runner("version", check_rc=True, output_process=process) as ctx:
+                return runner, ctx.run(version=True)
 
     def __init_module__(self):
-        self.runner = CmdRunner(self.module, command=self.command, arg_formats=self.command_args_formats, force_lang=self.force_lang)
-        self.ansible_version = self._get_ansible_galaxy_version()
+        # self.runner = CmdRunner(self.module, command=self.command, arg_formats=self.command_args_formats, force_lang=self.force_lang)
+        self.runner, self.ansible_version = self._get_ansible_galaxy_version()
         if self.ansible_version < (2, 11) and not self.vars.ack_min_ansiblecore211:
             self.module.deprecate(
                 "Support for Ansible 2.9 and ansible-base 2.10 is being deprecated. "
@@ -339,11 +355,12 @@ def process(rc, out, err):
             self._setup210plus()
         with self.runner("type galaxy_cmd force no_deps dest requirements_file name", output_process=process) as ctx:
             ctx.run(galaxy_cmd="install")
+            if self.verbosity > 2:
+                self.vars.set("run_info", ctx.run_info)
 
 
 def main():
-    galaxy = AnsibleGalaxyInstall()
-    galaxy.run()
+    AnsibleGalaxyInstall.execute()
 
 
 if __name__ == '__main__':


Test patch:
diff --git a/tests/integration/targets/ansible_galaxy_install/tasks/main.yml b/tests/integration/targets/ansible_galaxy_install/tasks/main.yml
--- a/tests/integration/targets/ansible_galaxy_install/tasks/main.yml
+++ b/tests/integration/targets/ansible_galaxy_install/tasks/main.yml
@@ -10,7 +10,7 @@
     name: netbox.netbox
   register: install_c0
 
-- name: Assert collection was installed
+- name: Assert collection netbox.netbox was installed
   assert:
     that:
     - install_c0 is changed
@@ -34,7 +34,7 @@
     name: ansistrano.deploy
   register: install_r0
 
-- name: Assert collection was installed
+- name: Assert collection ansistrano.deploy was installed
   assert:
     that:
     - install_r0 is changed
@@ -52,7 +52,7 @@
     - install_r1 is not changed
 
 ###################################################
-- name:
+- name: Set requirements file path
   set_fact:
     reqs_file: '{{ remote_tmp_dir }}/reqs.yaml'