Repository: ansible-collections/community.general

Issue:
zypper remove ignored which LANG=fr_FR
##### SUMMARY
The `zypper` module does not remove packages with `state: absent` when the local is set something other than 'en_US'.

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
- plugins/modules/packaging/os/zypper.py

##### ANSIBLE VERSION
```paste below
ansible 2.9.14
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/xenlo/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.8/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.8.6 (default, Sep 25 2020, 00:00:00) [GCC 10.2.1 20200723 (Red Hat 10.2.1-1)]
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```paste below
DEFAULT_STDOUT_CALLBACK(/etc/ansible/ansible.cfg) = yaml
DEFAULT_VAULT_PASSWORD_FILE(env: ANSIBLE_VAULT_PASSWORD_FILE) = /home/xenlo/.ansible/vault_keyring
```

##### OS / ENVIRONMENT
*Controler*:
- Fedora 32
- LANG: fr_BE.UTF-8

*Target host*:
- SLES 15
- LANG: POSIX

##### STEPS TO REPRODUCE

On a target SLES (or openSUSE) host ensure that a rpm package is installed with zypper:
```shell
target:~ # zypper install libwbclient0
[…]
target:~ # zypper search libwbclient0
[…]
S | Nom          | Résumé                    | Type  
--+--------------+---------------------------+-------
i | libwbclient0 | Samba libwbclient Library | paquet
```

And from there try to remove from an Ansible controle with an other LANG than 'en_US' (either with `ansible` ad-hoc or `ansible-playbook):
```yaml
controler:~/Projects/344$ LANG=fr_FR.UTF8  ansible -i inventories/ target -m zypper -a "name=libwbclient0 state=absent" -u root
target | SUCCESS => {​​​​​​​
    "changed": false,
    "name": [
        "libwbclient0"
    ],
    "rc": 0,
    "state": "absent",
    "update_cache": false
}​​​​​​​
```
You get `"changed": false,` and `"state": "absent",`.

##### EXPECTED RESULTS
I expect to have the package removed from the target and as output "changed: true". Just like if I run the same ansible command but with 'en_US'. 

