Repository: ansible-collections/community.general

Issue:
opkg fails because package is already installed
_From @imp1sh on Jun 23, 2020 23:31_

#54284 ### SUMMARY
This task is being used:
```
- name: Install opkg packages
  opkg:
    name: tcpdump-mini, htop, zabbix-agentd, zabbix-extra-wifi
    state: present
```

 Installation fails because package is already installed. This is the error given:
```
fatal: [myhostname]: FAILED! => {"changed": false, "msg": "failed to install  htop: Installing htop (2.2.0-2) to root...\nDownloading http://downloads.openwrt.org/releases/19.07.3/packages/mipsel_24kc/packages/htop_2.2.0-2_mipsel_24kc.ipk\nConfiguring htop.\n"}
fatal: [otherhostname]: FAILED! => {"changed": false, "msg": "failed to install  htop: Package htop (2.2.0-2) installed in root is up to date.\n"}
```
When I manually invoke opkg install htop on the host, it just says:
```
root@myhostname:/etc/config# opkg install htop
Package htop (2.2.0-2) installed in root is up to date.
root@kukilala:/etc/config# echo $?
0
```


##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
opkg

##### ANSIBLE VERSION
```
ansible --version
ansible 2.9.9
  config file = /home/jochen/ansible/ansible.cfg
  configured module search path = ['/home/jochen/.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.3 (default, May 15 2020, 01:53:50) [GCC 9.3.0]

```

##### CONFIGURATION
```
ANSIBLE_SSH_ARGS(/home/myusername/ansible/ansible.cfg) = -o ServerAliveInterval=60
DEFAULT_GATHERING(/home/myusername/ansible/ansible.cfg) = implicit
DEFAULT_GATHER_SUBSET(/home/myusername/ansible/ansible.cfg) = ['all']
DEFAULT_GATHER_TIMEOUT(/home/myusername/ansible/ansible.cfg) = 15
DEFAULT_HOST_LIST(/home/myusername/ansible/ansible.cfg) = ['/home/myusername/ansible/hosts.yml']
DEFAULT_LOG_PATH(/home/myusername/ansible/ansible.cfg) = /home/myusername/ansible/ansible.log
DEFAULT_REMOTE_USER(/home/myusername/ansible/ansible.cfg) = myusername
DEFAULT_TIMEOUT(/home/myusername/ansible/ansible.cfg) = 15
INJECT_FACTS_AS_VARS(/home/myusername/ansible/ansible.cfg) = True

```

##### OS / ENVIRONMENT
ansible host is virtualized on KVM with Alpine Linux 3.12.0
There are different target systems, all OpenWrt 19.07.3.
TP-Link Archer C7 v2
Edgerouter X
Netgear WNDR3700


##### STEPS TO REPRODUCE

```
- name: Install opkg packages
  opkg:   
          name: "tcpdump-mini, htop, zabbix-agentd, zabbix-extra-wifi"
          state: present
          update_cache: yes

```


##### EXPECTED RESULTS
I would expect it to throw no error. If it's already installed well ok, but it should not end in an error.


##### ACTUAL RESULTS
```
<myhostname> (1, b'\r\n{"msg": "failed to install  htop: Package htop (2.2.0-2) installed in root is up to date.\\n", "failed": true, "invocation": {"module_args": {"name": "tcpdump-mini, htop, zabbix-agentd, zabbix-extra-wifi", "state": "present", "update_cache": true, "force": ""}}}\r\n', b'Connection to myhostname closed.\r\n')
<myhostname> Failed to connect to the host via ssh: Connection to myhostname closed.
<myhostname> ESTABLISH SSH CONNECTION FOR USER: root
<myhostname> SSH: EXEC ssh -o ServerAliveInterval=60 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=15 myhostname '/bin/sh -c '"'"'rm -f -r /root/.ansible/tmp/ansible-tmp-1592954817.423573-31454-82451051331659/ > /dev/null 2>&1 && sleep 0'"'"''
<myhostname> (0, b'', b'')
fatal: [myhostname]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "force": "",
            "name": "tcpdump-mini, htop, zabbix-agentd, zabbix-extra-wifi",
            "state": "present",
            "update_cache": true
        }
    },
    "msg": "failed to install  htop: Package htop (2.2.0-2) installed in root is up to date.\n"
}

```


