From 15d210aade1b4f27793e465b704dfd3fdc942f9e Mon Sep 17 00:00:00 2001 From: Sergei Petrosian Date: Wed, 6 Dec 2023 15:26:48 +0100 Subject: [PATCH] Modify settings idempotently --- library/bootloader_settings.py | 33 ++++++--- tests/unit/test_bootloader_facts.py | 95 ++++++++++++++++++++++++++ tests/unit/test_bootloader_settings.py | 34 ++++++--- 3 files changed, 141 insertions(+), 21 deletions(-) create mode 100644 tests/unit/test_bootloader_facts.py diff --git a/library/bootloader_settings.py b/library/bootloader_settings.py index 5b5b16b..59074d4 100644 --- a/library/bootloader_settings.py +++ b/library/bootloader_settings.py @@ -20,7 +20,7 @@ options: bootloader_settings: - description: List of kernels and their command line parameters that you want to set. + description: List of dict of kernels and their command line parameters that you want to set. required: true type: list elements: dict @@ -84,8 +84,15 @@ def get_kernels(bootloader_setting_kernel, kernels_keys): return kernels +def get_boot_args(kernel_info): + args = re.search(r'args="(.*)"', kernel_info) + if args is None: + return None + return args.group(1) + + def get_rm_boot_args_cmd(kernel_info, kernel): - bootloader_args = re.search(r'args="(.*)"', kernel_info).group(1) + bootloader_args = get_boot_args(kernel_info) return ( "grubby --update-kernel=" + kernel @@ -94,10 +101,11 @@ def get_rm_boot_args_cmd(kernel_info, kernel): ) -def get_mod_boot_args_cmd(bootloader_setting_options, kernel): +def get_mod_boot_args_cmd(bootloader_setting_options, kernel, kernel_info): boot_absent_args = "" boot_present_args = "" boot_mod_args = "" + bootloader_args = get_boot_args(kernel_info) for kernel_setting in bootloader_setting_options: if {"previous": "replaced"} == kernel_setting: continue @@ -106,15 +114,21 @@ def get_mod_boot_args_cmd(bootloader_setting_options, kernel): else: setting_name = kernel_setting["name"] if "state" in kernel_setting and kernel_setting["state"] == "absent": - boot_absent_args += setting_name + " " + if bootloader_args and re.search( + r"(^|$| )" + setting_name + r"(^|$| )", bootloader_args + ): + boot_absent_args += setting_name + " " else: - boot_present_args += setting_name + " " + if bootloader_args and ( + not re.search(r"(^|$| )" + setting_name + r"(^|$| )", bootloader_args) + ): + boot_present_args += setting_name + " " if boot_absent_args: boot_mod_args = " --remove-args=" + escapeval(boot_absent_args.strip()) if len(boot_present_args) > 0: boot_mod_args += " --args=" + escapeval(boot_present_args.strip()) if boot_mod_args: - return "grubby --update-kernel=" + escapeval(kernel) + boot_mod_args + return "grubby --update-kernel=" + kernel + boot_mod_args def run_module(): @@ -137,8 +151,6 @@ def run_module(): # 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) - result["rm_boot_args_cmd"] = [] - result["mod_boot_args_cmd"] = [] kernels_keys = ["kernel_index", "kernel_path", "kernel_title", "DEFAULT", "ALL"] for bootloader_setting in module.params["bootloader_settings"]: kernels = get_kernels(bootloader_setting["kernel"], kernels_keys) @@ -156,15 +168,14 @@ def run_module(): rm_boot_args_cmd = get_rm_boot_args_cmd(stdout, kernel) if rm_boot_args_cmd: rc, stdout, stderr = module.run_command(rm_boot_args_cmd) - result["rm_boot_args_cmd"].append(rm_boot_args_cmd) result["changed"] = True + rc, stdout, stderr = module.run_command("grubby --info=" + kernel) # Configure boot settings mod_boot_args_cmd = get_mod_boot_args_cmd( - bootloader_setting["options"], kernel + bootloader_setting["options"], kernel, stdout ) if mod_boot_args_cmd: rc, stdout, stderr = module.run_command(mod_boot_args_cmd) - result["mod_boot_args_cmd"].append(mod_boot_args_cmd) 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 diff --git a/tests/unit/test_bootloader_facts.py b/tests/unit/test_bootloader_facts.py new file mode 100644 index 0000000..36eb984 --- /dev/null +++ b/tests/unit/test_bootloader_facts.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Sergei Petrosian +# SPDX-License-Identifier: GPL-2.0-or-later +# +""" Unit tests for the bootloader_settings module """ + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import unittest + +import bootloader_facts + +INFO = """ +index=0 +kernel="/boot/vmlinuz-6.5.12-100.fc37.x86_64" +args="$tuned_params ro rootflags=subvol=root rd.luks.uuid=luks-9da1fdf5-14ac-49fd-a388-8b1ee48f3df1 rhgb quiet" +root="UUID=65c70529-e9ad-4778-9001-18fe8c525285" +initrd="/boot/initramfs-6.5.12-100.fc37.x86_64.img $tuned_initrd" +title="Fedora Linux (6.5.12-100.fc37.x86_64) 37 (Workstation Edition)" +id="c44543d15b2c4e898912c2497f734e67-6.5.12-100.fc37.x86_64" +index=1 +kernel="/boot/vmlinuz-6.5.10-100.fc37.x86_64" +args="ro rootflags=subvol=root rd.luks.uuid=luks-9da1fdf5-14ac-49fd-a388-8b1ee48f3df1 rhgb quiet $tuned_params" +root="UUID=65c70529-e9ad-4778-9001-18fe8c525285" +initrd="/boot/initramfs-6.5.10-100.fc37.x86_64.img $tuned_initrd" +title="Fedora Linux (6.5.10-100.fc37.x86_64) 37 (Workstation Edition)" +id="c44543d15b2c4e898912c2497f734e67-6.5.10-100.fc37.x86_64" +index=2 +kernel="/boot/vmlinuz-6.5.7-100.fc37.x86_64" +args="ro rootflags=subvol=root rd.luks.uuid=luks-9da1fdf5-14ac-49fd-a388-8b1ee48f3df1 rhgb quiet $tuned_params" +root="UUID=65c70529-e9ad-4778-9001-18fe8c525285" +initrd="/boot/initramfs-6.5.7-100.fc37.x86_64.img $tuned_initrd" +title="Fedora Linux (6.5.7-100.fc37.x86_64) 37 (Workstation Edition)" +id="c44543d15b2c4e898912c2497f734e67-6.5.7-100.fc37.x86_64" +index=3 +kernel="/boot/vmlinuz-0-rescue-c44543d15b2c4e898912c2497f734e67" +args="ro rootflags=subvol=root rd.luks.uuid=luks-9da1fdf5-14ac-49fd-a388-8b1ee48f3df1 rhgb quiet" +root="UUID=65c70529-e9ad-4778-9001-18fe8c525285" +initrd="/boot/initramfs-0-rescue-c44543d15b2c4e898912c2497f734e67.img" +title="Fedora Linux (0-rescue-c44543d15b2c4e898912c2497f734e67) 36 (Workstation Edition)" +id="c44543d15b2c4e898912c2497f734e67-0-rescue" +""" + +FACTS = [ + { + "args": "$tuned_params ro rootflags=subvol=root rd.luks.uuid=luks-9da1fdf5-14ac-49fd-a388-8b1ee48f3df1 rhgb quiet", + "id": "c44543d15b2c4e898912c2497f734e67-6.5.12-100.fc37.x86_64", + "index": "0", + "initrd": "/boot/initramfs-6.5.12-100.fc37.x86_64.img $tuned_initrd", + "kernel": "/boot/vmlinuz-6.5.12-100.fc37.x86_64", + "root": "UUID=65c70529-e9ad-4778-9001-18fe8c525285", + "title": "Fedora Linux (6.5.12-100.fc37.x86_64) 37 (Workstation Edition)", + }, + { + "args": "ro rootflags=subvol=root rd.luks.uuid=luks-9da1fdf5-14ac-49fd-a388-8b1ee48f3df1 rhgb quiet $tuned_params", + "id": "c44543d15b2c4e898912c2497f734e67-6.5.10-100.fc37.x86_64", + "index": "1", + "initrd": "/boot/initramfs-6.5.10-100.fc37.x86_64.img $tuned_initrd", + "kernel": "/boot/vmlinuz-6.5.10-100.fc37.x86_64", + "root": "UUID=65c70529-e9ad-4778-9001-18fe8c525285", + "title": "Fedora Linux (6.5.10-100.fc37.x86_64) 37 (Workstation Edition)", + }, + { + "args": "ro rootflags=subvol=root rd.luks.uuid=luks-9da1fdf5-14ac-49fd-a388-8b1ee48f3df1 rhgb quiet $tuned_params", + "id": "c44543d15b2c4e898912c2497f734e67-6.5.7-100.fc37.x86_64", + "index": "2", + "initrd": "/boot/initramfs-6.5.7-100.fc37.x86_64.img $tuned_initrd", + "kernel": "/boot/vmlinuz-6.5.7-100.fc37.x86_64", + "root": "UUID=65c70529-e9ad-4778-9001-18fe8c525285", + "title": "Fedora Linux (6.5.7-100.fc37.x86_64) 37 (Workstation Edition)", + }, + { + "args": "ro rootflags=subvol=root rd.luks.uuid=luks-9da1fdf5-14ac-49fd-a388-8b1ee48f3df1 rhgb quiet", + "id": "c44543d15b2c4e898912c2497f734e67-0-rescue", + "index": "3", + "initrd": "/boot/initramfs-0-rescue-c44543d15b2c4e898912c2497f734e67.img", + "kernel": "/boot/vmlinuz-0-rescue-c44543d15b2c4e898912c2497f734e67", + "root": "UUID=65c70529-e9ad-4778-9001-18fe8c525285", + "title": "Fedora Linux (0-rescue-c44543d15b2c4e898912c2497f734e67) 36 (Workstation Edition)", + }, +] + + +class InputValidator(unittest.TestCase): + """test functions that process bootloader_settings argument""" + + def test_get_facts(self): + kernels = bootloader_facts.get_facts(INFO) + self.assertEqual( + FACTS, + kernels, + ) diff --git a/tests/unit/test_bootloader_settings.py b/tests/unit/test_bootloader_settings.py index dd8658f..5a87ffa 100644 --- a/tests/unit/test_bootloader_settings.py +++ b/tests/unit/test_bootloader_settings.py @@ -19,7 +19,7 @@ {"name": "arg_without_val", "state": "present"}, {"name": "arg_with_str_value_absent", "value": "test_value", "state": "absent"}, {"name": "arg_with_int_value_absent", "value": 1, "state": "absent"}, - {"name": "arg_without_val", "state": "absent"}, + {"name": "arg_without_val_absent", "state": "absent"}, {"previous": "replaced"}, ] @@ -48,7 +48,7 @@ INFO = """ index=0 kernel="/boot/vmlinuz-6.5.12-100.fc37.x86_64" -args="$tuned_params ro rootflags=subvol=root rd.luks.uuid=luks-9da1fdf5-14ac-49fd-a388-8b1ee48f3df1 rhgb quiet" +args="arg_with_str_value_absent=test_value arg_with_int_value_absent=1 arg_without_val_absent" root="UUID=65c70529-e9ad-4778-9001-18fe8c525285" initrd="/boot/initramfs-6.5.12-100.fc37.x86_64.img $tuned_initrd" title="Fedora Linux (6.5.12-100.fc37.x86_64) 37 (Workstation Edition)" @@ -88,49 +88,63 @@ def test_get_kernels(self): kernels = bootloader_settings.get_kernels(KERNELS[7]["kernel"], kernels_keys) self.assertEqual(["ALL"], kernels) + def test_get_boot_args(self): + bootloader_args = bootloader_settings.get_boot_args(INFO) + self.assertEqual( + bootloader_args, + "arg_with_str_value_absent=test_value arg_with_int_value_absent=1 arg_without_val_absent", + ) + bootloader_args = bootloader_settings.get_boot_args("") + self.assertEqual(bootloader_args, None) + def test_get_rm_boot_args_cmd(self): rm_boot_args_cmd = bootloader_settings.get_rm_boot_args_cmd(INFO, "0") self.assertEqual( "grubby --update-kernel=0 --remove-args=" - + "'$tuned_params ro rootflags=subvol=root rd.luks.uuid=luks-9da1fdf5-14ac-49fd-a388-8b1ee48f3df1 rhgb quiet'", + + "'arg_with_str_value_absent=test_value arg_with_int_value_absent=1 arg_without_val_absent'", rm_boot_args_cmd, ) def test_get_mod_boot_args_cmd(self): args = ( - "--remove-args='arg_with_str_value_absent=test_value arg_with_int_value_absent=1 arg_without_val' " + "--remove-args='arg_with_str_value_absent=test_value arg_with_int_value_absent=1 arg_without_val_absent' " + "--args='arg_with_str_value=test_value arg_with_int_value=1 arg_without_val'" ) mod_boot_args_cmd = bootloader_settings.get_mod_boot_args_cmd( - OPTIONS, str(KERNELS[1]["kernel"]["kernel_index"]) + OPTIONS, str(KERNELS[1]["kernel"]["kernel_index"]), INFO ) self.assertEqual( "grubby --update-kernel=2 " + args, mod_boot_args_cmd, ) mod_boot_args_cmd = bootloader_settings.get_mod_boot_args_cmd( - OPTIONS, KERNELS[3]["kernel"]["kernel_path"] + OPTIONS, KERNELS[3]["kernel"]["kernel_path"], INFO ) self.assertEqual( "grubby --update-kernel=/path/3 " + args, mod_boot_args_cmd, ) mod_boot_args_cmd = bootloader_settings.get_mod_boot_args_cmd( - OPTIONS, "TITLE=" + KERNELS[5]["kernel"]["kernel_title"] + OPTIONS, + bootloader_settings.escapeval( + "TITLE=" + KERNELS[5]["kernel"]["kernel_title"] + ), + INFO, ) self.assertEqual( - "grubby --update-kernel='TITLE=Fedora Linux (1.1.11-300.fc37.x86_64) 37 (Workstation Edition)' " + args, + "grubby --update-kernel='TITLE=Fedora Linux (1.1.11-300.fc37.x86_64) 37 (Workstation Edition)' " + + args, mod_boot_args_cmd, ) mod_boot_args_cmd = bootloader_settings.get_mod_boot_args_cmd( - OPTIONS, KERNELS[6]["kernel"] + OPTIONS, KERNELS[6]["kernel"], INFO ) self.assertEqual( "grubby --update-kernel=DEFAULT " + args, mod_boot_args_cmd, ) mod_boot_args_cmd = bootloader_settings.get_mod_boot_args_cmd( - OPTIONS, KERNELS[7]["kernel"] + OPTIONS, KERNELS[7]["kernel"], INFO ) self.assertEqual( "grubby --update-kernel=ALL " + args,