```shell
controler:~/Projects/344$ LANG=en_US.UTF8 ansible -i inventories/ target -m zypper -a "name=colordiff state=absent" -u root
target | CHANGED => {
    "changed": true,
    "cmd": [
        "/usr/bin/zypper",
        "--quiet",
        "--non-interactive",
        "--xmlout",
        "remove",
        "--type",
        "package",
        "colordiff"
    ],
    "name": [
        "colordiff"
    ],
    "rc": 0,
    "state": "absent",
    "stderr": "",
    "stderr_lines": [],
    "stdout": "<?xml version='1.0'?>\n<stream>\n<install-summary download-size=\"0\" space-usage-diff=\"-54086\">\n<to-remove>\n<solvable type=\"package\" name=\"colordiff\" edition=\"1.0.18-bp150.2.4\" arch=\"noarch\" summary=\"Colour-highlighted &apos;diff&apos; output\">\n<description>The Perl script colordiff is a wrapper for &apos;diff&apos; and produces the same\noutput but with pretty &apos;syntax&apos; highlighting. Colour schemes can be\ncustomized.</description></solvable>\n</to-remove>\n</install-summary>\n<prompt id=\"0\">\n<text>Continue?</text>\n<option default=\"1\" value=\"y\" desc=\"Yes, accept the summary and proceed with installation/removal of packages.\"/>\n<option value=\"n\" desc=\"No, cancel the operation.\"/>\n<option value=\"v\" desc=\"Toggle display of package versions.\"/>\n<option value=\"a\" desc=\"Toggle display of package architectures.\"/>\n<option value=\"r\" desc=\"Toggle display of repositories from which the packages will be installed.\"/>\n<option value=\"m\" desc=\"Toggle display of package vendor names.\"/>\n<option value=\"d\" desc=\"Toggle between showing all details and as few details as possible.\"/>\n<option value=\"g\" desc=\"View the summary in pager.\"/>\n</prompt>\n</stream>\n",
    "stdout_lines": [
        "<?xml version='1.0'?>",
        "<stream>",
        "<install-summary download-size=\"0\" space-usage-diff=\"-54086\">",
        "<to-remove>",
        "<solvable type=\"package\" name=\"colordiff\" edition=\"1.0.18-bp150.2.4\" arch=\"noarch\" summary=\"Colour-highlighted &apos;diff&apos; output\">",
        "<description>The Perl script colordiff is a wrapper for &apos;diff&apos; and produces the same",
        "output but with pretty &apos;syntax&apos; highlighting. Colour schemes can be",
        "customized.</description></solvable>",
        "</to-remove>",
        "</install-summary>",
        "<prompt id=\"0\">",
        "<text>Continue?</text>",
        "<option default=\"1\" value=\"y\" desc=\"Yes, accept the summary and proceed with installation/removal of packages.\"/>",
        "<option value=\"n\" desc=\"No, cancel the operation.\"/>",
        "<option value=\"v\" desc=\"Toggle display of package versions.\"/>",
        "<option value=\"a\" desc=\"Toggle display of package architectures.\"/>",
        "<option value=\"r\" desc=\"Toggle display of repositories from which the packages will be installed.\"/>",
        "<option value=\"m\" desc=\"Toggle display of package vendor names.\"/>",
        "<option value=\"d\" desc=\"Toggle between showing all details and as few details as possible.\"/>",
        "<option value=\"g\" desc=\"View the summary in pager.\"/>",
        "</prompt>",
        "</stream>"
    ],
    "update_cache": false
```
##### ACTUAL RESULTS
Output with `-vvvv` in [this gist](https://gist.github.com/xenlo/bd49aa660a32b4df9a64fa94f61e3d71).



Discussion:
Files identified in the description:
* [`plugins/modules/packaging/os/zypper.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/packaging/os/zypper.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 @AnderEnder @alxgu @andytom @commel @dcermak @evrardjp @lrupp @toabctl
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
According to my colleague's investigations, the `zypper` module use the xml output of zypper to check the state of a package. It looks for attribute '_name_' when in French it get translated form: '_nom_'.

- **en_US**: `<solvable status="installed" name="libwbclient0" kind="package" edition="4.7.6+git.54.6e3276c9872-2.47" arch="x86_64" repository="PTS-SLES-15_basesystem-x86_64"/>`
- **fr_FR**: `<solvable status="installed" nom="libwbclient0" kind="paquet" edition="4.7.6+git.54.6e3276c9872-2.47" arch="x86_64" dépôt="PTS-SLES-15_basesystem-x86_64"/>`

Which explains the strange behaviour. 

Is it possible to enforce en_US LANG on the module execution on the target host to ensure that the output check catch what it expect to catch?
I'd rather set it to `C`, as the `en_US` locale is not available everywhere. It should be easy to do that, similar to here: https://github.com/ansible-collections/community.general/blob/main/plugins/modules/cloud/atomic/atomic_image.py#L93

In any case, 'translating' XML property names looks like a bug to me. This should probably also be reported as a bug in zypper.
Indeed, I will look on Monday if I can manage to propose PR. And also have look where I can open an issue upstream to escalate the xml proprties translation. 
i can't reproduce on opensuse leap 15.2 (don't have SLES installed) , but probably the following patch fixes the issue? 
```
diff --git a/plugins/modules/packaging/os/zypper.py b/plugins/modules/packaging/os/zypper.py
index 5997cfa4..f6c623ef 100644
--- a/plugins/modules/packaging/os/zypper.py
+++ b/plugins/modules/packaging/os/zypper.py
@@ -515,6 +515,8 @@ def main():
         supports_check_mode=True
     )
 
+    module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C')
+
     name = module.params['name']
     state = module.params['state']
     update_cache = module.params['update_cache']
```
I just give it a try and was able to reporduce on a OpenSUSE 15.1.
So indeed the fix works. Thanks @felixfontein and @mator.

I created a PR with that change: #1175. Next task, escalate the topic upstream. 

Thanks @xenlo @felixfontein and @mator !
@evrardjp, you're welcome.

As I see that you works for Suse, maybe you can advice me where to open a discussion about the translation of attribute in xml output?
I assume that https://github.com/openSUSE/zypper is a good start?
I work for SUSE, but I don't work on zypper. Yes, that would be one place. The alternative would be the bugzilla. Let's start with the github issue and file a bugzilla issue if necessary.
Thanks @evrardjp.

FYI: openSUSE/zypper#361
still can't reproduce with opensuse 15.2 . Set system locale to `fr_FR.UTF-8` via yast : 
```
opensuse$ cat /etc/locale.conf
LANG=fr_FR.UTF-8

archlinux$ ssh opensuse locale
LANG=fr_FR.UTF-8
LC_CTYPE="fr_FR.UTF-8"
LC_NUMERIC="fr_FR.UTF-8"
LC_TIME="fr_FR.UTF-8"
LC_COLLATE="fr_FR.UTF-8"
LC_MONETARY="fr_FR.UTF-8"
LC_MESSAGES="fr_FR.UTF-8"
LC_PAPER="fr_FR.UTF-8"
LC_NAME="fr_FR.UTF-8"
LC_ADDRESS="fr_FR.UTF-8"
LC_TELEPHONE="fr_FR.UTF-8"
LC_MEASUREMENT="fr_FR.UTF-8"
LC_IDENTIFICATION="fr_FR.UTF-8"
LC_ALL=
```
even set `ROOT_USES_LANG` : 
```
opensuse:~> grep ROOT_U /etc/sysconfig/language 
# Root uses this variable only if ROOT_USES_LANG is set to "yes".
# Again, ROOT_USES_LANG must be set to "yes", if an effect on the superuser
ROOT_USES_LANG="yes"
```
first check:
```
$ ansible opensuse -m setup -a 'filter=ansible_env' | grep LANG
            "LANG": "fr_FR.utf8",
```
test with `mtr` package installed/removed:
```
opensuse:~> rpm -q mtr
le paquet mtr n'est pas installé

archlinux$ ansible opensuse -b -m zypper -a 'name=mtr state=absent'
opensuse | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.6"
    },
    "changed": false,
    "name": [
        "mtr"
    ],
    "rc": 0,
    "state": "absent",
    "update_cache": false
}

