Repository: ansible-collections/community.general

Issue:
sudoers module does not handle state: absent properly
### Summary

The `community.general.sudoers` module does not handle `state: absent` properly.
If the file specified by `name` does not exist, it gets created, with possibly invalid content. As a side effect, `commands` and `user`/`group` is required, although these options do not make a lot of sense with `state: absent`.
If the file specified by `name` exists, it is (correctly) deleted. Of course, the next invocation of the play creates it again.

### Issue Type

Bug Report

### Component Name

sudoers

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.12.5]
  config file = ...
  configured module search path = [...]
  ansible python module location = /usr/lib/python3.9/site-packages/ansible
  ansible collection location = /usr/share/ansible/collections
  executable location = /usr/lib/python-exec/python3.9/ansible
  python version = 3.9.12 (main, Jun 17 2022, 16:04:33) [GCC 11.3.0]
  jinja version = 3.1.2
  libyaml = True
```

### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
# /usr/lib/python3.9/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 4.8.0  
```

### Configuration

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


### OS / Environment

Target OS is Debian 11.

### Steps to Reproduce

```yaml (paste below)
---
- hosts: all
  gather_facts: false
  tasks:
    - community.general.sudoers:
        name: test
        commands: []
        user: root
        state: absent

    - ansible.builtin.stat:
        path: /etc/sudoers.d/test
      register: _stat_sudoers

    - ansible.builtin.assert:
        that:
          - "not _stat_sudoers.stat.exists"
```
Note: `/etc/sudoers.d/test` is assumed not to exist prior to running this play.

### Expected Results

The play never fails, i.e. `/etc/sudoers.d/test` gets deleted/not created.

### Actual Results

The assertion fails on every second invocation of the play (starting with the first). The file `/etc/sudoers.d/test` gets created by the `sudoers` module.
The assertion passes on every other invocation of the play (starting with the second).

```console (paste below)
PLAY [all] **********************************************************************************************

TASK [community.general.sudoers] ************************************************************************
changed: [test]

TASK [ansible.builtin.stat] *****************************************************************************
ok: [test]

TASK [ansible.builtin.assert] ***************************************************************************
fatal: [test]: FAILED! => {
    "assertion": "not _stat_sudoers.stat.exists",
    "changed": false,
    "evaluated_to": false,
    "msg": "Assertion failed"
}

PLAY RECAP **********************************************************************************************
test                       : ok=2    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
```

### Code of Conduct

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


Discussion:
Files identified in the description:
* [`plugins/modules/system/sudoers.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/system/sudoers.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 @JonEllis @JonEllis0
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/plugins/modules/system/sudoers.py b/plugins/modules/system/sudoers.py
--- a/plugins/modules/system/sudoers.py
+++ b/plugins/modules/system/sudoers.py
@@ -168,9 +168,12 @@ def content(self):
         return "{owner} ALL={runas}{nopasswd} {commands}\n".format(owner=owner, runas=runas_str, nopasswd=nopasswd_str, commands=commands_str)
 
     def run(self):
-        if self.state == 'absent' and self.exists():
-            self.delete()
-            return True
+        if self.state == 'absent':
+            if self.exists():
+                self.delete()
+                return True
+            else:
+                return False
 
         if self.exists() and self.matches():
             return False


Test patch:
diff --git a/tests/integration/targets/sudoers/tasks/main.yml b/tests/integration/targets/sudoers/tasks/main.yml
--- a/tests/integration/targets/sudoers/tasks/main.yml
+++ b/tests/integration/targets/sudoers/tasks/main.yml
@@ -135,6 +135,18 @@
   register: revoke_rule_1_stat
 
 
+- name: Revoke non-existing rule
+  community.general.sudoers:
+    name: non-existing-rule
+    state: absent
+  register: revoke_non_existing_rule
+
+- name: Stat non-existing rule
+  ansible.builtin.stat:
+    path: "{{ sudoers_path }}/non-existing-rule"
+  register: revoke_non_existing_rule_stat
+
+
 # Run assertions
 
 - name: Check rule 1 file stat
@@ -151,6 +163,7 @@
       - rule_1_again is not changed
       - rule_5 is changed
       - revoke_rule_1 is changed
+      - revoke_non_existing_rule is not changed
 
 - name: Check contents
   ansible.builtin.assert:
@@ -166,3 +179,4 @@
   ansible.builtin.assert:
     that:
       - not revoke_rule_1_stat.stat.exists
+      - not revoke_non_existing_rule_stat.stat.exists