Source code for boom.command

# Copyright (C) 2017 Red Hat, Inc., Bryn M. Reeves <bmr@redhat.com>
#
# command.py - Boom BLS bootloader command interface
#
# This file is part of the boom project.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions
# of the GNU General Public License v.2.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""The ``boom.command`` module provides both the Boom command line
interface infrastructure, and a simple procedural interface to the
``boom`` library modules.

The procedural interface is used by the ``boom`` command line tool,
and may be used by application programs, or interactively in the
Python shell by users who do not require all the features present
in the Boom object API.

In addition the module contains definitions for ``BoomReport``
object types and fields that may be of use in implementing custom
reports using the ``boom.report`` module.
"""
from boom import *
from boom.osprofile import *
from boom.report import *
from boom.bootloader import *
from boom.legacy import *
from boom.config import *

import sys
from os import environ, uname
from os.path import basename
from argparse import ArgumentParser
import logging

#: The environment variable from which to take the location of the
#: ``/boot`` file system.
BOOM_BOOT_PATH_ENV="BOOM_BOOT_PATH"

# Module logging configuration
_log = logging.getLogger(__name__)
_log.set_debug_mask(BOOM_DEBUG_COMMAND)

_log_debug = _log.debug
_log_debug_cmd = _log.debug_masked
_log_info = _log.info
_log_warn = _log.warning
_log_error = _log.error

_default_log_level = logging.WARNING
_console_handler = None

#
# Reporting object types
#

class BoomReportObj(object):
    """BoomReportObj()
        The universal object type used for all reports generated by
        the Boom CLI. Individual fields map to one of the contained
        objects via the ``BoomReportObjType`` object's ``data_fn``
        method. It is an error to attempt to report an object that
        is undefined: the BoomReportObj used for a report must
        contain values for each object type that the specified list
        of fields will attempt to access.

        This allows a single report to include fields from both a
        ``BootEntry`` object and an attached ``OsProfile``.
    """
    be = None
    osp = None

    def __init__(self, boot_entry=None, os_profile=None):
        """Initialise new BoomReportObj objects.

            Construct a new BoomReportObj object containing the
            specified BootEntry and or OsProfile objects.

            :returns: a new BoomReportObj.
            :returntype: ``BoomReportObj``
        """
        self.be = boot_entry
        self.osp = os_profile


#: BootEntry report object type
BR_ENTRY = 1
#: OsProfile report object type
BR_PROFILE = 2
#: BootParams report object type
BR_PARAMS = 4

#: Report object type table for ``boom.command`` reports.
_report_obj_types = [
    BoomReportObjType(
        BR_ENTRY, "Boot loader entries", "entry_", lambda o: o.be),
    BoomReportObjType(
        BR_PROFILE, "OS profiles", "profile_", lambda o: o.osp),
    BoomReportObjType(
        BR_PARAMS, "Boot parameters", "param_", lambda o: o.be.bp)
]

#
# Reporting field definitions
#

#: Fields derived from OsProfile data.
_profile_fields = [
    BoomFieldType(
        BR_PROFILE, "osid", "OsID", "OS identifier", 7,
        REP_SHA, lambda f, d: f.report_sha(d.os_id)),
    BoomFieldType(
        BR_PROFILE, "osname", "Name", "OS name", 24,
        REP_STR, lambda f, d: f.report_str(d.name)),
    BoomFieldType(
        BR_PROFILE, "osshortname", "OsShortName", "OS short name", 12,
        REP_STR, lambda f, d: f.report_str(d.short_name)),
    BoomFieldType(
        BR_PROFILE, "osversion", "OsVersion", "OS version", 10,
        REP_STR, lambda f, d: f.report_str(d.version)),
    BoomFieldType(
        BR_PROFILE, "osversion_id", "VersionID", "Version identifier", 10,
        REP_STR, lambda f, d: f.report_str(d.version_id)),
    BoomFieldType(
        BR_PROFILE, "unamepattern", "UnamePattern", "UTS name pattern", 12,
        REP_STR, lambda f, d: f.report_str(d.uname_pattern)),
    BoomFieldType(
        BR_PROFILE, "kernelpattern", "KernPattern", "Kernel image pattern", 13,
        REP_STR, lambda f, d: f.report_str(d.kernel_pattern)),
    BoomFieldType(
        BR_PROFILE, "initrdpattern", "InitrdPattern", "Initrd pattern", 13,
        REP_STR, lambda f, d: f.report_str(d.initramfs_pattern)),
    BoomFieldType(
        BR_PROFILE, "lvm2opts", "LVM2Opts", "LVM2 options", 12,
        REP_STR, lambda f, d: f.report_str(d.root_opts_lvm2)),
    BoomFieldType(
        BR_PROFILE, "btrfsopts", "BTRFSOpts", "BTRFS options", 13,
        REP_STR, lambda f, d: f.report_str(d.root_opts_btrfs)),
    BoomFieldType(
        BR_PROFILE, "options", "Options", "Kernel options", 24,
        REP_STR, lambda f, d: f.report_str(d.options)),
    BoomFieldType(
        BR_PROFILE, "profilepath", "Profile path", "On-disk profile path", 12,
        REP_STR, lambda f, d: f.report_str(d._profile_path()))
]

_default_profile_fields = "osid,osname,osversion"
_verbose_profile_fields = _default_profile_fields + ",unamepattern,options"

def _int_if_val(val):
    """Return an int if val is defined or None otherwise.

        A TypeError exception is raised if val is defined but does
        not contain a parsable integer value.

        :param val: The value to convert
        :returns: None if val is None or an integer representation of
                  the string val
        :raises: TypeError is val cannot be converted to an int
    """
    return int(val) if val is not None else None

#: Fields derived from BootEntry data.
_entry_fields = [
    BoomFieldType(
        BR_ENTRY, "bootid", "BootID", "Boot identifier", 7,
        REP_SHA, lambda f, d: f.report_sha(d.boot_id)),
    BoomFieldType(
        BR_ENTRY, "title", "Title", "Entry title", 24,
        REP_STR, lambda f, d: f.report_str(d.title)),
    BoomFieldType(
        BR_ENTRY, "options", "Options", "Kernel options", 24,
        REP_STR, lambda f, d: f.report_str(d.options)),
    BoomFieldType(
        BR_ENTRY, "kernel", "Kernel", "Kernel image", 32,
        REP_STR, lambda f, d: f.report_str(d.linux)),
    BoomFieldType(
        BR_ENTRY, "initramfs", "Initramfs", "Initramfs image", 40,
        REP_STR, lambda f, d: f.report_str(d.initrd)),
    BoomFieldType(
        BR_ENTRY, "machineid", "Machine ID", "Machine identifier", 12,
        REP_SHA, lambda f, d: f.report_sha(d.machine_id)),
    BoomFieldType(
        BR_ENTRY, "entrypath", "Entry path", "On-disk entry path", 12,
        REP_STR, lambda f, d: f.report_str(d._entry_path))
]

#: Fields derived from BootParams data
_params_fields = [
    BoomFieldType(
        BR_PARAMS, "version", "Version", "Kernel version", 24,
        REP_STR, lambda f, d: f.report_str(d.version)),
    BoomFieldType(
        BR_PARAMS, "rootdev", "RootDevice", "Root device", 10,
        REP_STR, lambda f, d: f.report_str(d.root_device)),
    BoomFieldType(
        BR_PARAMS, "rootlv", "RootLV", "Root logical volume", 6,
        REP_STR, lambda f, d: f.report_str(d.lvm_root_lv or "")),
    BoomFieldType(
        BR_PARAMS, "subvolpath", "SubvolPath", "BTRFS subvolume path", 10,
        REP_STR, lambda f, d: f.report_str(d.btrfs_subvol_path or "")),
    BoomFieldType(
        BR_PARAMS, "subvolid", "SubvolID", "BTRFS subvolume ID", 8,
        REP_NUM, lambda f, d: f.report_num(_int_if_val(d.btrfs_subvol_id)))
]

_default_entry_fields = "bootid,version,osname,rootdev"
_verbose_entry_fields = (_default_entry_fields + ",options,machineid")


def _subvol_from_arg(subvol):
    """Parse a BTRFS subvolume from a string argument.

        Parse a BTRFS subvolume path or identifier from a command line
        argument string. Numeric values are assumed to be a subvolume ID
        and values beginning with a '/' character are assumed to be a
        subvolume path.

        :param subvol: A subvolume path or ID string
        :returns: (path, id) tuple or (None, None) if neither is found
        :returntype: (str, str)
    """
    if not subvol:
        return (None, None)
    subvol = _parse_btrfs_subvol(subvol)
    if subvol.startswith('/'):
        btrfs_subvol_path = subvol
        btrfs_subvol_id = None
    else:
        btrfs_subvol_path = None
        btrfs_subvol_id = subvol
    return (btrfs_subvol_path, btrfs_subvol_id)


def _str_indent(string, indent):
    """Indent all lines of a multi-line string.

        Indent each line of the multi line string ``string`` to the
        specified indentation level.

        :param string: The string to be indented
        :param indent: The number of characters to indent by
        :returns: str
    """
    outstr = ""
    for line in string.splitlines():
        outstr += indent * ' ' + line + '\n'
    return outstr.rstrip('\n')


def _canonicalize_lv_name(lvname):
    """Canonicalize an LVM2 logical volume name as "VG/LV", removing any
        "/dev/" prefix and return the result as a string.

        The use of "/dev/mapper/VG-LV" names is not supported.
    """
    dev_prefix = DEV_PATTERN % ""
    if lvname.startswith(dev_prefix):
        lvname = lvname[len(dev_prefix):]
    if not '/' in lvname or lvname.count('/') != 1:
        raise ValueError("Root logical volume name must be in VG/LV format.")
    return lvname


def __write_legacy():
    """Synchronise boom boot entries with the configured legacy
        bootloader format.
    """
    config = get_boom_config()
    if config.legacy_enable and config.legacy_sync:
        write_legacy_loader(selection=Selection(), loader=config.legacy_format)


#
# Command driven API: BootEntry and OsProfile management and reporting.
#

#
# BootEntry manipulation
#

[docs]def create_entry(title, version, machine_id, root_device, lvm_root_lv=None, btrfs_subvol_path=None, btrfs_subvol_id=None, osprofile=None, write=True, allow_no_dev=False): """Create new boot loader entry. Create the specified boot entry in the configured loader directory. An error is raised if a matching entry already exists. :param title: the title of the new entry. :param version: the version string for the new entry. :param root_device: the root device path for the new entry. :param lvm_root_lv: an optional LVM2 root logical volume. :param btrfs_subvol_path: an optional BTRFS subvolume path. :param btrfs_subvol_id: an optional BTRFS subvolume id. :param osprofile: The ``OsProfile`` for this entry. :param allow_no_dev: Accept a non-existent or invalid root dev. :returns: a ``BootEntry`` object corresponding to the new entry. :returntype: ``BootEntry`` :raises: ``ValueError`` if either required values are missing or a duplicate entry exists, or``OsError`` if an error occurs while writing the entry file. """ if not title: raise ValueError("Entry title cannot be empty.") if not version: raise ValueError("Entry version cannot be empty.") if not machine_id: raise ValueError("Entry machine_id cannot be empty.") if not root_device: raise ValueError("Entry requires a root_device.") if not osprofile: raise ValueError("Cannot create entry without OsProfile.") btrfs = any([btrfs_subvol_path, btrfs_subvol_id]) bp = BootParams(version, root_device, lvm_root_lv=lvm_root_lv, btrfs_subvol_path=btrfs_subvol_path, btrfs_subvol_id=btrfs_subvol_id) be = BootEntry(title=title, machine_id=machine_id, osprofile=osprofile, boot_params=bp, allow_no_dev=allow_no_dev) if find_entries(Selection(boot_id=be.boot_id)): raise ValueError("Entry already exists (boot_id=%s)." % be.disp_boot_id) if write: be.write_entry() __write_legacy() return be
[docs]def delete_entries(selection=None): """Delete entries matching selection criteria. Delete the specified boot entry or entries from the configured loader directory. If ``boot_id`` is used, or if the criteria specified match exactly one entry, a single entry is removed. If ``boot_id`` is not used, and more than one matching entry is present, all matching entries will be removed. Selection criteria may also be expressed via a Selection object passed to the call using the ``selection`` parameter. On success the number of entries removed is returned. :param selection: A Selection object giving selection criteria for the operation. :returns: the number of entries removed. :returntype: ``int`` """ bes = find_entries(selection=selection) if not bes: raise IndexError("No matching entry found.") deleted = 0 for be in bes: be.delete_entry() deleted += 1 __write_legacy() return deleted
[docs]def clone_entry(selection=None, title=None, version=None, machine_id=None, root_device=None, lvm_root_lv=None, btrfs_subvol_path=None, btrfs_subvol_id=None, osprofile=None, write=True): """Clone an existing boot loader entry. Create the specified boot entry in the configured loader directory by cloning all un-set parameters from the boot entry selected by the ``selection`` argument. An error is raised if a matching entry already exists. :param selection: criteria matching the entry to clone. :param title: the title of the new entry. :param version: the version string for the new entry. :param root_device: the root device path for the new entry. :param lvm_root_lv: an optional LVM2 root logical volume. :param btrfs_subvol_path: an optional BTRFS subvolume path. :param btrfs_subvol_id: an optional BTRFS subvolume id. :param osprofile: The ``OsProfile`` for this entry. :returns: a ``BootEntry`` object corresponding to the new entry. :returntype: ``BootEntry`` :raises: ``ValueError`` if either required values are missing or a duplicate entry exists, or``OsError`` if an error occurs while writing the entry file. """ if not selection.boot_id: raise ValueError("clone requires boot_id") return 1 all_args = (title, version, machine_id, root_device, lvm_root_lv, btrfs_subvol_path, btrfs_subvol_id, osprofile) if not any(all_args): raise ValueError("clone requires one or more of:\ntitle, version, " "machine_id, root_device, lvm_root_lv, " "btrfs_subvol_path, btrfs_subvol_id, osprofile") return 1 bes = find_entries(selection) if len(bes) > 1: raise ValueError("clone criteria must match exactly one entry") return 1 be = bes[0] title = title if title else be.title version = version if version else be.version machine_id = machine_id if machine_id else be.machine_id root_device = root_device if root_device else be.bp.root_device lvm_root_lv = lvm_root_lv if lvm_root_lv else be.bp.lvm_root_lv btrfs_subvol_path = (btrfs_subvol_path if btrfs_subvol_path else be.bp.btrfs_subvol_path) btrfs_subvol_id = (btrfs_subvol_id if btrfs_subvol_id else be.bp.btrfs_subvol_id) osprofile = osprofile if osprofile else be._osp bp = BootParams(version, root_device, lvm_root_lv=lvm_root_lv, btrfs_subvol_path=btrfs_subvol_path, btrfs_subvol_id=btrfs_subvol_id) clone_be = BootEntry(title=title, machine_id=machine_id, osprofile=osprofile, boot_params=bp) if find_entries(Selection(boot_id=clone_be.boot_id)): raise ValueError("Entry already exists (boot_id=%s)." % clone_be.disp_boot_id) if write: clone_be.write_entry() __write_legacy() return clone_be
[docs]def edit_entry(selection=None, title=None, version=None, machine_id=None, root_device=None, lvm_root_lv=None, btrfs_subvol_path=None, btrfs_subvol_id=None, osprofile=None, write=True): """Edit an existing boot loader entry. Modify an existing BootEntry by changing one or more of the entry values or boot parameters. The modified BootEntry is written to disk and returned on success. Modifying a BootEntry causes the entry's boot_id to change, since the ID is based on the values of all configured boot keys. :param selection: A Selection specifying the boot_id to edit :param title: The new entry title :param version: The new entry version :param machine_id: The new machine_id :param root_device: The new root device :param lvm_root_lv: The new LVM root LV :param btrfs_subvol_path: The new BTRFS subvolume path :param btrfs_subvol_id: The new BTRFS subvolme ID :returns: The modified ``BootEntry`` :returntype: ``BootEntry`` """ # Discard all selection criteria but boot_id. selection = Selection(boot_id=selection.boot_id) osp = None if osprofile: os_id = osprofile osps = find_profiles(Selection(os_id=os_id)) if not osps: raise ValueError("No matching profile found: %s" % os_id) if len(osps) > 1: raise ValueError("OS profile identifier '%s' is ambiguous" % os_id) return 1 osp = osps.pop() bes = find_entries(selection=selection) if not bes: raise ValueError("No matching entry found for boot ID %s" % selection.disp_boot_id) return 1 be = bes.pop() # Boot ID will change: clean up old file. be.delete_entry() be._osp = osp or be._osp be.title = title or be.title be.version = version or be.version be.machine_id = machine_id or be.machine_id be.bp.root_device = root_device or be.bp.root_device be.bp.lvm_root_lv = lvm_root_lv or be.bp.lvm_root_lv be.bp.btrfs_subvol_path = btrfs_subvol_path or be.bp.btrfs_subvol_path be.bp.btrfs_subvol_id = btrfs_subvol_id or be.bp.btrfs_subvol_id if write: be.write_entry() __write_legacy() return be
[docs]def list_entries(selection=None): """List entries matching selection criteria. Return a list of ``boom.bootloader.BootEntry`` objects matching the given criteria. Selection criteria may be expressed via a Selection object passed to the call using the ``selection`` parameter. :param selection: A Selection object giving selection criteria for the operation. :returns: A list of matching BootEntry objects. :returntype: list """ bes = find_entries(selection=selection) return bes
# # OsProfile manipulation # def _os_profile_from_file(os_release, uname_pattern, kernel_pattern, initramfs_pattern, root_opts_lvm2, root_opts_btrfs, options): """Create OsProfile from os-release file. Construct a new ``OsProfile`` object from the specified path, substituting each set kwarg parameter with the supplied value in the resulting object. :param os_release: The os-release file to read :param uname_pattern: A replacement uname_pattern value :param kernel_pattern: A replacement kernel_pattern value :param initramfs_pattern: A replacement initramfs_pattern value :param root_opts_lvm2: Replacement LVM2 root options :param root_opts_btrfs: Replacement BTRFS root options :param options: Replacement options string template :returns: A new OsProfile :returntype: OsProfile """ osp = OsProfile.from_os_release_file(os_release) if uname_pattern: osp.uname_pattern = uname_pattern if kernel_pattern: osp.kernel_pattern = kernel_pattern if initramfs_pattern: osp.initramfs_pattern = initramfs_pattern if root_opts_lvm2: osp.root_opts_lvm2 = root_opts_lvm2 if root_opts_btrfs: osp.root_opts_btrfs = root_opts_btrfs if options: osp.options = options if find_profiles(selection=Selection(os_id=osp.os_id)): raise ValueError("Profile already exists (os_id=%s)" % osp.disp_os_id) osp.write_profile() return osp
[docs]def create_profile(name, short_name, version, version_id, uname_pattern=None, kernel_pattern=None, initramfs_pattern=None, root_opts_lvm2=None, root_opts_btrfs=None, options=None, profile_data=None, profile_file=None): """Create new operating system profile. Create the specified OsProfile in the configured profiles directory. OsProfile key values may be specified either by passing individual keyword arguments, or by passing a dictionary of OsProfile key name to value pairs as the ``profile_data`` argument. If a key is present as both a keyword argument and in the ``profile_data`` dictionary, the argument will take precedence. An error is raised if a matching profile already exists. :param name: The name of the new OsProfile :param short_name: The short name of the new OsProfile :param version: The version string of the new OsProfile :param version_id: The version ID string of the new OsProfile :param uname_pattern: A uname pattern to match for this profile :param kernel_pattern: Pattern to generate kernel paths :param initramfs_pattern: Pattern to generate initramfs paths :param root_opts_lvm2: Template options for LVM2 entries :param root_opts_btrfs: Template options for BTRFS entries :param options: Template kernel command line options :param profile_data: Dictionary of profile key:value pairs :returns: an ``OsProfile`` object for the new profile :returntype: ``OsProfile`` :raises: ``ValueError`` if either required values are missing or a duplicate profile exists, or``OsError`` if an error occurs while writing the profile file. """ def _have_key(pd, arg, key): return arg or pd and key in pd if profile_file: return _os_profile_from_file(profile_file, uname_pattern, kernel_pattern, initramfs_pattern, root_opts_lvm2, root_opts_btrfs, options) if not _have_key(profile_data, name, BOOM_OS_NAME): raise ValueError("Profile name cannot be empty.") if not _have_key(profile_data, short_name, BOOM_OS_SHORT_NAME): raise ValueError("Profile short name cannot be empty.") if not _have_key(profile_data, version, BOOM_OS_VERSION): raise ValueError("Profile version cannot be empty.") if not _have_key(profile_data, version_id, BOOM_OS_VERSION_ID): raise ValueError("Profile version ID cannot be empty.") if not profile_data: profile_data = {} # Allow keyword arguments to override if name: profile_data[BOOM_OS_NAME] = name if short_name: profile_data[BOOM_OS_SHORT_NAME] = short_name if version: profile_data[BOOM_OS_VERSION] = version if version_id: profile_data[BOOM_OS_VERSION_ID] = version_id if uname_pattern: profile_data[BOOM_OS_UNAME_PATTERN] = uname_pattern if kernel_pattern: profile_data[BOOM_OS_KERNEL_PATTERN] = kernel_pattern if initramfs_pattern: profile_data[BOOM_OS_INITRAMFS_PATTERN] = initramfs_pattern if root_opts_lvm2: profile_data[BOOM_OS_ROOT_OPTS_LVM2] = root_opts_lvm2 if root_opts_btrfs: profile_data[BOOM_OS_ROOT_OPTS_BTRFS] = root_opts_btrfs if options: profile_data[BOOM_OS_OPTIONS] = options osp = OsProfile(name, short_name, version, version_id, profile_data=profile_data) if find_profiles(selection=Selection(os_id=osp.os_id)): raise ValueError("Profile already exists (os_id=%s)" % osp.disp_os_id) osp.write_profile() return osp
[docs]def delete_profiles(selection=None): """Delete profiles matching selection criteria. Delete the specified OsProfile or profiles from the configured profile directory. If ``os_id`` is used, or if the criteria specified match exactly one profile, a single entry is removed. If ``os_id`` is not used, and more than one matching profile is present, all matching profiles will be removed. Selection criteria are expressed via a Selection object passed to the call using the ``selection`` parameter. On success the number of profiles removed is returned. :param selection: A Selection object giving selection criteria for the operation. :returns: the number of entries removed. :returntype: ``int`` """ osps = find_profiles(selection=selection) if not osps: raise IndexError("No matching profiles found.") deleted = 0 for osp in osps: osp.delete_profile() deleted += 1 return deleted
[docs]def clone_profile(selection=None, name=None, short_name=None, version=None, version_id=None, uname_pattern=None, kernel_pattern=None, initramfs_pattern=None, root_opts_lvm2=None, root_opts_btrfs=None, options=None): """Clone an existing operating system profile. Create the specified profile in the configured profile directory by cloning all un-set parameters from the profile selected by the ``selection`` argument. An error is raised if a matching profile already exists. :param selection: criteria matching the profile to clone. :param name: the name of the new profile. :param short_name: the short name of the new profile. :param version: the version string for the new profile. :param version_id: the version ID string for the new profile. :param uname_pattern: a uname pattern to match this profile. :param root_opts_lvm2: LVM2 root options template. :param root_opts_btrfs: BTRFS root options template. :param options: Kernel options template. :returns: a new ``OsProfile`` object. :returntype: ``OsProfile`` :raises: ``ValueError`` if either required values are missing or a duplicate profile exists, or``OsError`` if an error occurs while writing the profile file. """ if not selection.os_id: raise ValueError("clone requires os_id") return 1 all_args = (name, short_name, version, version_id, uname_pattern, kernel_pattern, initramfs_pattern, root_opts_lvm2, root_opts_btrfs, options) if not any(all_args): raise ValueError('clone requires one or more of:\nname, ' 'short_name, version, version_id, uname_pattern,' 'kernel_pattern, initramfs_pattern, root_opts_lvm2, ' 'root_opts_btrfs, options') return 1 osps = find_profiles(selection) if not(osps): raise ValueError("No matching profile found: %s" % selection.os_id) if len(osps) > 1: raise ValueError("clone criteria must match exactly one profile") return 1 osp = osps.pop() # Clone unset keys name = name if name else osp.name short_name = short_name if short_name else osp.short_name version = version if version else osp.version version_id = version_id if version_id else osp.version_id uname_pattern = uname_pattern if uname_pattern else osp.uname_pattern kernel_pattern = kernel_pattern if kernel_pattern else osp.kernel_pattern root_opts_lvm2 = root_opts_lvm2 if root_opts_lvm2 else osp.root_opts_lvm2 root_opts_btrfs = (root_opts_btrfs if root_opts_btrfs else osp.root_opts_btrfs) options = options if options else osp.options pd = {} if name: pd[BOOM_OS_NAME] = name if short_name: pd[BOOM_OS_SHORT_NAME] = short_name if version: pd[BOOM_OS_VERSION] = version if version_id: pd[BOOM_OS_VERSION_ID] = version_id if uname_pattern: pd[BOOM_OS_UNAME_PATTERN] = uname_pattern if kernel_pattern: pd[BOOM_OS_KERNEL_PATTERN] = kernel_pattern if initramfs_pattern: pd[BOOM_OS_INITRAMFS_PATTERN] = initramfs_pattern if root_opts_lvm2: pd[BOOM_OS_ROOT_OPTS_LVM2] = root_opts_lvm2 if root_opts_btrfs: pd[BOOM_OS_ROOT_OPTS_BTRFS] = root_opts_btrfs if options: pd[BOOM_OS_OPTIONS] = options clone_osp = OsProfile(None, None, None, None, profile_data=pd) if find_profiles(Selection(os_id=clone_osp.os_id)): raise ValueError("Profile already exists (os_id=%s)." % clone_osp.disp_os_id) clone_osp.write_profile() return clone_osp
[docs]def edit_profile(selection=None, uname_pattern=None, kernel_pattern=None, initramfs_pattern=None, root_opts_lvm2=None, root_opts_btrfs=None, options=None): """Edit an existing operating system profile. Modify an existing OsProfile by changing one or more of the profile values. The modified OsProfile is written to disk and returned on success. :param selection: A Selection specifying the boot_id to edit :param uname_pattern: The new uname pattern :param kernel_pattern: The new kernel pattern :param initramfs_pattern: The new initramfs pattern :param root_opts_lvm2: The new LVM2 root options :param root_opts_btrfs: The new BTRFS root options :param options: The new kernel options template :returns: The modified ``OsProfile`` :returntype: ``OsProfile`` """ # Discard all selection criteria but os_id. selection = Selection(os_id=selection.os_id) osp = None osps = find_profiles(Selection(os_id=selection.os_id)) if not osps: raise ValueError("No matching profile found: %s" % selection.os_id) if len(osps) > 1: raise ValueError("OS profile identifier '%s' is ambiguous" % selection.os_id) return 1 osp = osps.pop() osp.uname_pattern = uname_pattern or osp.uname_pattern osp.kernel_pattern = kernel_pattern or osp.kernel_pattern osp.initramfs_pattern = initramfs_pattern or osp.initramfs_pattern osp.root_opts_lvm2 = root_opts_lvm2 or osp.root_opts_lvm2 osp.root_opts_btrfs = root_opts_btrfs or osp.root_opts_btrfs osp.options = options or osp.options osp.write_profile() return osp
[docs]def list_profiles(selection=None): """List operating system profiles matching selection criteria. Return a list of ``boom.osprofile.OsProfile`` objects matching the given criteria. Selection criteria may be expressed via a Selection object passed to the call using the ``selection`` parameter. :param selection: A Selection object giving selection criteria for the operation. :returns: a list of ``OsProfile`` objects. :returntype: list """ osps = find_profiles(selection=selection) return osps
def show_legacy(selection=None, loader=BOOM_LOADER_GRUB1): """Print boot entries in legacy boot loader formats. :param selection: A Selection object giving selection criteria for the operation :param fmt: The name of a legacy boot loader format """ (name, decorator, path) = find_legacy_loader(loader, None) bes = find_entries(selection=selection) for be in bes: gbe = Grub1BootEntry(entry_data=be._entry_data) print(gbe) # # boom command line tool # def _apply_profile_overrides(boot_entry, cmd_args): if cmd_args.linux: boot_entry.linux = cmd_args.linux modified = True if cmd_args.initrd: boot_entry.initrd = cmd_args.initrd modified = True def _create_cmd(cmd_args, select, opts, identifier): """Create entry command handler. Attempt to create a new boot entry using the arguments supplied in ``cmd_args`` and return the command status as an integer. :param cmd_args: Command line arguments for the command :param select: Unused :returns: integer status code returned from ``main()`` """ if not check_bootloader(): _log_warn("Boom configuration not found in grub.cfg") _log_warn("Run 'grub2-mkconfig > /boot/grub2/grub.cfg' to enable") if identifier is not None: print("entry create does not accept <identifier>") return 1 if not cmd_args.version: version = get_uts_release() if not version: print("create requires --version") return 1 else: version = cmd_args.version if not cmd_args.title: print("create requires --title") return 1 else: title = cmd_args.title if not cmd_args.machine_id: # Use host machine-id by default machine_id = _get_machine_id() if not machine_id: print("Could not determine machine_id") return 1 else: machine_id = cmd_args.machine_id if not cmd_args.root_device: print("create requires --root-device") return 1 else: root_device = cmd_args.root_device lvm_root_lv = cmd_args.root_lv if cmd_args.root_lv else None subvol = cmd_args.btrfs_subvolume (btrfs_subvol_path, btrfs_subvol_id) = _subvol_from_arg(subvol) no_dev = cmd_args.no_dev # FIXME: default to host OsProfile if not cmd_args.profile: # Attempt to find a matching OsProfile by version string osp = match_os_profile_by_version(version) if not osp: print("create requires --profile") return 1 os_id = osp.os_id else: os_id = cmd_args.profile osps = find_profiles(Selection(os_id=os_id)) if not osps: print("OsProfile not found: %s" % os_id) return 1 if len(osps) > 1: print("OsProfile ID '%s' is ambiguous" % os_id) return 1 osp = osps[0] try: be = create_entry(title, version, machine_id, root_device, lvm_root_lv=lvm_root_lv, btrfs_subvol_path=btrfs_subvol_path, btrfs_subvol_id=btrfs_subvol_id, osprofile=osp, write=False, allow_no_dev=no_dev) except BoomRootDeviceError as brde: print(brde) print("Creating an entry with no valid root device requires --no-dev") return 1 except ValueError as e: print(e) return 1 _apply_profile_overrides(be, cmd_args) try: be.write_entry() __write_legacy() except Exception as e: if cmd_args.debug: raise print(e) return 1 print("Created entry with boot_id %s:" % be.disp_boot_id) print(_str_indent(str(be), 2)) def _delete_cmd(cmd_args, select, opts, identifier): """Delete entry command handler. Attempt to delete boot entries matching the selection criteria given in ``select``. :param cmd_args: Command line arguments for the command :param select: Selection criteria for the entries to remove :returns: integer status code returned from ``main()`` """ # If a boot_id is given as a command line argument treat it as # a single boot entry to delete and ignore any other criteria. if identifier is not None: select = Selection(boot_id=identifier) if not select or select.is_null(): print("delete requires selection criteria") return 1 if cmd_args.options: fields = cmd_args.options elif cmd_args.verbose: fields = _verbose_entry_fields else: fields = None try: if cmd_args.verbose: print_entries(selection=select, output_fields=fields, opts=opts, sort_keys=cmd_args.sort) nr = delete_entries(select) except (ValueError, IndexError) as e: print(e) return 1 print("Deleted %d entr%s" % (nr, "ies" if nr > 1 else "y")) def _clone_cmd(cmd_args, select, opts, identifier): """Clone entry command handler. Attempt to create a new boot entry by cloning an existing entry. The ``boot_id`` of the supplied ``Selection`` object is used to select the entry to clone. Any set entry values supplied in ``cmd_args`` will be used to modify the newly cloned entry. :param cmd_args: Command line arguments for the command :param select: The ``boot_id`` to clone :returns: integer status code returned from ``main()`` """ if identifier is not None: select = Selection(boot_id=identifier) title = cmd_args.title version = cmd_args.version machine_id = cmd_args.machine_id root_device = cmd_args.root_device lvm_root_lv = cmd_args.root_lv subvol = cmd_args.btrfs_subvolume (btrfs_subvol_path, btrfs_subvol_id) = _subvol_from_arg(subvol) # Discard all selection criteria but boot_id. select = Selection(boot_id=select.boot_id) osp = None if cmd_args.profile: osps = find_profiles(Selection(os_id=cmd_args.profile)) if not osps: print("OsProfile not found: %s" % cmd_args.profile) return 1 if len(osps) > 1: print("OS profile identifier '%s' is ambiguous" % cmd_args.profile) return 1 osp = osps[0] try: be = clone_entry(select, title=title, version=version, machine_id=machine_id, root_device=root_device, lvm_root_lv=lvm_root_lv, btrfs_subvol_path=btrfs_subvol_path, btrfs_subvol_id=btrfs_subvol_id, osprofile=osp) except ValueError as e: print(e) return 1 _apply_profile_overrides(be, cmd_args) try: be.write_entry() __write_legacy() except Exception as e: if cmd_args.debug: raise print(e) return 1 print("Cloned entry with boot_id %s as boot_id %s:" % (select.boot_id, be.disp_boot_id)) print(_str_indent(str(be), 2)) return 0 def _show_cmd(cmd_args, select, opts, identifier): """Show entry command handler. Show the boot entries that match the given selection criteria in BLS boot entry notation: one key per line, with keys and values separated by a single space character. :param cmd_args: Command line arguments for the command :param select: Selection criteria for the entries to show. :returns: integer status code returned from ``main()`` """ if identifier is not None: select = Selection(boot_id=identifier) try: bes = find_entries(select) except ValueError as e: print(e) return 1 first = True for be in bes: ws = "" if first else "\n" be_str = _str_indent(str(be), 2) print("%sBoot Entry (boot_id=%s)\n%s" % (ws, be.disp_boot_id, be_str)) first = False return 0 def _list_cmd(cmd_args, select, opts, identifier): """List entry command handler. List the boot entries that match the given selection criteria as a tabular report, with one boot entry per row. :param cmd_args: Command line arguments for the command :param select: Selection criteria fore the entries to list :returns: integer status code returned from ``main()`` """ if identifier is not None: select = Selection(boot_id=identifier) if cmd_args.options: fields = cmd_args.options elif cmd_args.verbose: fields = _verbose_entry_fields else: fields = None try: print_entries(selection=select, output_fields=fields, opts=opts, sort_keys=cmd_args.sort) except ValueError as e: print(e) return 1 def _edit_cmd(cmd_args, select, opts, identifier): """Edit entry command handler. Attempt to edit an existing boot entry. The ``boot_id`` of the supplied ``Selection`` object is used to select the entry to edit. Any set entry values supplied in ``cmd_args`` will be used to modify the edited entry. :param cmd_args: Command line arguments for the command :param select: The ``boot_id`` of the entry to edit :returns: integer status code returned from ``main()`` """ if identifier is not None: select = Selection(boot_id=identifier) title = cmd_args.title version = cmd_args.version machine_id = cmd_args.machine_id root_device = cmd_args.root_device lvm_root_lv = cmd_args.root_lv subvol = cmd_args.btrfs_subvolume (btrfs_subvol_path, btrfs_subvol_id) = _subvol_from_arg(subvol) try: be = edit_entry(selection=select, title=title, version=version, machine_id=machine_id, root_device=root_device, lvm_root_lv=lvm_root_lv, btrfs_subvol_path=btrfs_subvol_path, btrfs_subvol_id=btrfs_subvol_id) except ValueError as e: print(e) return 1 _apply_profile_overrides(be, cmd_args) try: be.write_entry() __write_legacy() except Exception as e: if cmd_args.debug: raise print(e) return 1 print("Edited entry, boot_id now: %s" % be.disp_boot_id) print(_str_indent(str(be), 2)) return 0 def _create_profile_cmd(cmd_args, select, opts, identifier): """Create profile command handler. Attempt to create a new OS profile using the arguments supplied in ``cmd_args`` and return the command status as an integer. :param cmd_args: Command line arguments for the command :param select: Unused :returns: integer status code returned from ``main()`` """ if identifier is not None: print("profile create does not accept <identifier>") return 1 if cmd_args.os_release or cmd_args.from_host: name = None short_name = None version = None version_id = None release = cmd_args.os_release or "/etc/os-release" else: if not cmd_args.name: print("profile create requires --name") return 1 else: name = cmd_args.name if not cmd_args.short_name: print("profile create requires --short-name") return 1 else: short_name = cmd_args.short_name if not cmd_args.os_version: print("profile create requires --os-version") return 1 else: version = cmd_args.os_version if not cmd_args.os_version_id: print("profile create requires --os-version-id") return 1 else: version_id = cmd_args.os_version_id release = None if not cmd_args.uname_pattern: print("profile create requires --uname-pattern") return 1 try: osp = create_profile(name, short_name, version, version_id, uname_pattern=cmd_args.uname_pattern, kernel_pattern=cmd_args.kernel_pattern, initramfs_pattern=cmd_args.initramfs_pattern, root_opts_lvm2=cmd_args.lvm_opts, root_opts_btrfs=cmd_args.btrfs_opts, options=cmd_args.os_options, profile_file=release) except ValueError as e: print(e) return 1 print("Created profile with os_id %s:" % osp.disp_os_id) print(_str_indent(str(osp), 2)) return 0 def _delete_profile_cmd(cmd_args, select, opts, identifier): """Delete profile command handler. Attempt to delete OS profiles matching the selection criteria given in ``select``. :param cmd_args: Command line arguments for the command :param select: Selection criteria for the profiles to remove :returns: integer status code returned from ``main()`` """ # If an os_id is given as a command line argument treat it as # a single OsProfile to delete and ignore any other criteria. if identifier is not None: select = Selection(os_id=identifier) if not select or select.is_null(): print("profile delete requires selection criteria") return 1 if cmd_args.options: fields = cmd_args.options elif cmd_args.verbose: fields = _verbose_profile_fields else: fields = None try: if cmd_args.verbose: print_profiles(select, output_fields=fields, sort_keys=cmd_args.sort) nr = delete_profiles(select) except (ValueError, IndexError) as e: print(e) return 1 print("Deleted %d profile%s" % (nr, "s" if nr > 1 else "")) def _clone_profile_cmd(cmd_args, select, opts, identifier): """Clone profile command handler. Attempt to create a new OS profile by cloning an existing profile. The ``os_id`` of the supplied ``Selection`` object is used to select the profile to clone. Any set profile values supplied in ``cmd_args`` will be used to modify the newly cloned profile. :param cmd_args: Command line arguments for the command :param select: The ``os_id`` to clone :returns: integer status code returned from ``main()`` """ name = cmd_args.name short_name = cmd_args.short_name version = cmd_args.os_version version_id = cmd_args.os_version_id uname_pattern = cmd_args.uname_pattern kernel_pattern = cmd_args.kernel_pattern initramfs_pattern = cmd_args.initramfs_pattern root_opts_lvm2 = cmd_args.lvm_opts root_opts_btrfs = cmd_args.btrfs_opts options = cmd_args.os_options if identifier is not None: select = Selection(os_id=identifier) # Discard all selection criteria but os_id. select = Selection(os_id=select.os_id) try: be = clone_profile(selection=select, name=name, short_name=short_name, version=version, version_id=version_id, uname_pattern=uname_pattern, kernel_pattern=kernel_pattern, initramfs_pattern=initramfs_pattern, root_opts_lvm2=root_opts_lvm2, root_opts_btrfs=root_opts_btrfs, options=options) except ValueError as e: print(e) return 1 print("Cloned profile with os_id %s as %s:" % (select.os_id, osp.disp_os_id)) print(_str_indent(str(osp), 2)) return 0 def _show_profile_cmd(cmd_args, select, opts, identifier): """Show profile command handler. Show the OS profiles that match the given selection criteria in human readable form. Each matching profile is printed as a multi-line record, with like attributes grouped together on a line. :param cmd_args: Command line arguments for the command :param select: Selection criteria for the profiles to show. :returns: integer status code returned from ``main()`` """ if identifier is not None: select = Selection(os_id=identifier) try: osps = find_profiles(select) except ValueError as e: print(e) return 1 first = True for osp in osps: ws = "" if first else "\n" osp_str = _str_indent(str(osp), 2) print("%sOS Profile (os_id=%s)\n%s" % (ws, osp.disp_os_id, osp_str)) first = False return 0 def _list_profile_cmd(cmd_args, select, opts, identifier): """List profile command handler. List the OS profiles that match the given selection criteria as a tabular report, with one profile per row. :param cmd_args: Command line arguments for the command :param select: Selection criteria fore the profiles to list :returns: integer status code returned from ``main()`` """ if identifier is not None: select = Selection(os_id=identifier) if cmd_args.options: fields = cmd_args.options elif cmd_args.verbose: fields = _verbose_profile_fields else: fields = None try: print_profiles(selection=select, output_fields=fields, opts=opts, sort_keys=cmd_args.sort) except ValueError as e: print(e) return 1 def _edit_profile_cmd(cmd_args, select, opts, identifier): """Edit profile command handler. Attempt to edit an existing OS profile. The ``os_id`` of the supplied ``Selection`` object is used to select the profile to edit. Any set entry values supplied in ``cmd_args`` will be used to modify the edited profile. :param cmd_args: Command line arguments for the command :param select: The ``os_id`` of the profile to edit :returns: integer status code returned from ``main()`` """ if identifier is not None: select = Selection(os_id=identifier) id_keys = (cmd_args.name, cmd_args.short_name, cmd_args.version, cmd_args.os_version_id) if cmd_args.options: print("Invalid argument for 'profile edit': --options") return 1 if any(id_keys): print("Cannot edit name, short_name, version, or version_id:\n" "Use 'clone --profile OS_ID'.") return 1 uname_pattern = cmd_args.uname_pattern kernel_pattern = cmd_args.kernel_pattern initramfs_pattern = cmd_args.initramfs_pattern root_opts_lvm2 = cmd_args.lvm_opts root_opts_btrfs = cmd_args.btrfs_opts options = cmd_args.os_options try: osp = edit_profile(selection=select, uname_pattern=uname_pattern, kernel_pattern=kernel_pattern, initramfs_pattern=initramfs_pattern, root_opts_lvm2=root_opts_lvm2, root_opts_btrfs=root_opts_btrfs, options=options) except ValueError as e: print(e) return 1 print("Edited profile:") print(_str_indent(str(osp), 2)) return 0 def _write_legacy_cmd(cmd_args, select, opts, identifier): if identifier: print("write legacy does not accept a boot_id") return 1 config = get_boom_config() try: clear_legacy_loader() write_legacy_loader(selection=select, loader=config.legacy_format) except Exception as e: print(e) return 1 def _clear_legacy_cmd(cmd_args, select, opts, identifier): """Remove all boom entries from the legacy bootloader configuration. :param cmd_args: Command line arguments for the command :returns: integer status code returned from ``main()`` """ if identifier: print("write legacy does not accept a boot_id") return 1 try: clear_legacy_loader() except BoomLegacyFormatError as e: print(e) return 1 def _show_legacy_cmd(cmd_args, select, opts, identifier): # FIXME: args config = get_boom_config() show_legacy(selection=select, loader=config.legacy_format) boom_usage = """%(prog}s [type] <command> [options]\n\n" [entry] create <title> <version> [--osprofile=os_id] [...] [entry] delete [title|version|boot_id|os_id] [entry] clone --boot-id ID [entry] list [title|version|boot_id|os_id|root_device|machine_id]\n\n [entry] edit [...] profile create <name> <shortname> <version> <versionid> [...] profile delete [...] profile list [...] profile edit [...] legacy write [...] legacy delete [...] """ CREATE_CMD = "create" DELETE_CMD = "delete" CLONE_CMD = "clone" CLEAR_CMD = "clear" SHOW_CMD = "show" LIST_CMD = "list" EDIT_CMD = "edit" WRITE_CMD = "write" ENTRY_TYPE = "entry" PROFILE_TYPE = "profile" LEGACY_TYPE = "legacy" _boom_entry_commands = [ (CREATE_CMD, _create_cmd), (DELETE_CMD, _delete_cmd), (CLONE_CMD, _clone_cmd), (SHOW_CMD, _show_cmd), (LIST_CMD, _list_cmd), (EDIT_CMD, _edit_cmd) ] _boom_profile_commands = [ (CREATE_CMD, _create_profile_cmd), (DELETE_CMD, _delete_profile_cmd), (CLONE_CMD, _clone_profile_cmd), (SHOW_CMD, _show_profile_cmd), (LIST_CMD, _list_profile_cmd), (EDIT_CMD, _edit_profile_cmd) ] _boom_legacy_commands = [ (WRITE_CMD, _write_legacy_cmd), (CLEAR_CMD, _clear_legacy_cmd), (SHOW_CMD, _show_legacy_cmd) ] _boom_command_types = [ (ENTRY_TYPE, _boom_entry_commands), (PROFILE_TYPE, _boom_profile_commands), (LEGACY_TYPE, _boom_legacy_commands) ] def _id_from_arg(cmd_args, cmdtype, cmd): if cmd == CREATE_CMD: if cmdtype == ENTRY_TYPE: return cmd_args.boot_id if cmdtype == PROFILE_TYPE: return cmd_args.profile else: if cmd_args.identifier: return cmd_args.identifier if cmdtype == ENTRY_TYPE: return cmd_args.boot_id if cmdtype == PROFILE_TYPE: return cmd_args.profile return None def _match_cmd_type(cmdtype): for t in _boom_command_types: if t[0].startswith(cmdtype): return t return None def _match_command(cmd, cmds): for c in cmds: if cmd == c[0]: return c return None def _report_opts_from_args(cmd_args): opts = BoomReportOpts() if not cmd_args: return opts if cmd_args.rows: opts.columns_as_rows = True if cmd_args.separator: opts.separator = cmd_args.separator if cmd_args.name_prefixes: opts.field_name_prefix = "BOOM_" opts.unquoted = False opts.aligned = False if cmd_args.no_headings: opts.headings = False return opts def get_uts_release(): return uname()[2] def setup_logging(cmd_args): global _console_handler level = _default_log_level if cmd_args.verbose and cmd_args.verbose > 1: level = logging.DEBUG elif cmd_args.verbose and cmd_args.verbose > 0: level = logging.INFO # Configure the package-level logger boom_log = logging.getLogger("boom") formatter = logging.Formatter('%(levelname)s - %(message)s') boom_log.setLevel(level) _console_handler = logging.StreamHandler() _console_handler.setLevel(level) _console_handler.setFormatter(formatter) boom_log.addHandler(_console_handler) def shutdown_logging(): logging.shutdown() def set_debug(debug_arg): if not debug_arg: return mask_map = { "profile": BOOM_DEBUG_PROFILE, "entry": BOOM_DEBUG_ENTRY, "report": BOOM_DEBUG_REPORT, "command": BOOM_DEBUG_COMMAND, "all": BOOM_DEBUG_ALL } mask = 0 for name in debug_arg.split(','): if name not in mask_map: raise ValueError("Unknown debug mask: %s" % name) mask |= mask_map[name] set_debug_mask(mask) def main(args): global _boom_entry_commands, _boom_profile_commands, _boom_command_types parser = ArgumentParser(prog=basename(args[0]), description="Boom Boot Manager") # Default type is boot entry. if len(args) > 1 and _match_command(args[1], _boom_entry_commands): args.insert(1, "entry") parser.add_argument("type", metavar="[TYPE]", type=str, help="The command type to run: profile or entry", action="store") parser.add_argument("command", metavar="COMMAND", type=str, action="store", help="The command to run: create, delete, list, edit, " "clone, show") parser.add_argument("identifier", metavar="ID", type=str, action="store", help="An optional profile or boot identifier to " "operate on", nargs="?", default=None) parser.add_argument("-b", "--boot-id", "--bootid", metavar="BOOT_ID", type=str, help="The BOOT_ID of a boom boot entry") parser.add_argument("--boot-dir", "--bootdir", metavar="PATH", type=str, help="The path to the /boot file system") parser.add_argument("-B", "--btrfs-subvolume", "--btrfssubvolume", metavar="SUBVOL", type=str, help="The path or ID of a BTRFS subvolume") parser.add_argument("--btrfs-opts", "--btrfsopts", metavar="OPTS", type=str, help="A template option string for BTRFS devices") parser.add_argument("-c", "--config", metavar="FILE", type=str, help="Path to a boom configuration file", default=None) parser.add_argument("--debug", metavar="DEBUGOPTS", type=str, help="A list of debug options to enable") parser.add_argument("-e", "--efi", metavar="IMG", type=str, help="An executable EFI application image") parser.add_argument("-H", "--from-host", "--fromhost", help="Take os-release values from the running host", action="store_true") parser.add_argument("-i", "--initrd", metavar="IMG", type=str, help="A linux initrd image path") parser.add_argument("-k", "--kernel-pattern", "--kernelpattern", metavar="PATTERN", type=str, help="A pattern for generating kernel paths") parser.add_argument("-l", "--linux", metavar="IMG", type=str, help="A linux kernel image path") parser.add_argument("-L", "--root-lv", "--rootlv", metavar="LV", type=str, help="An LVM2 root logical volume") parser.add_argument("--lvm-opts", "--lvmopts", metavar="OPTS", type=str, help="A template option string for LVM2 devices") parser.add_argument("-m", "--machine-id", "--machineid", metavar="MACHINE_ID", type=str, help="The machine_id value to use") parser.add_argument("-n", "--name", metavar="OSNAME", type=str, help="The name of a Boom OsProfile") parser.add_argument("--name-prefixes", "--nameprefixes", help="Add a prefix to report field names", action='store_true'), parser.add_argument("--no-headings", "--noheadings", action='store_true', help="Suppress output of report headings"), parser.add_argument("--no-dev", "--nodev", action='store_true', help="Disable checks for a valid root device") parser.add_argument("-o", "--options", metavar="FIELDS", type=str, help="Specify which fields to display") parser.add_argument("--os-version", "--osversion", metavar="OSVERSION", help="A Boom OsProfile version", type=str) parser.add_argument("-O", "--sort", metavar="SORTFIELDS", type=str, help="Specify which fields to sort by") parser.add_argument("-I", "--os-version-id", "--osversionid", help="A Boom OsProfile version ID", metavar="OSVERSIONID", type=str) parser.add_argument("--os-options", "--osoptions", metavar="OPTIONS", help="A Boom OsProfile options template", type=str) parser.add_argument("--os-release", "--osrelease", metavar="OSRELEASE", help="Path to an os-release file", type=str) parser.add_argument("-p", "--profile", metavar="OS_ID", type=str, help="A boom operating system profile " "identifier") parser.add_argument("-r", "--root-device", "--rootdevice", metavar="ROOT", help="The root device for a boot entry", type=str) parser.add_argument("-R", "--initramfs-pattern", "--initramfspattern", type=str, help="A pattern for generating initramfs " "paths", metavar="PATTERN") parser.add_argument("--rows", action="store_true", help="Output report columnes as rows") parser.add_argument("--separator", metavar="SEP", type=str, help="Report field separator") parser.add_argument("-s", "--short-name", "--shortname", help="A Boom OsProfile short name", metavar="OSSHORTNAME", type=str) parser.add_argument("-t", "--title", metavar="TITLE", type=str, help="The title of a boom boot entry") parser.add_argument("-u", "--uname-pattern", "--unamepattern", help="A Boom OsProfile uname pattern", metavar="PATTERN", type=str) parser.add_argument("-V", "--verbose", help="Enable verbose ouput", action="count") parser.add_argument("-v", "--version", metavar="VERSION", type=str, help="The kernel version of a boom " "boot entry") cmd_args = parser.parse_args() try: set_debug(cmd_args.debug) except ValueError as e: print(e) return 1 setup_logging(cmd_args) cmd_type = _match_cmd_type(cmd_args.type) if cmd_args.boot_dir or BOOM_BOOT_PATH_ENV in environ: boot_path = cmd_args.boot_dir or environ[BOOM_BOOT_PATH_ENV] set_boot_path(boot_path) set_boom_config_path("boom.conf") if cmd_args.config: set_boom_config_path(cmd_args.config) load_boom_config(path=cmd_args.config) # Parse an LV name from root_lv and re-write the root_device if found if cmd_args.root_lv: try: root_lv = _canonicalize_lv_name(cmd_args.root_lv) except ValueError as e: print(e) print("Invalid logical volume name: '%s'" % cmd_args.root_lv) return 1 root_device = DEV_PATTERN % root_lv if cmd_args.root_device and cmd_args.root_device != root_device: print("Options --root-lv %s and --root-device %s do not match." % (root_lv, root_device)) return 1 cmd_args.root_device = root_device cmd_args.root_lv = root_lv # Try parsing an LV name from root_device and rewrite root_lv if found elif cmd_args.root_device: try: root_lv = _canonicalize_lv_name(cmd_args.root_device) cmd_args.root_lv = root_lv except ValueError: # No valid VG name pass if not cmd_type: print("Unknown command type: %s" % cmd_args.type) return 1 type_cmds = cmd_type[1] command = _match_command(cmd_args.command, type_cmds) if not command: print("Unknown command: %s %s" % (cmd_type[0], cmd_args.command)) return 1 select = Selection.from_cmd_args(cmd_args) opts = _report_opts_from_args(cmd_args) identifier = _id_from_arg(cmd_args, cmd_type[0], command[0]) status = 1 if cmd_args.debug: status = command[1](cmd_args, select, opts, identifier) else: try: status = command[1](cmd_args, select, opts, identifier) except Exception as e: _log_error("Command failed: %s" % e) shutdown_logging() sys.exit(status) __all__ = [ # BootEntry manipulation 'create_entry', 'delete_entries', 'clone_entry', 'edit_entry', 'list_entries', 'print_entries', # OsProfile manipulation 'create_profile', 'delete_profiles', 'clone_profile', 'edit_profile', 'list_profiles', 'print_profiles', ] # vim: set et ts=4 sw=4 :