archlinux$ ansible opensuse -b -m zypper -a 'name=mtr state=installed'
opensuse | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.6"
    },
    "changed": true,
    "cmd": [
        "/usr/bin/zypper",
        "--quiet",
        "--non-interactive",
        "--xmlout",
        "install",
        "--type",
        "package",
        "--auto-agree-with-licenses",
        "--no-recommends",
        "--",
        "+mtr"
    ],
    "name": [
        "mtr"
    ],
    "rc": 0,
    "state": "installed",
    "stderr": "",
    "stderr_lines": [],
    "stdout": "<?xml version='1.0'?>\n<stream>\n<install-summary download-size=\"80828\" space-usage-diff=\"181267\" packages-to-change=\"1\">\n<to-install>\n<solvable type=\"package\" name=\"mtr\" edition=\"0.92-lp152.4.3\" arch=\"x86_64\" summary=\"Ping and Traceroute Network Diagnostic Tool\">\n<description>Mtr is a network diagnostic tool that combines Ping and Traceroute into\none program. This package contains the mtr version with an ncurses\ninterface, in other words, the text mode version is usable in a shell\n(telnet or SSH session, for example).\n\nFind the graphical version in the mtr-gtk package.</description></solvable>\n</to-install>\n</install-summary>\n<prompt id=\"0\">\n<text>Continue?</text>\n<option default=\"1\" value=\"y\" desc=\"Yes, accept the summary and proceed with installation/removal of packages.\"/>\n<option value=\"n\" desc=\"No, cancel the operation.\"/>\n<option value=\"v\" desc=\"Toggle display of package versions.\"/>\n<option value=\"a\" desc=\"Toggle display of package architectures.\"/>\n<option value=\"r\" desc=\"Toggle display of repositories from which the packages will be installed.\"/>\n<option value=\"m\" desc=\"Toggle display of package vendor names.\"/>\n<option value=\"d\" desc=\"Toggle between showing all details and as few details as possible.\"/>\n<option value=\"g\" desc=\"View the summary in pager.\"/>\n</prompt>\n<download url=\"http://download.opensuse.org/distribution/leap/15.2/repo/oss/x86_64/mtr-0.92-lp152.4.3.x86_64.rpm\" percent=\"-1\" rate=\"-1\"/>\n<download url=\"http://download.opensuse.org/distribution/leap/15.2/repo/oss/x86_64/mtr-0.92-lp152.4.3.x86_64.rpm\" rate=\"-1\" done=\"1\"/>\n</stream>\n",
    "stdout_lines": [
        "<?xml version='1.0'?>",
        "<stream>",
        "<install-summary download-size=\"80828\" space-usage-diff=\"181267\" packages-to-change=\"1\">",
        "<to-install>",
        "<solvable type=\"package\" name=\"mtr\" edition=\"0.92-lp152.4.3\" arch=\"x86_64\" summary=\"Ping and Traceroute Network Diagnostic Tool\">",
        "<description>Mtr is a network diagnostic tool that combines Ping and Traceroute into",
        "one program. This package contains the mtr version with an ncurses",
        "interface, in other words, the text mode version is usable in a shell",
        "(telnet or SSH session, for example).",
        "",
        "Find the graphical version in the mtr-gtk package.</description></solvable>",
        "</to-install>",
        "</install-summary>",
        "<prompt id=\"0\">",
        "<text>Continue?</text>",
        "<option default=\"1\" value=\"y\" desc=\"Yes, accept the summary and proceed with installation/removal of packages.\"/>",
        "<option value=\"n\" desc=\"No, cancel the operation.\"/>",
        "<option value=\"v\" desc=\"Toggle display of package versions.\"/>",
        "<option value=\"a\" desc=\"Toggle display of package architectures.\"/>",
        "<option value=\"r\" desc=\"Toggle display of repositories from which the packages will be installed.\"/>",
        "<option value=\"m\" desc=\"Toggle display of package vendor names.\"/>",
        "<option value=\"d\" desc=\"Toggle between showing all details and as few details as possible.\"/>",
        "<option value=\"g\" desc=\"View the summary in pager.\"/>",
        "</prompt>",
        "<download url=\"http://download.opensuse.org/distribution/leap/15.2/repo/oss/x86_64/mtr-0.92-lp152.4.3.x86_64.rpm\" percent=\"-1\" rate=\"-1\"/>",
        "<download url=\"http://download.opensuse.org/distribution/leap/15.2/repo/oss/x86_64/mtr-0.92-lp152.4.3.x86_64.rpm\" rate=\"-1\" done=\"1\"/>",
        "</stream>"
    ],
    "update_cache": false
}

opensuse:~> rpm -q mtr
mtr-0.92-lp152.4.3.x86_64
```
@mator, the issue occurs the other way around. So having the ansible controller (so the archlinux in your detailed case here above). The locale config on the openSUSE target does not matter as it will open a session with the locale from the source of the ssh connection.

Resolving patch:
diff --git a/plugins/modules/packaging/os/zypper.py b/plugins/modules/packaging/os/zypper.py
--- a/plugins/modules/packaging/os/zypper.py
+++ b/plugins/modules/packaging/os/zypper.py
@@ -515,6 +515,8 @@ def main():
         supports_check_mode=True
     )
 
+    module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C')
+
     name = module.params['name']
     state = module.params['state']
     update_cache = module.params['update_cache']


Test patch: