diff --git a/library/bootloader_facts.py b/library/bootloader_facts.py index e1eeb15..c4d6eae 100644 --- a/library/bootloader_facts.py +++ b/library/bootloader_facts.py @@ -3,6 +3,7 @@ # Copyright: (c) 2023, Sergei Petrosian # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function + __metaclass__ = type DOCUMENTATION = r""" @@ -47,23 +48,20 @@ def run_module(): # supports check mode module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) - # Keys from `grubby --info=ALL`, values for which this module returns - grubby_keys = ["args", "id", "index", "initrd", "kernel", "root", "title"] - rc, stdout, stderr = module.run_command("grubby --info=ALL") if "Permission denied" in stderr: module.fail_json(msg="You must run this as sudo", **result) - stdout_lines = stdout.strip().split('\n') + stdout_lines = stdout.strip().split("\n") kernels = [] index_count = 0 for line in stdout_lines: - if re.search("index=\d+", line): + if re.search(r"index=\d+", line): index_count += 1 kernels.append({}) - search = re.search("(.*?)=(.*)", line) + search = re.search(r"(.*?)=(.*)", line) key = search.group(1).strip('"') value = search.group(2).strip('"') - kernels[index_count -1].update({key: value}) + kernels[index_count - 1].update({key: value}) result["ansible_facts"]["bootloader_facts"] = kernels # in the event of a successful module execution, you will want to diff --git a/library/bootloader_settings.py b/library/bootloader_settings.py new file mode 100644 index 0000000..2f05e90 --- /dev/null +++ b/library/bootloader_settings.py @@ -0,0 +1,203 @@ +#!/usr/bin/python + +# Copyright: (c) 2023, Sergei Petrosian +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: bootloader_settings + +short_description: Configure grubby boot loader arguments for specified kernels + +version_added: "0.0.1" + +description: + - "WARNING: Do not use this module directly! It is only for role internal use." + - Configure grubby boot loader arguments for specified kernels + +options: + bootloader_settings: + description: List of kernels and their command line parameters that you want to set. + required: true + type: list + +author: + - Sergei Petrosian (@spetrosi) +""" + +EXAMPLES = r""" +- name: Test with a message + bootloader_settings: + bootloader_settings: +""" + +RETURN = r""" +# These are examples of possible return values, and in general should use other names for return values. +# original_message: +# description: The original name param that was passed in. +# type: str +# returned: always +# sample: 'hello world' +# message: +# description: The output message that the test module generates. +# type: str +# returned: always +# sample: 'goodbye' +""" + +import re + +from ansible.module_utils.basic import AnsibleModule + +# import ansible.module_utils.six as ansible_six +import ansible.module_utils.six.moves as ansible_six_moves + + +def get_grubby_args(kernel): + module_args = dict(bootloader_settings=dict(type="list", required=True)) + module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) + rc, kernel_info, stderr = module.run_command("grubby --info=" + kernel) + # kernel_info = "index=0\nkernel=\"/boot/vmlinuz-5.14.0-386.el9.x86_64\"\nargs=\"console=tty0 console=ttyS0,115200n8 no_timer_check net.ifnames=0 crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M $tuned_params\"\nroot=\"UUID=b4f467ad-fe53-4523-af54-30fcad90aacc\"\ninitrd=\"/boot/initramfs-5.14.0-386.el9.x86_64.img $tuned_initrd\"\ntitle=\"Red Hat Enterprise Linux (5.14.0-386.el9.x86_64) 9.4 (Plow)\"\nid=\"ffffffffffffffffffffffffffffffff-5.14.0-386.el9.x86_64\"\n" + return re.search(r'args="(.*)"', kernel_info).group(1) + + +def escapeval(val): + """make sure val is quoted as in shell""" + return ansible_six_moves.shlex_quote(str(val)) + + +def run_module(): + # define available arguments/parameters a user can pass to the module + module_args = dict(bootloader_settings=dict(type="list", required=True)) + + # seed the result dict in the object + # we primarily care about changed and state + # changed is if this module effectively modified the target + # state will include any data that you want your module to pass back + # for consumption, for example, in a subsequent task + result = dict( + changed=False, + ) + + # the AnsibleModule object will be our abstraction working with Ansible + # this includes instantiation, a couple of common attr would be the + # args/params passed to the execution, as well as if the module + # supports check mode + module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) + + kernels = [] + # kernels_keys = ["kernel_index", "kernel_path", "kernel_title"] + for bootloader_setting in module.params["bootloader_settings"]: + # if type(bootloader_setting["kernel"]) is not str: + # bootloader_kernel = bootloader_setting["kernel"] + # for kernel_key in kernels_keys: + # kernel_key_list = [] + # if kernel_key in bootloader_setting["kernel"]: + # if type(bootloader_setting["kernel"][kernel_key]) is list: + # for kernel_val in bootloader_setting["kernel"][kernel_key]: + # kernel_key_list.append( + # {"name": escapeval(kernel_val)} + # ) + # kernel_key_list.append( + # {"options": bootloader_setting["options"]} + # ) + # else: + # kernel_key_list.append( + # {"name": escapeval(bootloader_setting["kernel"][kernel_key])} + # ) + # kernel_key_list.append( + # {"options": bootloader_setting["options"]} + # ) + # kernels.append({kernel_key: kernel_key_list}) + # else: + # kernels.append({bootloader_setting["kernel"]: bootloader_setting["options"]}) + + if "kernel_path" in bootloader_setting["kernel"]: + kernels = bootloader_setting["kernel"]["kernel_path"] + elif "kernel_index" in bootloader_setting["kernel"]: + kernels = bootloader_setting["kernel"]["kernel_index"] + elif "kernel_title" in bootloader_setting["kernel"]: + if isinstance(bootloader_setting["kernel"], str): + kernels = "TITLE=" + bootloader_setting["kernel"]["kernel_title"] + else: + for kernel_title in bootloader_setting["kernel"]["kernel_title"]: + kernels.append( + "TITLE=" + bootloader_setting["kernel"]["kernel_title"] + ) + elif ( + bootloader_setting["kernel"] == "ALL" + or bootloader_setting["kernel"] == "DEFAULT" + ): + kernels = bootloader_setting["kernel"] + else: + module.fail_json( + msg='bootloader_settings.kernel must contain one of kernel_path, kernel_index, kernel_title, "ALL", "DEFAULT"', + **result + ) + if not isinstance(kernels, list): + kernels = [kernels] + + for kernel in kernels: + kernel = escapeval(kernel) + # Remove all existing boot settings + if {"previous": "replaced"} in bootloader_setting["options"]: + bootloader_args = get_grubby_args(kernel) + if len(bootloader_args) > 0: + rc, stdout, stderr = module.run_command( + "grubby --update-kernel=" + + kernel + + " --remove-args=" + + escapeval(bootloader_args) + ) + # result['remove_args_cmd'] = "grubby --update-kernel=" + kernel + " --remove-args=" + escapeval(bootloader_args) + result["changed"] = True + # Configure boot settings + bootloader_absent_args = "" + bootloader_present_args = "" + bootloader_mod_args = "" + for kernel_setting in bootloader_setting["options"]: + if {"previous": "replaced"} == kernel_setting: + continue + if "value" in kernel_setting: + setting_name = ( + kernel_setting["name"] + "=" + str(kernel_setting["value"]) + ) + else: + setting_name = kernel_setting["name"] + if "state" in kernel_setting and kernel_setting["state"] == "absent": + bootloader_absent_args += setting_name + " " + else: + bootloader_present_args += setting_name + " " + if len(bootloader_absent_args) > 0: + bootloader_mod_args = " --remove-args=" + escapeval( + bootloader_absent_args + ) + if len(bootloader_present_args) > 0: + bootloader_mod_args += " --args=" + escapeval(bootloader_present_args) + if len(bootloader_mod_args) > 0: + # result['mod_args_command'] = "grubby --update-kernel=" + kernel + bootloader_mod_args + rc, stdout, stderr = module.run_command( + "grubby --update-kernel=" + kernel + bootloader_mod_args + ) + result["changed"] = True + + # if the user is working with this module in only check mode we do not + # want to make any changes to the environment, just return the current + # state with no modifications + # if module.check_mode: + # module.exit_json(**result) + + # in the event of a successful module execution, you will want to + # simple AnsibleModule.exit_json(), passing the key/value results + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/tasks/loop_kernels.yml b/tasks/loop_kernels.yml deleted file mode 100644 index 4ac578f..0000000 --- a/tasks/loop_kernels.yml +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-License-Identifier: MIT ---- -- name: Remove and modify settings for each kernel - loop: "{{ __bootloader_kernel }}" - loop_control: - loop_var: __bootloader_kernel_value - include_tasks: rm_mod_settings.yml diff --git a/tasks/main.yml b/tasks/main.yml index 16b7c28..cbf9a0d 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -8,24 +8,12 @@ name: "{{ __bootloader_packages }}" state: present -- name: Remove for previous replaced then modify boot settings - include_tasks: loop_kernels.yml - vars: - __bootloader_kernel_prep: >- - {%- if item.kernel.kernel_path is defined -%} - {{ item.kernel.kernel_path }} - {%- elif item.kernel.kernel_index is defined -%} - {{ item.kernel.kernel_index }} - {%- elif item.kernel.kernel_title is defined -%} - TITLE={{ item.kernel.kernel_title | quote }} - {%- elif item.kernel in ["ALL", "DEFAULT"] -%} - {{ item.kernel }} - {%- endif -%} - __bootloader_kernel: "{{ - __bootloader_kernel_prep - if __bootloader_kernel_prep | type_debug == 'list' else - [__bootloader_kernel_prep] }}" - loop: "{{ bootloader_settings }}" +- name: Ensure boot loader settings + bootloader_settings: + bootloader_settings: "{{ bootloader_settings }}" + notify: + - Fix default kernel boot parameters + - Reboot system - name: Update boot loader timeout configuration lineinfile: diff --git a/tasks/modify_settings.yml b/tasks/modify_settings.yml deleted file mode 100644 index bb51b3d..0000000 --- a/tasks/modify_settings.yml +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-License-Identifier: MIT ---- -- name: Configure settings - when: __bootloader_kernel_setting.previous | d() != 'replaced' - vars: - __bootloader_with_value: >- - {{ __bootloader_kernel_setting.name is defined and - __bootloader_kernel_setting.value is defined }} - __bootloader_setting: >- - {{ __bootloader_with_value | - ternary( - __bootloader_kernel_setting.name | string + '=' + - __bootloader_kernel_setting.value | d() | string, - __bootloader_kernel_setting.name) }} - __bootloader_absent: >- - {{ __bootloader_kernel_setting.state | d('present') == 'absent' }} - block: - - name: Check boot setting {{ __bootloader_setting }} - shell: >- - set -euo pipefail; - grubby --info={{ __bootloader_kernel_value }} | grep '^args="' | sed 's/^args=//' - register: __bootloader_check_setting - changed_when: false - - - name: Configure boot setting {{ __bootloader_setting }} - command: >- - grubby - --update-kernel={{ __bootloader_kernel_value }} - {{ __bootloader_absent | ternary('--remove-args=', '--args=') - }}{{ __bootloader_setting }} - changed_when: true - when: >- - ((__bootloader_setting not in __bootloader_check_setting.stdout) | bool) - == ((not __bootloader_absent) | bool) - notify: - - Fix default kernel boot parameters - - Reboot system diff --git a/tasks/remove_settings.yml b/tasks/remove_settings.yml deleted file mode 100644 index 5ed2e2a..0000000 --- a/tasks/remove_settings.yml +++ /dev/null @@ -1,21 +0,0 @@ -# SPDX-License-Identifier: MIT ---- -- name: Get existing boot settings for {{ __bootloader_kernel_value }} - shell: >- - set -o pipefail; - grubby --info={{ __bootloader_kernel_value }} | - grep '^args="' | sed 's/^args=//' - changed_when: false - register: __bootloader_args - -- name: Remove all boot settings for {{ __bootloader_kernel_value }} - command: >- - grubby - --update-kernel={{ __bootloader_kernel_value }} - --remove-args={{ __bootloader_args.stdout }} - changed_when: true - # length > 2 for two quotes - when: __bootloader_args.stdout | length > 2 - notify: - - Fix default kernel boot parameters - - Reboot system diff --git a/tasks/rm_mod_settings.yml b/tasks/rm_mod_settings.yml deleted file mode 100644 index abb4f52..0000000 --- a/tasks/rm_mod_settings.yml +++ /dev/null @@ -1,15 +0,0 @@ -# SPDX-License-Identifier: MIT ---- -- name: Remove the existing boot settings with a loop - when: - - item.options | - selectattr('previous', 'defined') | - selectattr('previous', 'match', '^replaced$') | - list | length > 0 - include_tasks: remove_settings.yml - -- name: Configure setting with value - include_tasks: modify_settings.yml - loop: "{{ item.options }}" - loop_control: - loop_var: __bootloader_kernel_setting diff --git a/tests/tests_settings.yml b/tests/tests_settings.yml index 6f8fc8b..db63500 100644 --- a/tests/tests_settings.yml +++ b/tests/tests_settings.yml @@ -137,8 +137,9 @@ - name: Verify settings assert: that: >- - (bootloader_facts | selectattr('index', 'search', '0') | first).args | - regex_search('^.*console=tty0 print-fatal-signals=1 no_timer_check( |)$') + (bootloader_facts | selectattr('index', 'search', '0') | first).args + | regex_search('^.*console=tty0 print-fatal-signals=1 + no_timer_check( |)$') - name: Verify boot loader timeout configuration command: cat {{ __bootloader_grub_conf }}