Repository: ansible-collections/community.general

Issue:
pam_limits: check mode creates empty file
### Summary

If `dest` is set and the destination file does not exist, running pam_limits in check mode creates it.

### Issue Type

Bug Report

### Component Name

pam_limits

### Ansible Version

```console 
ansible [core 2.16.4]
  config file = /Users/mpv/.ansible.cfg
  configured module search path = ['/Users/mpv/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/Cellar/ansible/9.3.0/libexec/lib/python3.12/site-packages/ansible
  ansible collection location = /Users/mpv/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.12.2 (main, Feb  6 2024, 20:19:44) [Clang 15.0.0 (clang-1500.1.0.2.5)] (/usr/local/Cellar/ansible/9.3.0/libexec/bin/python)
  jinja version = 3.1.3
  libyaml = True
```


### Community.general Version

```console
$ ansible-galaxy collection list community.general

# /Users/mpv/.ansible/collections/ansible_collections
Collection        Version
----------------- -------
community.general 7.0.1  

# /usr/local/Cellar/ansible/9.3.0/libexec/lib/python3.12/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 8.4.0  

```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
CACHE_PLUGIN(/Users/mpv/.ansible.cfg) = jsonfile
CACHE_PLUGIN_CONNECTION(/Users/mpv/.ansible.cfg) = ~/.ansible/cache/fact
COLOR_ERROR(/Users/mpv/.ansible.cfg) = bright red
CONFIG_FILE() = /Users/mpv/.ansible.cfg
DEFAULT_HOST_LIST(env: ANSIBLE_INVENTORY) = ['/Users/mpv/sysop/ansible/inventory/devlabs']
DEFAULT_STDOUT_CALLBACK(/Users/mpv/.ansible.cfg) = yaml
DEFAULT_VAULT_PASSWORD_FILE(/Users/mpv/.ansible.cfg) = /Users/mpv/bin/av-client
DEPRECATION_WARNINGS(/Users/mpv/.ansible.cfg) = False
DISPLAY_SKIPPED_HOSTS(/Users/mpv/.ansible.cfg) = False
EDITOR(env: EDITOR) = vim
INTERPRETER_PYTHON(/Users/mpv/.ansible.cfg) = auto_silent
PLAYBOOK_DIR(env: ANSIBLE_PLAYBOOK_DIR) = /Users/mpv/sysop/ansible/playbooks
```


### OS / Environment

Target hosts Oracle Linux 7-9.

### Steps to Reproduce

<!--- playbook -->
```yaml
- hosts: mytesthost

  tasks:

  - name: set limit
    become: true

    community.general.pam_limits:
      domain: checkmode
      limit_item: nproc
      limit_type: soft
      value: 1024
      dest: /etc/security/limits.d/checkmode.conf

    check_mode: true
```

<!--- running playbook -->
```sh
$ ansible-playbook pam_limit.yml 

PLAY [mytesthost] ***************************************************************************

TASK [Gathering Facts] *****************************************************************************
ok: [mytesthost]

TASK [set limit] ***********************************************************************************
changed: [mytesthost]

PLAY RECAP *****************************************************************************************
mytesthost                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```


### Expected Results

Playbook will report what it would do without actually doing anything.

### Actual Results

Playbook creates an empty configuration file on host:

```console (paste below)
mytesthost:~ $ ls -l /etc/security/limits.d/checkmode.conf
-rw-r--r--. 1 root root 0 Mar  1 10:02 /etc/security/limits.d/checkmode.conf
```


### Code of Conduct

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


Discussion:
Files identified in the description:
* [`plugins/modules/pam_limits.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/pam_limits.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 @giovannisciortino
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
This is caused by https://github.com/ansible-collections/community.general/blob/main/plugins/modules/pam_limits.py#L211, which is run also in check mode.

Resolving patch:
diff --git a/plugins/modules/pam_limits.py b/plugins/modules/pam_limits.py
--- a/plugins/modules/pam_limits.py
+++ b/plugins/modules/pam_limits.py
@@ -175,7 +175,6 @@ def main():
     limits_conf = '/etc/security/limits.conf'
 
     module = AnsibleModule(
-        # not checking because of daisy chain to file module
         argument_spec=dict(
             domain=dict(required=True, type='str'),
             limit_type=dict(required=True, type='str', choices=pam_types),
@@ -201,6 +200,7 @@ def main():
     new_comment = module.params['comment']
 
     changed = False
+    does_not_exist = False
 
     if os.path.isfile(limits_conf):
         if not os.access(limits_conf, os.W_OK):
@@ -208,7 +208,7 @@ def main():
     else:
         limits_conf_dir = os.path.dirname(limits_conf)
         if os.path.isdir(limits_conf_dir) and os.access(limits_conf_dir, os.W_OK):
-            open(limits_conf, 'a').close()
+            does_not_exist = True
             changed = True
         else:
             module.fail_json(msg="directory %s is not writable (check presence, access rights, use sudo)" % limits_conf_dir)
@@ -224,15 +224,20 @@ def main():
 
     space_pattern = re.compile(r'\s+')
 
+    if does_not_exist:
+        lines = []
+    else:
+        with open(limits_conf, 'rb') as f:
+            lines = list(f)
+
     message = ''
-    f = open(limits_conf, 'rb')
     # Tempfile
     nf = tempfile.NamedTemporaryFile(mode='w+')
 
     found = False
     new_value = value
 
-    for line in f:
+    for line in lines:
         line = to_native(line, errors='surrogate_or_strict')
         if line.startswith('#'):
             nf.write(line)
@@ -323,17 +328,17 @@ def main():
         message = new_limit
         nf.write(new_limit)
 
-    f.close()
     nf.flush()
 
-    with open(limits_conf, 'r') as content:
-        content_current = content.read()
-
     with open(nf.name, 'r') as content:
         content_new = content.read()
 
     if not module.check_mode:
-        # Copy tempfile to newfile
+        if does_not_exist:
+            with open(limits_conf, 'a'):
+                pass
+
+        # Move tempfile to newfile
         module.atomic_move(nf.name, limits_conf)
 
     try:
@@ -344,7 +349,7 @@ def main():
     res_args = dict(
         changed=changed,
         msg=message,
-        diff=dict(before=content_current, after=content_new),
+        diff=dict(before=b''.join(lines), after=content_new),
     )
 
     if backup:


Test patch: