mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
faa3b7349f
modprobe: Avoid FileNotFoundError when directories don't exist. (#8005)
* Avoid FileNotFoundError when directories don't exist.
* Adds changelog fragment.
* Update changelogs/fragments/7717-prevent-modprobe-error.yml
Co-authored-by: Felix Fontein <felix@fontein.de>
---------
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit e0dbe9c98d
)
Co-authored-by: Nate Douglas <ndouglas@users.noreply.github.com>
324 lines
11 KiB
Python
324 lines
11 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2013, David Stygstra <david.stygstra@gmail.com>
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
__metaclass__ = type
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: modprobe
|
|
short_description: Load or unload kernel modules
|
|
author:
|
|
- David Stygstra (@stygstra)
|
|
- Julien Dauphant (@jdauphant)
|
|
- Matt Jeffery (@mattjeffery)
|
|
description:
|
|
- Load or unload kernel modules.
|
|
extends_documentation_fragment:
|
|
- community.general.attributes
|
|
attributes:
|
|
check_mode:
|
|
support: full
|
|
diff_mode:
|
|
support: none
|
|
options:
|
|
name:
|
|
type: str
|
|
required: true
|
|
description:
|
|
- Name of kernel module to manage.
|
|
state:
|
|
type: str
|
|
description:
|
|
- Whether the module should be present or absent.
|
|
choices: [ absent, present ]
|
|
default: present
|
|
params:
|
|
type: str
|
|
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 V(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 V(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 V(disabled), will not touch 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 V(disabled)."
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
- name: Add the 802.1q module
|
|
community.general.modprobe:
|
|
name: 8021q
|
|
state: present
|
|
|
|
- name: Add the dummy module
|
|
community.general.modprobe:
|
|
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)
|
|
|
|
self.check_mode = module.check_mode
|
|
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:
|
|
command.append('-n')
|
|
command.extend([self.name] + shlex.split(self.params))
|
|
|
|
rc, out, err = self.module.run_command(command)
|
|
|
|
if rc != 0:
|
|
return self.module.fail_json(msg=err, rc=rc, stdout=out, stderr=err, **self.result)
|
|
|
|
if self.check_mode or self.module_loaded():
|
|
self.changed = True
|
|
else:
|
|
rc, stdout, stderr = self.module.run_command(
|
|
[self.modprobe_bin, '-n', '--first-time', self.name] + shlex.split(self.params)
|
|
)
|
|
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):
|
|
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)]
|
|
|
|
def module_loaded(self):
|
|
is_loaded = False
|
|
try:
|
|
with open('/proc/modules') as modules:
|
|
module_name = self.name.replace('-', '_') + ' '
|
|
for line in modules:
|
|
if line.startswith(module_name):
|
|
is_loaded = True
|
|
break
|
|
|
|
if not is_loaded:
|
|
module_file = '/' + self.name + '.ko'
|
|
builtin_path = os.path.join('/lib/modules/', RELEASE_VER, 'modules.builtin')
|
|
with open(builtin_path) as builtins:
|
|
for line in builtins:
|
|
if line.rstrip().endswith(module_file):
|
|
is_loaded = True
|
|
break
|
|
except (IOError, OSError) as e:
|
|
self.module.fail_json(msg=to_native(e), exception=traceback.format_exc(), **self.result)
|
|
|
|
return is_loaded
|
|
|
|
def unload_module(self):
|
|
command = [self.modprobe_bin, '-r', self.name]
|
|
if self.check_mode:
|
|
command.append('-n')
|
|
|
|
rc, out, err = self.module.run_command(command)
|
|
if rc != 0:
|
|
return self.module.fail_json(msg=err, rc=rc, stdout=out, stderr=err, **self.result)
|
|
|
|
self.changed = True
|
|
|
|
@property
|
|
def result(self):
|
|
return {
|
|
'changed': self.changed,
|
|
'name': self.name,
|
|
'params': self.params,
|
|
'state': self.desired_state,
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|