Repository: ansible-collections/community.general

Issue:
modprobe (without params) with persistent fails if /etc/modprobe.d does not exists
### Summary

When you try to execute the following:
```yaml
 - name: Modprobe Kernel Module
   community.general.modprobe:
     name:  kernel_module_x
     state: present
     persistent: present
```

And the directory `/etc/modprobe.d` does not exists on the host, modprobe fails with a FileNotFound excetpion when trying  to find a module parameters under `/etc/modprobe.d`

This came up in kubespray in kubernetes-sigs/kubespray#10677

### Issue Type

Bug Report

### Component Name

modprobe

### Ansible Version

ansible [core 2.15.6]
  config file = None
  configured module search path = ['/home/andrei/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/andrei/.pyenv/versions/kubespray/lib/python3.11/site-packages/ansible
  ansible collection location = /home/andrei/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/andrei/.pyenv/versions/kubespray/bin/ansible
  python version = 3.11.6 (main, Nov 25 2023, 11:49:51) [GCC 13.2.1 20230801] (/home/andrei/.pyenv/versions/kubespray/bin/python3)
  jinja version = 3.1.2
  libyaml = True

### Community.general Version

```console (paste below)
$ ansible-galaxy collection list
 community.general

# /home/snip/upstream/kubespray/venv/lib/python3.11/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 7.5.0  
```


### Configuration

_No response_

### OS / Environment

Linux 5.15.138-flatcar x86_64
NAME="Flatcar Container Linux by Kinvolk"
ID=flatcar
ID_LIKE=coreos
VERSION=3602.2.2
VERSION_ID=3602.2.2
BUILD_ID=2023-11-20-1825
SYSEXT_LEVEL=1.0
PRETTY_NAME="Flatcar Container Linux by Kinvolk 3602.2.2 (Oklo)"
ANSI_COLOR="38;5;75"
HOME_URL="https://flatcar.org/"
BUG_REPORT_URL="https://issues.flatcar.org"
FLATCAR_BOARD="amd64-usr"
CPE_NAME="cpe:2.3:o:flatcar-linux:flatcar_linux:3602.2.2:*:*:*:*:*:*:*"

### Steps to Reproduce

Put the following task in a playbook, execute on Flatcar host.
```yaml
 - name: Modprobe Kernel Module
   community.general.modprobe:
     name:  br_netfilter
     state: present
     persistent: present
```
Alternatively, use
`ansible -m community.general.modrobe -a 'name=br_netfilter state=present persistent=present` on a Flatcar host (or any host not having `/etc/modprobe.d` )


### Expected Results

Two differents things:

1. Without a `params` parameter, modprobe should not fails on the absence of `/etc/modprobe.d`
2. Even if `params` is specified, modprobe should create `/etc/modprobe.d` on demand.

### Actual Results

```console (paste below)
failed: [k8s07] (item=ip_vs_lc) => {"ansible_loop_var": "item", "changed": false, "item": "ip_vs_lc", "module_stderr": "Traceback (most recent call last):
  File \"<stdin>\", line 107, in <module>
  File \"<stdin>\", line 99, in _ansiballz_main
  File \"<stdin>\", line 47, in invoke_module
  File \"/opt/bin/pypy3/lib/pypy3.9/runpy.py\", line 213, in run_module
    return _run_module_code(code, init_globals, run_name, mod_spec)
  File \"/opt/bin/pypy3/lib/pypy3.9/runpy.py\", line 97, in _run_module_code
    _run_code(code, mod_globals, init_globals,
  File \"/opt/bin/pypy3/lib/pypy3.9/runpy.py\", line 87, in _run_code
    exec(code, run_globals)
  File \"/tmp/ansible_community.general.modprobe_payload_nper0d8d/ansible_community.general.modprobe_payload.zip/ansible_collections/community/general/plugins/modules/modprobe.py\", line 320, in <module>
  File \"/tmp/ansible_community.general.modprobe_payload_nper0d8d/ansible_community.general.modprobe_payload.zip/ansible_collections/community/general/plugins/modules/modprobe.py\", line 312, in main
  File \"/tmp/ansible_community.general.modprobe_payload_nper0d8d/ansible_community.general.modprobe_payload.zip/ansible_collections/community/general/plugins/modules/modprobe.py\", line 219, in load_module_permanent
  File \"/tmp/ansible_community.general.modprobe_payload_nper0d8d/ansible_community.general.modprobe_payload.zip/ansible_collections/community/general/plugins/modules/modprobe.py\", line 148, in params_is_set
  File \"/tmp/ansible_community.general.modprobe_payload_nper0d8d/ansible_community.general.modprobe_payload.zip/ansible_collections/community/general/plugins/modules/modprobe.py\", line 154, in permanent_params
  File \"/tmp/ansible_community.general.modprobe_payload_nper0d8d/ansible_community.general.modprobe_payload.zip/ansible_collections/community/general/plugins/modules/modprobe.py\", line 242, in modprobe_files
FileNotFoundError: [Errno 2] No such file or directory: '/etc/modprobe.d'
", "module_stdout": "", "msg": "MODULE FAILURE
See stdout/stderr for the exact error", "rc": 1}
```
(This is from the kubespray bug-report, reformatted for readability)

TL;DR (if my understanding of modprobe.d is correct) : modprobe try to found the file for the params of the module it handles, and fails because the parent directory does not exist.

### Code of Conduct

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


Discussion:
Files identified in the description:
* [`plugins/modules/modprobe.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/modprobe.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 @jdauphant @mattjeffery
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
This does not have a PR, btw, not sure why the bot thinks so ?
Maybe because I linked to it from kubespray ?
The bot sees that a PR is referencing this issue, hence it adds the `has_pr` label. Unfortunately it does not care whether the PR is part of this repository or not...

Resolving patch:
diff --git a/plugins/modules/modprobe.py b/plugins/modules/modprobe.py
--- a/plugins/modules/modprobe.py
+++ b/plugins/modules/modprobe.py
@@ -232,12 +232,16 @@ def unload_module_permanent(self):
 
     @property
     def modules_files(self):
+        if not os.path.isdir(MODULES_LOAD_LOCATION):
+            return []
         modules_paths = [os.path.join(MODULES_LOAD_LOCATION, path)
                          for path in os.listdir(MODULES_LOAD_LOCATION)]
         return [path for path in modules_paths if os.path.isfile(path)]
 
     @property
     def modprobe_files(self):
+        if not os.path.isdir(PARAMETERS_FILES_LOCATION):
+            return []
         modules_paths = [os.path.join(PARAMETERS_FILES_LOCATION, path)
                          for path in os.listdir(PARAMETERS_FILES_LOCATION)]
         return [path for path in modules_paths if os.path.isfile(path)]


Test patch: