Skip to content

Commit

Permalink
add persistent option for modprobe (#5424)
Browse files Browse the repository at this point in the history
* add persistent option for modprobe

* add suggested changes + fix broken test

* change modprobe module path in tests due to rebase

* change persistent option type from bool to str with choices

* fix unused import

* add example with persistent option

* fix some minor issues after review

- move regexps compiling to __init__
- move AnsibleModule to build_module function and use this function in tests instead of AnsibleModule
- fix terminlogy issue in documentation

* fix unused-import

(cherry picked from commit 29f5033)
  • Loading branch information
haddystuff authored and patchback[bot] committed Feb 26, 2023
1 parent 89dd500 commit 5b16abe
Show file tree
Hide file tree
Showing 3 changed files with 500 additions and 36 deletions.
3 changes: 3 additions & 0 deletions changelogs/fragments/4028-modprobe-persistent-option.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
minor_changes:
- modprobe - add ``persistent`` option (https://github.com/ansible-collections/community.general/issues/4028, https://github.com/ansible-collections/community.general/pull/542).
156 changes: 154 additions & 2 deletions plugins/modules/modprobe.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@
description:
- Modules parameters.
default: ''
persistent:
type: str
choices: [ disabled, absent, present ]
default: disabled
description:
- Persistency between reboots for configured module.
- This option creates files in C(/etc/modules-load.d/) and C(/etc/modprobe.d/) that make your module configuration persistent during reboots.
- If C(present), adds module name to C(/etc/modules-load.d/) and params to C(/etc/modprobe.d/) so the module will be loaded on next reboot.
- If C(absent), will comment out module name from C(/etc/modules-load.d/) and comment out params from C(/etc/modprobe.d/) so the module will not be
loaded on next reboot.
- If C(disabled), will not toch anything and leave C(/etc/modules-load.d/) and C(/etc/modprobe.d/) as it is.
- Note that it is usually a better idea to rely on the automatic module loading by PCI IDs, USB IDs, DMI IDs or similar triggers encoded in the
kernel modules themselves instead of configuration like this.
- In fact, most modern kernel modules are prepared for automatic loading already.
- "B(Note:) This option works only with distributions that use C(systemd) when set to values other than C(disabled)."
'''

EXAMPLES = '''
Expand All @@ -55,20 +70,31 @@
name: dummy
state: present
params: 'numdummies=2'
- name: Add the dummy module and make sure it is loaded after reboots
community.general.modprobe:
name: dummy
state: present
params: 'numdummies=2'
persistent: present
'''

import os.path
import platform
import shlex
import traceback
import re

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_native

RELEASE_VER = platform.release()
MODULES_LOAD_LOCATION = '/etc/modules-load.d'
PARAMETERS_FILES_LOCATION = '/etc/modprobe.d'


class Modprobe(object):

def __init__(self, module):
self.module = module
self.modprobe_bin = module.get_bin_path('modprobe', True)
Expand All @@ -77,9 +103,14 @@ def __init__(self, module):
self.desired_state = module.params['state']
self.name = module.params['name']
self.params = module.params['params']
self.persistent = module.params['persistent']

self.changed = False

self.re_find_module = re.compile(r'^ *{0} *(?:[#;].*)?\n?\Z'.format(self.name))
self.re_find_params = re.compile(r'^options {0} \w+=\S+ *(?:[#;].*)?\n?\Z'.format(self.name))
self.re_get_params_and_values = re.compile(r'^options {0} (\w+=\S+) *(?:[#;].*)?\n?\Z'.format(self.name))

def load_module(self):
command = [self.modprobe_bin]
if self.check_mode:
Expand All @@ -100,6 +131,117 @@ def load_module(self):
if rc != 0:
self.module.warn(stderr)

@property
def module_is_loaded_persistently(self):
for module_file in self.modules_files:
with open(module_file) as file:
for line in file:
if self.re_find_module.match(line):
return True

return False

@property
def params_is_set(self):
desired_params = set(self.params.split())

return desired_params == self.permanent_params

@property
def permanent_params(self):
params = set()

for modprobe_file in self.modprobe_files:
with open(modprobe_file) as file:
for line in file:
match = self.re_get_params_and_values.match(line)
if match:
params.add(match.group(1))

return params

def create_module_file(self):
file_path = os.path.join(MODULES_LOAD_LOCATION,
self.name + '.conf')
with open(file_path, 'w') as file:
file.write(self.name + '\n')

@property
def module_options_file_content(self):
file_content = ['options {0} {1}'.format(self.name, param)
for param in self.params.split()]
return '\n'.join(file_content) + '\n'

def create_module_options_file(self):
new_file_path = os.path.join(PARAMETERS_FILES_LOCATION,
self.name + '.conf')
with open(new_file_path, 'w') as file:
file.write(self.module_options_file_content)

def disable_old_params(self):

for modprobe_file in self.modprobe_files:
with open(modprobe_file) as file:
file_content = file.readlines()

content_changed = False
for index, line in enumerate(file_content):
if self.re_find_params.match(line):
file_content[index] = '#' + line
content_changed = True

if content_changed:
with open(modprobe_file, 'w') as file:
file.write('\n'.join(file_content))

def disable_module_permanent(self):

for module_file in self.modules_files:
with open(module_file) as file:
file_content = file.readlines()

content_changed = False
for index, line in enumerate(file_content):
if self.re_find_module.match(line):
file_content[index] = '#' + line
content_changed = True

if content_changed:
with open(module_file, 'w') as file:
file.write('\n'.join(file_content))

def load_module_permanent(self):

if not self.module_is_loaded_persistently:
self.create_module_file()
self.changed = True

if not self.params_is_set:
self.disable_old_params()
self.create_module_options_file()
self.changed = True

def unload_module_permanent(self):
if self.module_is_loaded_persistently:
self.disable_module_permanent()
self.changed = True

if self.permanent_params:
self.disable_old_params()
self.changed = True

@property
def modules_files(self):
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):
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)]

def module_loaded(self):
is_loaded = False
try:
Expand Down Expand Up @@ -144,23 +286,33 @@ def result(self):
}


def main():
module = AnsibleModule(
def build_module():
return AnsibleModule(
argument_spec=dict(
name=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['absent', 'present']),
params=dict(type='str', default=''),
persistent=dict(type='str', default='disabled', choices=['disabled', 'present', 'absent']),
),
supports_check_mode=True,
)


def main():
module = build_module()

modprobe = Modprobe(module)

if modprobe.desired_state == 'present' and not modprobe.module_loaded():
modprobe.load_module()
elif modprobe.desired_state == 'absent' and modprobe.module_loaded():
modprobe.unload_module()

if modprobe.persistent == 'present' and not (modprobe.module_is_loaded_persistently and modprobe.params_is_set):
modprobe.load_module_permanent()
elif modprobe.persistent == 'absent' and (modprobe.module_is_loaded_persistently or modprobe.permanent_params):
modprobe.unload_module_permanent()

module.exit_json(**modprobe.result)


Expand Down
Loading

0 comments on commit 5b16abe

Please sign in to comment.