opkg - `_package_in_desired_state` does not handle properly `opkg list_installed` output
Summary
Tried to integrate community.general.opkg for the first time in our ansible stack. We have some embedded devices that uses opkg for package management. My task is very trivial.
---
- name: Install vim - opkg
community.general.opkg:
name: "vim=9.0.1211-r0"
This results in
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible_collections.community.general.plugins.module_utils.mh.exceptions.ModuleHelperException
fatal: [<censored_hostname>]: FAILED! => {
"changed": false,
"output": {
"diff": {}
},
"vars": {}
}
MSG:
failed to install vim=9.0.1211-r0
The package is installed on the remote host
root@XXXXXXXX:~# opkg list_installed vim
vim - 9.0.1211-r0
After installing the package, the modules runs the above command to confirm that it has been installed properly. This part is broken. After diging a bit, the culprit is that function:
https://github.com/ansible-collections/community.general/blob/fd811df414095c7c268e09218520dc9db03da1e8/plugins/modules/opkg.py#L177-L181
In my case: out="vim - 9.0.1211-r0\n" So when it executes has_package = out.startswith(name + " - %s" % ("" if not version else (version + " "))) - It will always return False, because there is no whitespace at the end of my out. I am not sure what is the rationale behind this condition but it clearly does not fit the out from my device. It seems like an unhandled scenario.
I fixed locally by removing the extra whitespace after version in the condtion:
has_package = out.startswith(name + " - %s" % ("" if not version else version))
I'm not sure if that approach is fine, I can't find history about that extra whitespace.
Issue Type
Bug Report
Component Name
community.general.opkg
Ansible Version
$ ansible --version
ansible [core 2.16.9]
python version = 3.10.9 (main, May 1 2024, 16:32:48)
jinja version = 3.1.4
libyaml = True
Community.general Version
$ ansible-galaxy collection list community.general
Collection Version
----------------- -------
community.general 9.2.0
Configuration
Too sensitive environment. sorry. I don't think it's helpful for that issue.
OS / Environment
Controller: Ubuntu 22.04 LTS
Target: OS: PetaLinux 2023.1 kernel: Linux k26-lunx-tx 6.1.30-xilinx-v2023.1 #1 SMP Fri Jun 30 09:49:44 UTC 2023 aarch64 GNU/Linux opkg: version 0.6.1 (libsolv 0.7.22)
Steps to Reproduce
I think the description is self explanatory. The target device that we use is rather exotic and unfortunatelly I think it might be difficult to reproduce.
Expected Results
The package should show as "installed" and the module should not error out because of unhandled out
Actual Results
No response
Code of Conduct
- [X] I agree to follow the Ansible Code of Conduct
Files identified in the description:
If these files are incorrect, please update the component name section of the description or use the !component bot command.
cc @skinp click here for bot help
The condition
has_package = out.startswith(name + " - %s" % ("" if not version else version))
is not correct either, since it thinks 1.1 is installed if 1.10 is installed, for example, since 1.10 starts with 1.1. I guess the output format of opkg list_installed changed, or depends on the concrete version or whatever...
I would probably adjust the code to split into lines, and check whether the first line starts with foo - version , or equals foo - version. That way both output formats are covered.
CC @joergho who implemented version support in #5688 and might have some insight here as well...
Thans for reporting the bug @akire0ne and for the analysis @felixfontein
If the Yocto version of opkg is used, the following code is executed in the end: print_pkg. This means that depending of the configuration, the output can be any of the following:
NAME - VERSION
NAME - VERSION - SIZE
NAME - VERSION - SIZE - DESCRIPTION
NAME - VERSION - DESCRIPTION
I'd therefore suggest to split the string by -:
def _package_in_desired_state(self, name, want_installed, version=None):
dummy, out, dummy = self.runner("state package").run(state="query", package=name)
splitted = out.split(" - ")
has_package = (splitted[0] == name) and (version is None or splitted[1] == version)
return want_installed == has_package
I have done minimal testing in Python console but not yet using the whole ansible stack.
If you simply split by " - ", and the input is "NAME - VERSION\n", splitted[1] ends up as "VERSION\n" with the newline added.
How about using
lines = out.splitlines()
splitted = lines[0].split(" - ") if lines else [None, None]
instead of splitted = out.split(" - ")?
How about using (...)
Yes, this would work!