Repository: ansible-collections/community.general

Issue:
redhat_subscription module fails when using "force_register" on non registered system
### Summary

Hi,

Up to 6.4.0 the options force_register was not doing anything (according to [the commit e939cd07ef335e939e901c3ccc76523eab8d5e4a)](https://github.com/ansible-collections/community.general/commit/e939cd07ef335e939e901c3ccc76523eab8d5e4a#diff-b354707cac5ffce6845c0ab9c2070a9063747f9a41d64c284194ce5ca2d982c3) L551)

Now the modules starts with an unregister when the option is used. So if this option is used when the system is not registered the module fails. 

I think this should fails silently in this case. 

Regards,

### Issue Type

Bug Report

### Component Name

redhat_subscription

### Ansible Version

```console (paste below)
ansible [core 2.13.3]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.9/site-packages/ansible
  ansible collection location = /etc/ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.9.13 (main, Nov  9 2022, 13:16:24) [GCC 8.5.0 20210514 (Red Hat 8.5.0-15)]
  jinja version = 3.1.2
  libyaml = True
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
Collection        Version
----------------- -------
community.general 6.5.0  
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
ANSIBLE_NOCOWS(/etc/ansible/ansible.cfg) = True
CACHE_PLUGIN(/etc/ansible/ansible.cfg) = memory
COLLECTIONS_PATHS(/etc/ansible/ansible.cfg) = ['/etc/ansible/collections', '/usr/share/ansible/collections']
DEFAULT_ACTION_PLUGIN_PATH(/etc/ansible/ansible.cfg) = ['/usr/lib/python3.9/site-packages/ansible/plugins/action']
DEFAULT_CALLBACK_PLUGIN_PATH(/etc/ansible/ansible.cfg) = ['/usr/lib/python3.9/site-packages/ansible/plugins/callback'>
DEFAULT_CONNECTION_PLUGIN_PATH(/etc/ansible/ansible.cfg) = ['/usr/lib/python3.9/site-packages/ansible/plugins/connect>
DEFAULT_FILTER_PLUGIN_PATH(/etc/ansible/ansible.cfg) = ['/usr/lib/python3.9/site-packages/ansible/plugins/filter']
DEFAULT_FORKS(/etc/ansible/ansible.cfg) = 5
DEFAULT_GATHERING(/etc/ansible/ansible.cfg) = implicit
DEFAULT_LOOKUP_PLUGIN_PATH(/etc/ansible/ansible.cfg) = ['/usr/lib/python3.9/site-packages/ansible/plugins/lookup', '/>
DEFAULT_MANAGED_STR(/etc/ansible/ansible.cfg) = Ansible managed: {file}
DEFAULT_POLL_INTERVAL(/etc/ansible/ansible.cfg) = 15
DEFAULT_ROLES_PATH(/etc/ansible/ansible.cfg) = ['/etc/ansible/roles', '/usr/share/ansible/roles']
DEFAULT_TIMEOUT(/etc/ansible/ansible.cfg) = 10
DEFAULT_TRANSPORT(/etc/ansible/ansible.cfg) = smart
DEFAULT_VARS_PLUGIN_PATH(/etc/ansible/ansible.cfg) = ['/usr/lib/python3.9/site-packages/ansible/plugins/vars']
GALAXY_SERVER_LIST(/etc/ansible/ansible.cfg) = ['artifactory']
```


### OS / Environment

RHEL 8.7

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
       redhat_subscription:
         state: present
         activationkey: "{{ sat_activationkey }}"
         force_register: yes
         org_id: xxxxxxx
```

On an unregistered system


### Expected Results

force_register should not fail when the system is not registered and just ignore the fail or skip the unregister step in this case.

### Actual Results

```console (paste below)
TASK [satellite : Subcription manager register] **********************************************************************
task path: /etc/ansible/roles/satellite/tasks/RedHat_8.yml:71
redirecting (type: modules) ansible.builtin.redhat_subscription to community.general.redhat_subscription
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: root
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /var/tmp/.ansible/ `"&& mkdir "` echo /var/tmp/.ansible/ansible-tmp-1680017600.1291587-8982-142603344664991 `" && echo ansible-tmp-1680017600.1291587-8982-142603344664991="` echo /var/tmp/.ansible/ansible-tmp-1680017600.1291587-8982-142603344664991 `" ) && sleep 0'
redirecting (type: modules) ansible.builtin.redhat_subscription to community.general.redhat_subscription
Using module file /etc/ansible/collections/ansible_collections/community/general/plugins/modules/redhat_subscription.py
<localhost> PUT /root/.ansible/tmp/ansible-local-8637enxlydxo/tmp6vn0vrs1 TO /tmp/.ansible/ansible-tmp-1680017600.1291587-8982-142603344664991/AnsiballZ_redhat_subscription.py
<localhost> EXEC /bin/sh -c 'chmod u+x /var/tmp/.ansible/ansible-tmp-1680017600.1291587-8982-142603344664991/ /var/tmp/.ansible/ansible-tmp-1680017600.1291587-8982-142603344664991/AnsiballZ_redhat_subscription.py && sleep 0'
<localhost> EXEC /bin/sh -c '/usr/libexec/platform-python /var/tmp/.ansible/ansible-tmp-1680017600.1291587-8982-142603344664991/AnsiballZ_redhat_subscription.py && sleep 0'
<localhost> EXEC /bin/sh -c 'rm -f -r /var/tmp/.ansible/ansible-tmp-1680017600.1291587-8982-142603344664991/ > /dev/null 2>&1 && sleep 0'
fatal: [localhost]: FAILED! => {
    "changed": false,
    "cmd": "/usr/sbin/subscription-manager unregister",
    "invocation": {
        "module_args": {
            "activationkey": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "auto_attach": null,
            "consumer_id": null,
            "consumer_name": null,
            "consumer_type": null,
            "environment": null,
            "force_register": true,
            "org_id": "xxxxxxxxxxx",
            "password": null,
            "pool": "^$",
            "pool_ids": [],
            "release": null,
            "rhsm_baseurl": null,
            "rhsm_repo_ca_cert": null,
            "server_hostname": null,
            "server_insecure": null,
            "server_port": null,
            "server_prefix": null,
            "server_proxy_hostname": null,
            "server_proxy_password": null,
            "server_proxy_port": null,
            "server_proxy_scheme": null,
            "server_proxy_user": null,
            "state": "present",
            "syspurpose": null,
            "token": null,
            "username": null
        }
    },
    "msg": "This system is currently not registered.",
    "rc": 1,
    "stderr": "This system is currently not registered.\n",
    "stderr_lines": [
        "This system is currently not registered."
    ],
    "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 @alikins @barnabycourt @kahowell
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Ops, regression in the D-Bus registration work (#6122), thanks for the report. Hopefully #6259 should fix this.

Out of curiosity: what's your use case for specifying `force_register`, as it will always unregister an already registered system?
> Up to 6.4.0 the options force_register was not doing anything (according to [the commit e939cd07ef335e939e901c3ccc76523eab8d5e4a)](https://github.com/ansible-collections/community.general/commit/e939cd07ef335e939e901c3ccc76523eab8d5e4a#diff-b354707cac5ffce6845c0ab9c2070a9063747f9a41d64c284194ce5ca2d982c3) L551)

Note that the comment there refers to a different thing, i.e. the `force` option for the D-Bus registration. Before the mentioned commit (and thus up to 6.4.0) the `redhat_subscription` module did not use D-Bus for registering, and rather always spawned `subscription-manager register`, which has a working `--force` option. Thus the `force_register` option of the module has always been working as intended so far, unregistering and registering again a system _every time_ the `redhat_subscription` module runs.
Hello @supershal, I saw your mentions of this issue in PRs that pinned the version of community.general. Since the problem here is specific to the `force_register` option, which will unregister and register the system _every time_ the `redhat_subscription` module runs, what's your use case for using it?
@ptoscano we pinned the version as a temporary fix to unblock us. 
Reason for using `force_register`: Our build process register with RHEL, download rpms and unregister at the end. Sometimes the builds are interrupted for various reasons and does not cleanup to unregister the system in the end. As a result we have to manually unregister the system to run the builds on the same system. using `force_register` would help us test our builds on same system ID.


@supershal thanks for the explanation; I have a question though: wouldn't it be possible to ensure the cleanup/unregistration of the system with a `block`, for example:
```yaml
- block:
    - redhat_subscription:
        state: present
        # credentials + other bits needed to register

    # download packages, etc
  always:
    - redhat_subscription:
        state: absent
```
?
The `downloading packages` tasks are spread throughout different roles and the `unregistration` is done in different role that is executed at the end as a "cleanup" role. Also the `registration` and `unregistration` are very conditional and triggered for specific usecases only. 
unless we refactor a lot of our playbooks, we didn't find a better way to accomplish this in a single transaction: `register`, `download packages`, `unregister` in different roles based on specific conditions. 

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
@@ -450,7 +450,7 @@ def _can_connect_to_dbus(self):
         self.module.debug('Verified system D-Bus bus as usable')
         return True
 
-    def register(self, username, password, token, auto_attach, activationkey, org_id,
+    def register(self, was_registered, username, password, token, auto_attach, activationkey, org_id,
                  consumer_type, consumer_name, consumer_id, force_register, environment,
                  release):
         '''
@@ -463,7 +463,7 @@ def register(self, username, password, token, auto_attach, activationkey, org_id
         # There is no support for token-based registration in the D-Bus API
         # of rhsm, so always use the CLI in that case.
         if not token and self._can_connect_to_dbus():
-            self._register_using_dbus(username, password, auto_attach,
+            self._register_using_dbus(was_registered, username, password, auto_attach,
                                       activationkey, org_id, consumer_type,
                                       consumer_name, consumer_id,
                                       force_register, environment, release)
@@ -521,7 +521,7 @@ def _register_using_cli(self, username, password, token, auto_attach,
 
         rc, stderr, stdout = self.module.run_command(args, check_rc=True, expand_user_and_vars=False)
 
-    def _register_using_dbus(self, username, password, auto_attach,
+    def _register_using_dbus(self, was_registered, username, password, auto_attach,
                              activationkey, org_id, consumer_type, consumer_name,
                              consumer_id, force_register, environment, release):
         '''
@@ -570,7 +570,7 @@ def str2int(s, default=0):
              distro_version[0] > 9)):
             dbus_force_option_works = True
 
-        if force_register and not dbus_force_option_works:
+        if force_register and not dbus_force_option_works and was_registered:
             self.unregister()
 
         register_opts = {}
@@ -592,7 +592,7 @@ def str2int(s, default=0):
                  distro_version[0] >= 9)):
                 environment_key = 'environments'
             register_opts[environment_key] = environment
-        if force_register and dbus_force_option_works:
+        if force_register and dbus_force_option_works and was_registered:
             register_opts['force'] = True
         # Wrap it as proper D-Bus dict
         register_opts = dbus.Dictionary(register_opts, signature='sv', variant_level=1)
@@ -1137,8 +1137,11 @@ def main():
     # Ensure system is registered
     if state == 'present':
 
+        # Cache the status of the system before the changes
+        was_registered = rhsm.is_registered
+
         # Register system
-        if rhsm.is_registered and not force_register:
+        if was_registered and not force_register:
             if syspurpose and 'sync' in syspurpose and syspurpose['sync'] is True:
                 try:
                     rhsm.sync_syspurpose()
@@ -1165,7 +1168,7 @@ def main():
             try:
                 rhsm.enable()
                 rhsm.configure(**module.params)
-                rhsm.register(username, password, token, auto_attach, activationkey, org_id,
+                rhsm.register(was_registered, username, password, token, auto_attach, activationkey, org_id,
                               consumer_type, consumer_name, consumer_id, force_register,
                               environment, release)
                 if syspurpose and 'sync' in syspurpose and syspurpose['sync'] is True:


Test patch: