Source code for cobbler.items.profile

"""
Copyright 2006-2009, Red Hat, Inc and Others
Michael DeHaan <michael.dehaan AT gmail>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU 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
"""
import uuid
from typing import Optional, Union, Dict, Any, List

from cobbler import autoinstall_manager, enums, utils, validate
from cobbler.cexceptions import CX
from cobbler.decorator import InheritableProperty, LazyProperty
from cobbler.items import item
from cobbler.items.distro import Distro


[docs] class Profile(item.Item): """ A Cobbler profile object. """ TYPE_NAME = "profile" COLLECTION_TYPE = "profile" def __init__(self, api, *args, **kwargs): """ :param api: The Cobbler API object which is used for resolving information. :param args: :param kwargs: """ super().__init__(api) # Prevent attempts to clear the to_dict cache before the object is initialized. self._has_initialized = False self._template_files = {} self._autoinstall = enums.VALUE_INHERITED self._boot_loaders: Union[list, str] = enums.VALUE_INHERITED self._dhcp_tag = "" self._distro = "" self._enable_ipxe: Union[str, bool] = enums.VALUE_INHERITED self._enable_menu: Union[str, bool] = enums.VALUE_INHERITED self._name_servers = api.settings().default_name_servers self._name_servers_search = api.settings().default_name_servers_search self._next_server_v4 = enums.VALUE_INHERITED self._next_server_v6 = enums.VALUE_INHERITED self._filename = "" self._proxy = enums.VALUE_INHERITED self._redhat_management_key = enums.VALUE_INHERITED self._repos = [] self._server = enums.VALUE_INHERITED self._menu = "" self._virt_auto_boot: Union[str, bool] = enums.VALUE_INHERITED self._virt_bridge = enums.VALUE_INHERITED self._virt_cpus: int = 1 self._virt_disk_driver = enums.VirtDiskDrivers.INHERITED self._virt_file_size: Union[str, float] = enums.VALUE_INHERITED self._virt_path = "" self._virt_ram: Union[str, int] = enums.VALUE_INHERITED self._virt_type: Union[str, enums.VirtType] = enums.VirtType.INHERITED # Overwrite defaults from item.py self._boot_files: Union[Dict[Any, Any], str] = enums.VALUE_INHERITED self._fetchable_files: Union[Dict[Any, Any], str] = enums.VALUE_INHERITED self._autoinstall_meta: Union[Dict[Any, Any], str] = enums.VALUE_INHERITED self._kernel_options: Union[Dict[Any, Any], str] = enums.VALUE_INHERITED self._kernel_options_post: Union[Dict[Any, Any], str] = enums.VALUE_INHERITED self._mgmt_classes: Union[List[Any], str] = enums.VALUE_INHERITED self._mgmt_parameters: Union[Dict[Any, Any], str] = enums.VALUE_INHERITED # Use setters to validate settings self.virt_disk_driver = api.settings().default_virt_disk_driver self.virt_type = api.settings().default_virt_type if len(kwargs) > 0: self.from_dict(kwargs) if not self._has_initialized: self._has_initialized = True def __getattr__(self, name): if name == "kickstart": return self.autoinstall elif name == "ks_meta": return self.autoinstall_meta raise AttributeError( 'Attribute "%s" did not exist on object type Profile.' % name ) # # override some base class methods first (item.Item) #
[docs] def make_clone(self): """ Clone this file object. Please manually adjust all value yourself to make the cloned object unique. :return: The cloned instance of this object. """ _dict = self.to_dict() _dict.pop("uid", None) cloned = Profile(self.api) cloned.from_dict(_dict) return cloned
[docs] def check_if_valid(self): """ Check if the profile is valid. This checks for an existing name and a distro as a conceptual parent. :raises CX: In case the distro or name is not present. """ # name validation super().check_if_valid() if not self.inmemory: return # distro validation distro = self.get_conceptual_parent() if distro is None: raise CX("Error with profile %s - distro is required" % self.name)
[docs] def from_dict(self, dictionary: dict): """ Initializes the object with attributes from the dictionary. :param dictionary: The dictionary with values. """ old_has_initialized = self._has_initialized self._has_initialized = False if "name" in dictionary: self.name = dictionary["name"] if "parent" in dictionary: self.parent = dictionary["parent"] if "distro" in dictionary: self.distro = dictionary["distro"] self._remove_depreacted_dict_keys(dictionary) self._has_initialized = old_has_initialized super().from_dict(dictionary)
[docs] def find_match_single_key( self, data: Dict[str, Any], key: str, value: Any, no_errors: bool = False ) -> bool: """ Look if the data matches or not. This is an alternative for ``find_match()``. :param data: The data to search through. :param key: The key to look for int the item. :param value: The value for the key. :param no_errors: How strict this matching is. :return: Whether the data matches or not. """ # special case for profile, since arch is a derived property from the parent distro if key == "arch": if self.arch: return self.arch.value == value return value is None return super().find_match_single_key(data, key, value, no_errors)
# # specific methods for item.Profile # @LazyProperty def arch(self): """ This represents the architecture of a profile. It is read only. :getter: ``None`` or the parent architecture. """ # FIXME: This looks so wrong. It cries: Please open a bug for me! parent = self.logical_parent if parent is not None: return parent.arch return None @LazyProperty def distro(self): """ The parent distro of a profile. This is not representing the Distro but the id of it. This is a required property, if saved to the disk, with the exception if this is a subprofile. :return: The distro object or None. """ if not self._distro: return None return self.api.distros().find(name=self._distro) @distro.setter def distro(self, distro_name: str): """ Sets the distro. This must be the name of an existing Distro object in the Distros collection. :param distro_name: The name of the distro. """ if not isinstance(distro_name, str): raise TypeError("distro_name needs to be of type str") if not distro_name: self._distro = "" return distro = self.api.distros().find(name=distro_name) if distro is None: raise ValueError('distribution "%s" not found' % distro_name) self._distro = distro_name self.depth = distro.depth + 1 # reset depth if previously a subprofile and now top-level @InheritableProperty def name_servers(self) -> list: """ Represents the list of nameservers to set for the profile. :getter: The nameservers. :setter: Comma delimited ``str`` or list with the nameservers. """ return self._resolve("name_servers") @name_servers.setter def name_servers(self, data: list): """ Set the DNS servers. :param data: string or list of nameservers """ self._name_servers = validate.name_servers(data) @InheritableProperty def name_servers_search(self) -> list: """ Represents the list of DNS search paths. :getter: The list of DNS search paths. :setter: Comma delimited ``str`` or list with the nameservers search paths. """ return self._resolve("name_servers_search") @name_servers_search.setter def name_servers_search(self, data: list): """ Set the DNS search paths. :param data: string or list of search domains """ self._name_servers_search = validate.name_servers_search(data) @InheritableProperty def proxy(self) -> str: """ Override the default external proxy which is used for accessing the internet. :getter: Returns the default one or the specific one for this repository. :setter: May raise a ``TypeError`` in case the wrong value is given. """ return self._resolve("proxy_url_int") @proxy.setter def proxy(self, proxy: str): """ Setter for the proxy setting of the repository. :param proxy: The new proxy which will be used for the repository. :raises TypeError: In case the new value is not of type ``str``. """ if not isinstance(proxy, str): raise TypeError("Field proxy of object profile needs to be of type str!") self._proxy = proxy @InheritableProperty def enable_ipxe(self) -> bool: r""" Sets whether or not the profile will use iPXE for booting. :getter: If set to inherit then this returns the parent value, otherwise it returns the real value. :setter: May throw a ``TypeError`` in case the new value cannot be cast to ``bool``. """ return self._resolve("enable_ipxe") @enable_ipxe.setter def enable_ipxe(self, enable_ipxe: Union[str, bool]): r""" Setter for the ``enable_ipxe`` property. :param enable_ipxe: New boolean value for enabling iPXE. :raises TypeError: In case after the conversion, the new value is not of type ``bool``. """ if enable_ipxe == enums.VALUE_INHERITED: self._enable_ipxe = enums.VALUE_INHERITED return enable_ipxe = utils.input_boolean(enable_ipxe) if not isinstance(enable_ipxe, bool): raise TypeError("enable_ipxe needs to be of type bool") self._enable_ipxe = enable_ipxe @InheritableProperty def enable_menu(self) -> bool: """ Sets whether or not the profile will be listed in the default PXE boot menu. This is pretty forgiving for YAML's sake. :getter: The value resolved from the defaults or the value specific to the profile. :setter: May raise a ``TypeError`` in case the boolean could not be converted. """ return self._resolve("enable_menu") @enable_menu.setter def enable_menu(self, enable_menu: Union[str, bool]): """ Setter for the ``enable_menu`` property. :param enable_menu: New boolean value for enabling the menu. :raises TypeError: In case the boolean could not be converted successfully. """ if enable_menu == enums.VALUE_INHERITED: self._enable_menu = enums.VALUE_INHERITED return enable_menu = utils.input_boolean(enable_menu) if not isinstance(enable_menu, bool): raise TypeError("enable_menu needs to be of type bool") self._enable_menu = enable_menu @LazyProperty def dhcp_tag(self) -> str: """ Represents the VLAN tag the DHCP Server is in/answering to. :getter: The VLAN tag or nothing if a system with the profile should not be in a VLAN. :setter: The new VLAN tag. """ return self._dhcp_tag @dhcp_tag.setter def dhcp_tag(self, dhcp_tag: str): r""" Setter for the ``dhcp_tag`` property. :param dhcp_tag: The new VLAN tag. :raises TypeError: Raised in case the tag was not of type ``str``. """ if not isinstance(dhcp_tag, str): raise TypeError("Field dhcp_tag of object profile needs to be of type str!") self._dhcp_tag = dhcp_tag @InheritableProperty def server(self) -> str: """ Represents the hostname the Cobbler server is reachable by a client. .. note:: This property can be set to ``<<inherit>>``. :getter: The hostname of the Cobbler server. :setter: May raise a ``TypeError`` in case the new value is not a ``str``. """ return self._resolve("server") @server.setter def server(self, server: str): """ Setter for the server property. :param server: The str with the new value for the server property. :raises TypeError: In case the new value was not of type ``str``. """ if not isinstance(server, str): raise TypeError("Field server of object profile needs to be of type str!") self._server = server @LazyProperty def next_server_v4(self) -> str: """ Represents the next server for IPv4. :getter: The IP for the next server. :setter: May raise a ``TypeError`` if the new value is not of type ``str``. """ return self._resolve("next_server_v4") @next_server_v4.setter def next_server_v4(self, server: str = ""): """ Setter for the next server value. :param server: The address of the IPv4 next server. Must be a string or ``enums.VALUE_INHERITED``. :raises TypeError: In case server is no string. """ if not isinstance(server, str): raise TypeError("Server must be a string.") if server == enums.VALUE_INHERITED: self._next_server_v4 = enums.VALUE_INHERITED else: self._next_server_v4 = validate.ipv4_address(server) @LazyProperty def next_server_v6(self) -> str: r""" Represents the next server for IPv6. :getter: The IP for the next server. :setter: May raise a ``TypeError`` if the new value is not of type ``str``. """ return self._resolve("next_server_v6") @next_server_v6.setter def next_server_v6(self, server: str = ""): """ Setter for the next server value. :param server: The address of the IPv6 next server. Must be a string or ``enums.VALUE_INHERITED``. :raises TypeError: In case server is no string. """ if not isinstance(server, str): raise TypeError("Server must be a string.") if server == enums.VALUE_INHERITED: self._next_server_v6 = enums.VALUE_INHERITED else: self._next_server_v6 = validate.ipv6_address(server) @InheritableProperty def filename(self) -> str: """ The filename which is fetched by the client from TFTP. :getter: Either the default/inherited one, or the one specific to this profile. :setter: The new filename which is fetched on boot. May raise a ``TypeError`` when the wrong type was given. """ return self._resolve("filename") @filename.setter def filename(self, filename: str): """ The setter for the ``filename`` property. :param filename: The new ``filename`` for the profile. :raises TypeError: In case the new value was not of type ``str``. """ if not isinstance(filename, str): # type: ignore raise TypeError("Field filename of object profile needs to be of type str!") parent = self.parent if filename == enums.VALUE_INHERITED and parent is None: filename = "" if not filename: if parent: filename = enums.VALUE_INHERITED else: filename = "" self._filename = filename @LazyProperty def autoinstall(self) -> str: """ Represents the automatic OS installation template file path, this must be a local file. :getter: Either the inherited name or the one specific to this profile. :setter: The name of the new autoinstall template is validated. The path should come in the format of a ``str``. """ return self._resolve("autoinstall") @autoinstall.setter def autoinstall(self, autoinstall: str): """ Setter for the ``autoinstall`` property. :param autoinstall: local automatic installation template path """ autoinstall_mgr = autoinstall_manager.AutoInstallationManager(self.api) self._autoinstall = autoinstall_mgr.validate_autoinstall_template_file_path( autoinstall ) @InheritableProperty def virt_auto_boot(self) -> bool: """ Whether the VM should be booted when booting the host or not. .. note:: This property can be set to ``<<inherit>>``. :getter: ``True`` means autoboot is enabled, otherwise VM is not booted automatically. :setter: The new state for the property. """ return self._resolve("virt_auto_boot") @virt_auto_boot.setter def virt_auto_boot(self, num: bool): """ Setter for booting a virtual machine automatically. :param num: The new value for whether to enable it or not. """ if num == enums.VALUE_INHERITED: self._virt_auto_boot = enums.VALUE_INHERITED return self._virt_auto_boot = validate.validate_virt_auto_boot(num) @LazyProperty def virt_cpus(self) -> int: """ The amount of vCPU cores used in case the image is being deployed on top of a VM host. :getter: The cores used. :setter: The new number of cores. """ return self._virt_cpus @virt_cpus.setter def virt_cpus(self, num: Union[int, str]): """ Setter for the number of virtual CPU cores to assign to the virtual machine. :param num: The number of cpu cores. """ self._virt_cpus = validate.validate_virt_cpus(num) @InheritableProperty def virt_file_size(self) -> float: r""" The size of the image and thus the usable size for the guest. .. warning:: There is a regression which makes the usage of multiple disks not possible right now. This will be fixed in a future release. .. note:: This property can be set to ``<<inherit>>``. :getter: The size of the image(s) in GB. :setter: The float with the new size in GB. """ return self._resolve("virt_file_size") @virt_file_size.setter def virt_file_size(self, num: Union[str, int, float]): """ Setter for the size of the virtual image size. :param num: The new size of the image. """ self._virt_file_size = validate.validate_virt_file_size(num) @InheritableProperty def virt_disk_driver(self) -> enums.VirtDiskDrivers: """ The type of disk driver used for storing the image. .. note:: This property can be set to ``<<inherit>>``. :getter: The enum type representation of the disk driver. :setter: May be a ``str`` with the name of the disk driver or from the enum type directly. """ return self._resolve_enum("virt_disk_driver", enums.VirtDiskDrivers) @virt_disk_driver.setter def virt_disk_driver(self, driver: str): """ Setter for the virtual disk driver that will be used. :param driver: The new driver. """ self._virt_disk_driver = enums.VirtDiskDrivers.to_enum(driver) @InheritableProperty def virt_ram(self) -> int: """ The amount of RAM given to the guest in MB. .. note:: This property can be set to ``<<inherit>>``. :getter: The amount of RAM currently assigned to the image. :setter: The new amount of ram. Must be an integer. """ return self._resolve("virt_ram") @virt_ram.setter def virt_ram(self, num: Union[str, int]): """ Setter for the virtual RAM used for the VM. :param num: The number of RAM to use for the VM. """ self._virt_ram = validate.validate_virt_ram(num) @InheritableProperty def virt_type(self) -> enums.VirtType: """ The type of image used. .. note:: This property can be set to ``<<inherit>>``. :getter: The value of the virtual machine. :setter: May be of the enum type or a str which is then converted to the enum type. """ return self._resolve_enum("virt_type", enums.VirtType) @virt_type.setter def virt_type(self, vtype: str): """ Setter for the virtual machine type. :param vtype: May be on out of "qemu", "kvm", "xenpv", "xenfv", "vmware", "vmwarew", "openvz" or "auto". """ self._virt_type = enums.VirtType.to_enum(vtype) @InheritableProperty def virt_bridge(self) -> str: """ Represents the name of the virtual bridge to use. .. note:: This property can be set to ``<<inherit>>``. :getter: Either the default name for the bridge or the specific one for this profile. :setter: The new name. Does not overwrite the default one. """ return self._resolve("virt_bridge") @virt_bridge.setter def virt_bridge(self, vbridge: str): """ Setter for the name of the virtual bridge to use. :param vbridge: The name of the virtual bridge to use. """ self._virt_bridge = validate.validate_virt_bridge(vbridge) @LazyProperty def virt_path(self) -> str: """ The path to the place where the image will be stored. :getter: The path to the image. :setter: The new path for the image. """ return self._virt_path @virt_path.setter def virt_path(self, path: str): """ Setter for the ``virt_path`` property. :param path: The path to where the image will be stored. """ self._virt_path = validate.validate_virt_path(path) @LazyProperty def repos(self) -> list: """ The repositories to add once the system is provisioned. :getter: The names of the repositories the profile has assigned. :setter: The new names of the repositories for the profile. Validated against existing repositories. """ return self._repos @repos.setter def repos(self, repos: list): """ Setter of the repositories for the profile. :param repos: The new repositories which will be set. """ self._repos = validate.validate_repos(repos, self.api, bypass_check=False) @InheritableProperty def redhat_management_key(self) -> str: """ Getter of the redhat management key of the profile or it's parent. .. note:: This property can be set to ``<<inherit>>``. :getter: Returns the redhat_management_key of the profile. :setter: May raise a ``TypeError`` in case of a validation error. """ return self._resolve("redhat_management_key") @redhat_management_key.setter def redhat_management_key(self, management_key: str): """ Setter of the redhat management key. :param management_key: The value may be reset by setting it to None. """ if not isinstance(management_key, str): raise TypeError("Field management_key of object profile is of type str!") if not management_key: self._redhat_management_key = enums.VALUE_INHERITED self._redhat_management_key = management_key @InheritableProperty def boot_loaders(self) -> list: """ This represents all boot loaders for which Cobbler will try to generate bootloader configuration for. .. note:: This property can be set to ``<<inherit>>``. :getter: The bootloaders. :setter: The new bootloaders. Will be validates against a list of well known ones. """ return self._resolve("boot_loaders") @boot_loaders.setter def boot_loaders(self, boot_loaders: list): """ Setter of the boot loaders. :param boot_loaders: The boot loaders for the profile. :raises ValueError: In case the supplied boot loaders were not a subset of the valid ones. """ if boot_loaders == enums.VALUE_INHERITED: self._boot_loaders = enums.VALUE_INHERITED return if boot_loaders: boot_loaders_split = utils.input_string_or_list(boot_loaders) parent = self.parent if parent is None: parent = self.distro if parent is not None: parent_boot_loaders = parent.boot_loaders else: self.logger.warning( 'Parent of profile "%s" could not be found for resolving the parent bootloaders.', self.name, ) parent_boot_loaders = [] if not set(boot_loaders_split).issubset(parent_boot_loaders): raise CX( 'Error with profile "%s" - not all boot_loaders are supported (given: "%s"; supported:' '"%s")' % (self.name, str(boot_loaders_split), str(parent_boot_loaders)) ) self._boot_loaders = boot_loaders_split else: self._boot_loaders = [] @LazyProperty def menu(self) -> str: r""" Property to represent the menu which this image should be put into. :getter: The name of the menu or an emtpy str. :setter: Should only be the name of the menu not the object. May raise ``CX`` in case the menu does not exist. """ return self._menu @menu.setter def menu(self, menu: str): """ Setter for the menu property. :param menu: The menu for the image. :raises CX: In case the menu to be set could not be found. """ if not isinstance(menu, str): raise TypeError("Field menu of object profile needs to be of type str!") if menu and menu != "": menu_list = self.api.menus() if not menu_list.find(name=menu): raise CX("menu %s not found" % menu) self._menu = menu @LazyProperty def display_name(self) -> str: """ Returns the display name. :getter: Returns the display name for the boot menu. :setter: Sets the display name for the boot menu. """ return self._display_name @display_name.setter def display_name(self, display_name: str): """ Setter for the display_name of the item. :param display_name: The new display_name. If ``None`` the display_name will be set to an emtpy string. """ self._display_name = display_name