Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add persistent option for modprobe #5424

Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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).
150 changes: 150 additions & 0 deletions plugins/modules/modprobe.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,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), module adds module name to C(/etc/modules-load.d/) and params to C(/etc/modprobe.d/) so module will be loaded on next reboot.
- If C(absent), module comment out module name from C(/etc/modules-load.d/) and comment out params from C(/etc/modprobe.d/) so module won't be
loaded on next reboot.
- If C(disabled), module won't 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.
- This options work only with distributions that uses C(systemd).
'''

EXAMPLES = '''
Expand All @@ -48,17 +63,27 @@
name: dummy
state: present
params: 'numdummies=2'

- name: Add the dummy module
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):
Expand All @@ -70,6 +95,7 @@ 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

Expand All @@ -93,6 +119,124 @@ def load_module(self):
if rc != 0:
self.module.warn(stderr)

@property
def module_is_loaded_persistently(self):
pattern = re.compile(r'^ *{0} *(?:[#;].*)?\n?\Z'.format(self.name))
for module_file in self.modules_files:
with open(module_file) as file:
for line in file:
if pattern.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()

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

for modprobe_file in self.modprobe_files:
with open(modprobe_file) as file:
for line in file:
match = pattern.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):

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

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 pattern.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):

pattern = re.compile(r'^ *{0} *(?:[#;].*)?\n?\Z'.format(self.name))

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 pattern.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 @@ -143,6 +287,7 @@ def main():
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,
)
Expand All @@ -154,6 +299,11 @@ def main():
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