Repository: ansible-collections/community.general

Issue:
Failed to install local rpm packages with apt_rpm in AltLinux
### Summary

`apt_rpm` module shows that it failed to install locally placed rpm package but when I check the list of installed packages with `rpm -qa` I see that package has been installed.

### Issue Type

Bug Report

### Component Name

apt_rpm

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.11.12] 
  config file = /home/administrator/ansible/ansible.cfg
  configured module search path = ['/home/administrator/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.7/dist-packages/ansible
  ansible collection location = /home/administrator/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.7.3 (default, Sep  6 2022, 14:17:44) [GCC 8.3.0]
  jinja version = 2.10
  libyaml = True
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
# /usr/local/lib/python3.7/dist-packages/ansible_collections
Collection        Version
----------------- -------
community.general 3.8.3
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
DEFAULT_FORKS(/home/administrator/ansible/ansible.cfg) = 15
HOST_KEY_CHECKING(/home/administrator/ansible/ansible.cfg) = False
RETRY_FILES_ENABLED(/home/administrator/ansible/ansible.cfg) = False
```


### OS / Environment

NAME="ALT Server"
VERSION="10.1"
ID=altlinux
VERSION_ID=10.1
PRETTY_NAME="ALT Server 10.1 (Mendelevium)"
ANSI_COLOR="1;33"
CPE_NAME="cpe:/o:alt:server:10.1"
BUILD_ID="ALT Server 10.1"
HOME_URL="https://basealt.ru/"
BUG_REPORT_URL="https://bugs.altlinux.org/"

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
place rpm package into files/rpm/ directory inside role
$ cat tasks/main.yml
```yaml (paste below)
---
- name: copy rpm package
  copy:
    src: rpm/
    dest: /tmp/

- name: install package
  package: 
    name: /tmp/nginx.rpm
    state: present
```


### Expected Results

Ansible installs locally placed rpm packages and doesn't show an error when package succesfully installed

### Actual Results

```console (paste below)
redirecting (type: modules) ansible.builtin.apt_rpm to community.general.apt_rpm
redirecting (type: modules) ansible.builtin.apt_rpm to community.general.apt_rpm
<192.168.56.39> ESTABLISH SSH CONNECTION FOR USER: user
<192.168.56.39> SSH: EXEC sshpass -d9 ssh -o ControlMaster=auto -o ControlPersist=600s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o 'User="user"' -o ConnectTimeout=10 -o 'ControlPath="/tmp/ansible-ssh-%h-%p-%r"' 192.168.56.39 '/bin/sh -c '"'"'echo ~user && sleep 0'"'"''                                                                                 
<192.168.56.39> (0, b'/home/user\n', b"Warning: Permanently added '192.168.56.39' (ECDSA) to the list of known hosts.\r\n")
<192.168.56.39> ESTABLISH SSH CONNECTION FOR USER: user
<192.168.56.39> SSH: EXEC sshpass -d9 ssh -o ControlMaster=auto -o ControlPersist=600s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o 'User="user"' -o ConnectTimeout=10 -o 'ControlPath="/tmp/ansible-ssh-%h-%p-%r"' 192.168.56.39 '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /home/user/.ansible/tmp `"&& mkdir "` echo /home/user/.ansible/tmp/ansible-tmp-1683181379.3949084-15552-154015328928646 `" && echo ansible-tmp-1683181379.3949084-15552-154015328928646="` echo /home/user/.ansible/tmp/ansible-tmp-1683181379.3949084-15552-154015328928646 `" ) && sleep 0'"'"''                                 
<192.168.56.39> (0, b'ansible-tmp-1683181379.3949084-15552-154015328928646=/home/user/.ansible/tmp/ansible-tmp-1683181379.3949084-15552-154015328928646\n', b"Warning: Permanently added '192.168.56.39' (ECDSA) to the list of known hosts.\r\n")    
redirecting (type: modules) ansible.builtin.apt_rpm to community.general.apt_rpm
Using module file /usr/local/lib/python3.7/dist-packages/ansible_collections/community/general/plugins/modules/apt_rpm.py
<192.168.56.39> PUT /home/administrator/.ansible/tmp/ansible-local-15325zyr8bhqz/tmps1_rd064 TO /home/user/.ansible/tmp/ansible-tmp-1683181379.3949084-15552-154015328928646/AnsiballZ_apt_rpm.py                                                     
<192.168.56.39> SSH: EXEC sshpass -d9 sftp -o BatchMode=no -b - -o ControlMaster=auto -o ControlPersist=600s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o 'User="user"' -o ConnectTimeout=10 -o 'ControlPath="/tmp/ansible-ssh-%h-%p-%r"' '[192.168.56.39]'                                                                                                    
<192.168.56.39> (0, b'sftp> put /home/administrator/.ansible/tmp/ansible-local-15325zyr8bhqz/tmps1_rd064 /home/user/.ansible/tmp/ansible-tmp-1683181379.3949084-15552-154015328928646/AnsiballZ_apt_rpm.py\n', b"Warning: Permanently added '192.168.56.39' (ECDSA) to the list of known hosts.\r\n")                                                                            
<192.168.56.39> ESTABLISH SSH CONNECTION FOR USER: user
<192.168.56.39> SSH: EXEC sshpass -d9 ssh -o ControlMaster=auto -o ControlPersist=600s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o 'User="user"' -o ConnectTimeout=10 -o 'ControlPath="/tmp/ansible-ssh-%h-%p-%r"' 192.168.56.39 '/bin/sh -c '"'"'chmod u+x /home/user/.ansible/tmp/ansible-tmp-1683181379.3949084-15552-154015328928646/ /home/user/.ansible/tmp/ansible-tmp-1683181379.3949084-15552-154015328928646/AnsiballZ_apt_rpm.py && sleep 0'"'"''                             
<192.168.56.39> (0, b'', b"Warning: Permanently added '192.168.56.39' (ECDSA) to the list of known hosts.\r\n")
<192.168.56.39> ESTABLISH SSH CONNECTION FOR USER: user
<192.168.56.39> SSH: EXEC sshpass -d9 ssh -o ControlMaster=auto -o ControlPersist=600s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o 'User="user"' -o ConnectTimeout=10 -o 'ControlPath="/tmp/ansible-ssh-%h-%p-%r"' -tt 192.168.56.39 '/bin/sh -c '"'"'su  root -c '"'"'"'"'"'"'"'"'/bin/sh -c '"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-vukuknvudjoeoabgkjblvqpnwdipfhan ; /usr/bin/python3 /home/user/.ansible/tmp/ansible-tmp-1683181379.3949084-15552-154015328928646/AnsiballZ_apt_rpm.py'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"''"'"'"'"'"'"'"'"' && sleep 0'"'"'' 
Escalation succeeded
<192.168.56.39> (1, b'\r\n\r\n\r\n{"failed": true, "msg": "\'apt-get -y install /tmp/nginx.rpm\' failed: ", "invocation": {"module_args": {"name": "/tmp/nginx.rpm", "state": "present", "package": ["/tmp/nginx.rpm"], "update_cache": false}}}\r\n', b"Warning: Permanently added '192.168.56.39' (ECDSA) to the list of known hosts.\r\nShared connection to 192.168.56.39 closed.\r\n")                                                                                                                 
<192.168.56.39> Failed to connect to the host via ssh: Warning: Permanently added '192.168.56.39' (ECDSA) to the list of known hosts.                                                                                                                 
Shared connection to 192.168.56.39 closed.
<192.168.56.39> ESTABLISH SSH CONNECTION FOR USER: user
<192.168.56.39> SSH: EXEC sshpass -d9 ssh -o ControlMaster=auto -o ControlPersist=600s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o 'User="user"' -o ConnectTimeout=10 -o 'ControlPath="/tmp/ansible-ssh-%h-%p-%r"' 192.168.56.39 '/bin/sh -c '"'"'rm -f -r /home/user/.ansible/tmp/ansible-tmp-1683181379.3949084-15552-154015328928646/ > /dev/null 2>&1 && sleep 0'"'"''
<192.168.56.39> (0, b'', b"Warning: Permanently added '192.168.56.39' (ECDSA) to the list of known hosts.\r\n")
fatal: [alt10-server]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "name": "/tmp/nginx.rpm",
            "package": [
                "/tmp/nginx.rpm"
            ],
            "state": "present",
            "update_cache": false
        }
    },
    "msg": "'apt-get -y install /tmp/nginx.rpm' failed: "
}
```


### Code of Conduct

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


Discussion:
Files identified in the description:
* [`plugins/modules/apt_rpm.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/apt_rpm.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 @evgkrsk
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Probably figured the issue

After package installation the module checks if is installed - if not, the module fails.
Code uses the same name to query package presence, but if it is a local path, then `rpm -q --provides path_to_rpm` fails, which is expected.
See these lines [apt_rpm.py](https://github.com/ansible-collections/community.general/blob/e07f5e2f40f4e7afda648f2d2a0577eccab41914/plugins/modules/apt_rpm.py#L206-L213)

Builtin yum modules handles this by detecting local/remote RPM paths and extracting the package name.
See [here](https://github.com/ansible/ansible/blob/5812cabaf53a7c972c73a4e45faa57032e5c1186/lib/ansible/modules/yum.py#L1057-L1067)


Resolving patch:
diff --git a/plugins/modules/apt_rpm.py b/plugins/modules/apt_rpm.py
--- a/plugins/modules/apt_rpm.py
+++ b/plugins/modules/apt_rpm.py
@@ -28,6 +28,9 @@
   package:
     description:
       - List of packages to install, upgrade, or remove.
+      - Since community.general 8.0.0, may include paths to local C(.rpm) files
+        if O(state=installed) or O(state=present), requires C(rpm) python
+        module.
     aliases: [ name, pkg ]
     type: list
     elements: str
@@ -63,6 +66,9 @@
     type: bool
     default: false
     version_added: 6.5.0
+requirements:
+  - C(rpm) python package (rpm bindings), optional. Required if O(package)
+    option includes local files.
 author:
 - Evgenii Terechkov (@evgkrsk)
 '''
@@ -109,8 +115,22 @@
 '''
 
 import os
-
-from ansible.module_utils.basic import AnsibleModule
+import traceback
+
+from ansible.module_utils.basic import (
+    AnsibleModule,
+    missing_required_lib,
+)
+from ansible.module_utils.common.text.converters import to_native
+
+try:
+    import rpm
+except ImportError:
+    HAS_RPM_PYTHON = False
+    RPM_PYTHON_IMPORT_ERROR = traceback.format_exc()
+else:
+    HAS_RPM_PYTHON = True
+    RPM_PYTHON_IMPORT_ERROR = None
 
 APT_PATH = "/usr/bin/apt-get"
 RPM_PATH = "/usr/bin/rpm"
@@ -118,6 +138,23 @@
 UPDATE_KERNEL_ZERO = "\nTry to install new kernel "
 
 
+def local_rpm_package_name(path):
+    """return package name of a local rpm passed in.
+    Inspired by ansible.builtin.yum"""
+
+    ts = rpm.TransactionSet()
+    ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
+    fd = os.open(path, os.O_RDONLY)
+    try:
+        header = ts.hdrFromFdno(fd)
+    except rpm.error as e:
+        return None
+    finally:
+        os.close(fd)
+
+    return to_native(header[rpm.RPMTAG_NAME])
+
+
 def query_package(module, name):
     # rpm -q returns 0 if the package is installed,
     # 1 if it is not installed
@@ -131,6 +168,16 @@ def query_package(module, name):
 def query_package_provides(module, name):
     # rpm -q returns 0 if the package is installed,
     # 1 if it is not installed
+    if name.endswith('.rpm'):
+        # Likely a local RPM file
+        if not HAS_RPM_PYTHON:
+            module.fail_json(
+                msg=missing_required_lib('rpm'),
+                exception=RPM_PYTHON_IMPORT_ERROR,
+            )
+
+        name = local_rpm_package_name(name)
+
     rc, out, err = module.run_command("%s -q --provides %s" % (RPM_PATH, name))
     return rc == 0
 


Test patch: