Repository: ansible-collections/community.general

Issue:
redhat_subscription module  fails on RHEL 7.3
### Summary

I tried to register a RHEL 7.3 VM using `redhat_subscription` module, the module threw an error. It seems that the module is trying to stop the "rhsm" service, but this service is not present on RHEL 7.3

### Issue Type

Bug Report

### Component Name

redhat_subscription

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.16.0rc1]
  config file = /home/jburati/gi/va/r73/ansible.cfg
  configured module search path = ['/home/jburati/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.12/site-packages/ansible
  ansible collection location = /home/jburati/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.12.0 (main, Oct  2 2023, 00:00:00) [GCC 13.2.1 20230918 (Red Hat 13.2.1-3)] (/usr/bin/python3)
  jinja version = 3.1.2
  libyaml = True
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general

# /usr/lib/python3.12/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 7.5.0  

# /usr/share/ansible/collections/ansible_collections
Collection        Version
----------------- -------
community.general 8.0.0  

```


### Configuration

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


### OS / Environment

RHEL 7.3

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)

```


### Expected Results

I expected the `redhat_subscription` module to register the VM without errors.

### Actual Results

```console (paste below)
fatal: [r73jbu-vm1]: FAILED! => {"changed": false, "cmd": "systemctl stop rhsm", "msg": "Failed to stop rhsm.service: Unit rhsm.service not loaded.", "rc": 5, "stderr": "Failed to stop rhsm.service: Unit rhsm.service not loaded.\n", "stderr_lines": ["Failed to stop rhsm.service: Unit rhsm.service not loaded."], "stdout": "", "stdout_lines": []}  ```
```


### Code of Conduct

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


Discussion:
Files identified in the description:
* [`plugins/modules/redhat_subscription.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/redhat_subscription.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 @cnsnyder @ptoscano
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
It looks like the `rhsm` service was introduced later on in the RHEL 7 lifecycle -- OK, should not be complicated to fix.

Please note that RHEL 7.3 is EOL, even if we consider very long EUS/AUS/etc maintenance streams.
Thanks @ptoscano, I just wrote a quick commit to check for the minor version before trying to stop the service but it looks like there is more to it because I am getting another error:
```
fatal: [r73jbu-vm1]: FAILED! => {"changed": false, "msg": "Failed to register with 'None': org.freedesktop.DBus.Error.ServiceUnknown: The name com.redhat.RHSM1 was not provided by any .service files"}
```
It looks like the D-Bus interface was introduced in 7.4, so most likely the whole registration with D-Bus needs to be versioned.

I'll do a different PR for this.

Resolving patch:
diff --git a/plugins/modules/redhat_subscription.py b/plugins/modules/redhat_subscription.py
--- a/plugins/modules/redhat_subscription.py
+++ b/plugins/modules/redhat_subscription.py
@@ -28,7 +28,7 @@
       process listing on the system. Due to limitations of the D-Bus interface of C(rhsm),
       the module will I(not) use D-Bus for registration when trying either to register
       using O(token), or when specifying O(environment), or when the system is old
-      (typically RHEL 6 and older).
+      (typically RHEL 7 older than 7.4, RHEL 6, and older).
     - In order to register a system, subscription-manager requires either a username and password, or an activationkey and an Organization ID.
     - Since 2.5 values for O(server_hostname), O(server_insecure), O(rhsm_baseurl),
       O(server_proxy_hostname), O(server_proxy_port), O(server_proxy_user) and
@@ -414,6 +414,30 @@ def is_registered(self):
         else:
             return False
 
+    def _has_dbus_interface(self):
+        """
+        Checks whether subscription-manager has a D-Bus interface.
+
+        :returns: bool -- whether subscription-manager has a D-Bus interface.
+        """
+
+        def str2int(s, default=0):
+            try:
+                return int(s)
+            except ValueError:
+                return default
+
+        distro_id = distro.id()
+        distro_version = tuple(str2int(p) for p in distro.version_parts())
+
+        # subscription-manager in any supported Fedora version has the interface.
+        if distro_id == 'fedora':
+            return True
+        # Any other distro: assume it is EL;
+        # the D-Bus interface was added to subscription-manager in RHEL 7.4.
+        return (distro_version[0] == 7 and distro_version[1] >= 4) or \
+            distro_version[0] >= 8
+
     def _can_connect_to_dbus(self):
         """
         Checks whether it is possible to connect to the system D-Bus bus.
@@ -457,7 +481,8 @@ def register(self, was_registered, username, password, token, auto_attach, activ
         # of rhsm, so always use the CLI in that case;
         # also, since the specified environments are names, and the D-Bus APIs
         # require IDs for the environments, use the CLI also in that case
-        if not token and not environment and self._can_connect_to_dbus():
+        if (not token and not environment and self._has_dbus_interface() and
+           self._can_connect_to_dbus()):
             self._register_using_dbus(was_registered, username, password, auto_attach,
                                       activationkey, org_id, consumer_type,
                                       consumer_name, consumer_id,


Test patch:
diff --git a/tests/unit/plugins/modules/test_redhat_subscription.py b/tests/unit/plugins/modules/test_redhat_subscription.py
--- a/tests/unit/plugins/modules/test_redhat_subscription.py
+++ b/tests/unit/plugins/modules/test_redhat_subscription.py
@@ -29,6 +29,8 @@ def patch_redhat_subscription(mocker):
                  return_value='/testbin/subscription-manager')
     mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.Rhsm._can_connect_to_dbus',
                  return_value=False)
+    mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.Rhsm._has_dbus_interface',
+                 return_value=False)
     mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.getuid',
                  return_value=0)