_Copied from original issue: ansible/ansible#70249_


Discussion:
Files identified in the description:
* [`plugins/modules/packaging/os/opkg.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/packaging/os/opkg.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 @skinp
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Looks legit. Will be happy to review a PR for it.
-label needs_triage
needs_contributor
Haven't confirmed yet because I'd need to reinstall my test device, but I'm pretty sure this is due to the spaces in the package names list. In your example, [we'd end up querying for installed packages against " htop"](https://github.com/ansible-collections/community.general/blob/d24fc92466cc48d8dc436b80a2613635061b8f07/plugins/modules/packaging/os/opkg.py#L107) because we're not stripping white spaces. That would probably be an easy "fix" to patch this quick and dirty, but I'm not sure if this is really necessary...

It's not great, but I'd recommend simply removing spaces. [Doc mostly shows the right usage fwiw](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/packaging/os/opkg.py#L79).

I'd be hesitant to actually modify the querying logic, I feel like it'd be too easy to change behavior others may rely on...
Yes, it looks like https://github.com/ansible-collections/community.general/blob/main/plugins/modules/packaging/os/opkg.py#L190 is the cause for this problem. It's probably better to let Ansible do the splitting (and support YAML lists as input as well) by changing `name` to be `type='list'` with `elements='str'`, and change that line to `pkgs = p["name"]`
(That way people who really need leading or trailing spaces can use YAML lists to pass a string in exactly the form they want. In case anyone actually needs that.)
So what's the solution / workaround here?
Currently I cannot install packages no matter if I use a csv string  "packageA, packageB" nor with a list
- "packageA"
- "packageB"

using Ansible 2.9.21 and OpenWrt 21.02.

OK, I see I need to remove the spaces, this way it seems to work:
"packageA,packageB"
Yes, not using spaces is the current workaround. Looks like nobody wanted to create a bugfix for this module so far. I think I'll do that now.

Resolving patch:
diff --git a/plugins/modules/packaging/os/opkg.py b/plugins/modules/packaging/os/opkg.py
--- a/plugins/modules/packaging/os/opkg.py
+++ b/plugins/modules/packaging/os/opkg.py
@@ -20,19 +20,20 @@
 options:
     name:
         description:
-            - name of package to install/remove
+            - Name of package(s) to install/remove.
         aliases: [pkg]
         required: true
-        type: str
+        type: list
+        elements: str
     state:
         description:
-            - state of the package
+            - State of the package.
         choices: [ 'present', 'absent', 'installed', 'removed' ]
         default: present
         type: str
     force:
         description:
-            - opkg --force parameter used
+            - The C(opkg --force) parameter used.
         choices:
             - ""
             - "depends"
@@ -48,10 +49,10 @@
         type: str
     update_cache:
         description:
-            - update the package db first
+            - Update the package DB first.
             - Alias C(update-cache) has been deprecated and will be removed in community.general 5.0.0.
         aliases: ['update-cache']
-        default: "no"
+        default: false
         type: bool
 requirements:
     - opkg
@@ -76,7 +77,9 @@
 
 - name: Remove foo and bar
   community.general.opkg:
-    name: foo,bar
+    name:
+      - foo
+      - bar
     state: absent
 
 - name: Install foo using overwrite option forcibly
@@ -170,7 +173,7 @@ def install_packages(module, opkg_path, packages):
 def main():
     module = AnsibleModule(
         argument_spec=dict(
-            name=dict(aliases=["pkg"], required=True),
+            name=dict(aliases=["pkg"], required=True, type="list", elements="str"),
             state=dict(default="present", choices=["present", "installed", "absent", "removed"]),
             force=dict(default="", choices=["", "depends", "maintainer", "reinstall", "overwrite", "downgrade", "space", "postinstall", "remove",
                                             "checksum", "removal-of-dependent-packages"]),
@@ -187,7 +190,7 @@ def main():
     if p["update_cache"]:
         update_package_db(module, opkg_path)
 
-    pkgs = p["name"].split(",")
+    pkgs = p["name"]
 
     if p["state"] in ["present", "installed"]:
         install_packages(module, opkg_path, pkgs)


Test patch: