Source code for cobbler.items.image

"""
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 Union

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


[docs] class Image(item.Item): """ A Cobbler Image. Tracks a virtual or physical image, as opposed to a answer file (autoinst) led installation. """ TYPE_NAME = "image" COLLECTION_TYPE = "image" def __init__(self, api, *args, **kwargs): """ Constructor :param api: The Cobbler API object which is used for resolving information. :param args: The arguments which should be passed additionally to the base Item class constructor. :param kwargs: The keyword arguments which should be passed additionally to the base Item class constructor. """ super().__init__(api) # Prevent attempts to clear the to_dict cache before the object is initialized. self._has_initialized = False self._arch = enums.Archs.X86_64 self._autoinstall = enums.VALUE_INHERITED self._breed = "" self._file = "" self._image_type = enums.ImageTypes.DIRECT self._network_count = 0 self._os_version = "" self._boot_loaders = [] self._menu = "" self._virt_auto_boot = False self._virt_bridge = "" self._virt_cpus = 1 self._virt_disk_driver = enums.VirtDiskDrivers.RAW self._virt_file_size = 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 self._supported_boot_loaders = [] 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 raise AttributeError("Attribute \"%s\" did not exist on object type Image." % name) # # override some base class methods first (item.Item) #
[docs] def make_clone(self): """ Clone this image 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 = Image(self.api) cloned.from_dict(_dict) cloned.uid = uuid.uuid4().hex return cloned
[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"] self._remove_depreacted_dict_keys(dictionary) self._has_initialized = old_has_initialized super().from_dict(dictionary)
# # specific methods for item.Image # @LazyProperty def arch(self) -> enums.Archs: """ Represents the architecture the image has. If deployed to a physical host this should be enforced, a virtual image may be deployed on a host with any architecture. :getter: The current architecture. Default is ``X86_64``. :setter: Should be of the enum type or str. May raise an exception in case the architecture is not known to Cobbler. """ return self._arch @arch.setter def arch(self, arch: Union[str, enums.Archs]): """ The field is mainly relevant to PXE provisioning. See comments for arch property in distro.py, this works the same. :param arch: The new architecture to set. """ self._arch = enums.Archs.to_enum(arch) @InheritableProperty def autoinstall(self) -> str: """ Property for the automatic installation file path, this must be a local file. It may not make sense for images to have automatic installation templates. It really doesn't. However if the image type is 'iso' koan can create a virtual floppy and shove an answer file on it, to script an installation. This may not be a automatic installation template per se, it might be a Windows answer file (SIF) etc. This property can inherit from a parent. Which is actually the default value. :getter: The path relative to the template directory. :setter: The location of the template relative to the template base directory. """ return self._resolve("autoinstall") @autoinstall.setter def autoinstall(self, autoinstall: str): """ Set the automatic installation file path, this must be a local file. :param autoinstall: local automatic installation template file path """ autoinstall_mgr = autoinstall_manager.AutoInstallationManager(self.api) self._autoinstall = autoinstall_mgr.validate_autoinstall_template_file_path(autoinstall) @LazyProperty def file(self) -> str: """ Stores the image location. This should be accessible on all nodes that need to access it. Format: can be one of the following: * username:password@hostname:/path/to/the/filename.ext * username@hostname:/path/to/the/filename.ext * hostname:/path/to/the/filename.ext * /path/to/the/filename.ext :getter: The path to the image location or an emtpy string. :setter: May raise a TypeError or SyntaxError in case the validation of the location fails. """ return self._file @file.setter def file(self, filename: str): """ The setter for the image location. :param filename: The location where the image is stored. :raises SyntaxError: In case a protocol was found. """ if not isinstance(filename, str): raise TypeError("file must be of type str to be parsable.") if not filename: self._file = "" return # validate file location format if filename.find("://") != -1: raise SyntaxError("Invalid image file path location, it should not contain a protocol") uri = filename auth = "" hostname = "" path = "" if filename.find("@") != -1: auth, filename = filename.split("@") # extract the hostname # 1. if we have a colon, then everything before it is a hostname # 2. if we don't have a colon, there is no hostname if filename.find(":") != -1: hostname, filename = filename.split(":") elif filename[0] != '/': raise SyntaxError("invalid file: %s" % filename) # raise an exception if we don't have a valid path if len(filename) > 0 and filename[0] != '/': raise SyntaxError("file contains an invalid path: %s" % filename) if filename.find("/") != -1: path, filename = filename.rsplit("/", 1) if len(filename) == 0: raise SyntaxError("missing filename") if len(auth) > 0 and len(hostname) == 0: raise SyntaxError("a hostname must be specified with authentication details") self._file = uri @LazyProperty def os_version(self) -> str: r""" The operating system version which the image contains. :getter: The sanitized operating system version. :setter: Accepts a str which will be validated against the ``distro_signatures.json``. """ return self._os_version @os_version.setter def os_version(self, os_version): """ Set the operating system version with this setter. :param os_version: This must be a valid OS-Version. """ self._os_version = validate.validate_os_version(os_version, self.breed) @LazyProperty def breed(self) -> str: r""" The operating system breed. :getter: Returns the current breed. :setter: When setting this it is validated against the ``distro_signatures.json`` file. """ return self._breed @breed.setter def breed(self, breed: str): """ Set the operating system breed with this setter. :param breed: The breed of the operating system which is available in the image. """ self._breed = validate.validate_breed(breed) @LazyProperty def image_type(self) -> enums.ImageTypes: """ Indicates what type of image this is. direct = something like "memdisk", physical only iso = a bootable ISO that pxe's or can be used for virt installs, virtual only virt-clone = a cloned virtual disk (FIXME: not yet supported), virtual only memdisk = hdd image (physical only) :getter: The enum type value of the image type. :setter: Accepts str like and enum type values and raises a TypeError or ValueError in the case of a problem. """ return self._image_type @image_type.setter def image_type(self, image_type: Union[enums.ImageTypes, str]): """ The setter which accepts enum type or str type values. Latter ones will be automatically converted if possible. :param image_type: One of the four options from above. :raises TypeError: In case a disallowed type was found. :raises ValueError: In case the conversion from str could not successfully executed. """ if not isinstance(image_type, (enums.ImageTypes, str)): raise TypeError("image_type must be of type str or enum.ImageTypes") if isinstance(image_type, str): if not image_type: # FIXME: Add None Image type self._image_type = enums.ImageTypes.DIRECT try: image_type = enums.ImageTypes[image_type.upper()] except KeyError as error: raise ValueError("image_type choices include: %s" % list(map(str, enums.ImageTypes))) from error # str was converted now it must be an enum.ImageTypes if not isinstance(image_type, enums.ImageTypes): raise TypeError("image_type needs to be of type enums.ImageTypes") if image_type not in enums.ImageTypes: raise ValueError("image type must be one of the following: %s" % ", ".join(list(map(str, enums.ImageTypes)))) self._image_type = image_type @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: int): """ Setter for the number of virtual cpus. :param num: The number of virtual cpu cores. """ self._virt_cpus = validate.validate_virt_cpus(num) @LazyProperty def network_count(self) -> int: """ Represents the number of virtual NICs this image has. .. deprecated:: 3.3.0 This is nowhere used in the project and will be removed in a future release. :getter: The number of networks. :setter: Raises a ``TypeError`` in case the value is not an int. """ return self._network_count @network_count.setter def network_count(self, network_count: int): """ Setter for the number of networks. :param network_count: If None or emtpy will be set to ``1``, otherwise the given integer value will be set. :raises TypeError: In case the network_count was not of type int. """ if network_count is None or network_count == "": network_count = 1 if not isinstance(network_count, int): raise TypeError("Field network_count of object image needs to be of type int.") self._network_count = network_count @InheritableProperty def virt_auto_boot(self) -> bool: r""" Whether the VM should be booted when booting the host or not. :getter: ``True`` means autoboot is enabled, otherwise VM is not booted automatically. :setter: The new state for the property. """ return self._virt_auto_boot @virt_auto_boot.setter def virt_auto_boot(self, num: Union[str, bool]): """ Setter for the virtual automatic boot option. :param num: May be "0" (disabled) or "1" (enabled), will be converted to a real bool. """ self._virt_auto_boot = validate.validate_virt_auto_boot(num) @LazyProperty 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. :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: float): """ Setter for the virtual file size of the image. :param num: Is a non-negative integer (0 means default). Can also be a comma seperated list -- for usage with multiple disks """ self._virt_file_size = validate.validate_virt_file_size(num) @LazyProperty def virt_disk_driver(self) -> enums.VirtDiskDrivers: """ The type of disk driver used for storing the image. :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._virt_disk_driver @virt_disk_driver.setter def virt_disk_driver(self, driver: enums.VirtDiskDrivers): """ Setter for the virtual disk driver. :param driver: The virtual disk driver which will be set. """ self._virt_disk_driver = enums.VirtDiskDrivers.to_enum(driver) @LazyProperty def virt_ram(self) -> int: """ The amount of RAM given to the guest in MB. :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: int): """ Setter for the amount of virtual RAM the machine will have. :param num: 0 tells Koan to just choose a reasonable default. """ self._virt_ram = validate.validate_virt_ram(num) @LazyProperty def virt_type(self) -> enums.VirtType: """ The type of image used. :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._virt_type @virt_type.setter def virt_type(self, vtype: enums.VirtType): """ Setter for the virtual type :param vtype: May be one of "qemu", "kvm", "xenpv", "xenfv", "vmware", "vmwarew", "openvz" or "auto". """ self._virt_type = enums.VirtType.to_enum(vtype) @LazyProperty def virt_bridge(self) -> str: r""" The name of the virtual bridge used for networking. .. warning:: The new validation for the setter is not working. Thus the inheritance from the settings is broken. :getter: The name of the bridge. :setter: The new name of the bridge. If set to an empty ``str``, it will be taken from the settings. """ return self._virt_bridge @virt_bridge.setter def virt_bridge(self, vbridge: str): """ Setter for the virtual bridge which is used. :param vbridge: The name of the virtual bridge to use. """ self._virt_bridge = validate.validate_virt_bridge(vbridge) @LazyProperty def virt_path(self) -> str: """ Represents the location where the image for the VM is stored. :getter: The path. :setter: Is being validated for being a reasonable path. If yes is set, otherwise ignored. """ return self._virt_path @virt_path.setter def virt_path(self, path: str): """ Setter for the virtual path which is used. :param path: The path to where the virtual image is stored. """ self._virt_path = validate.validate_virt_path(path) @LazyProperty def menu(self) -> str: """ 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 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 supported_boot_loaders(self): """ Read only property which represents the subset of settable bootloaders. :getter: The bootloaders which are available for being set. """ if len(self._supported_boot_loaders) == 0: self._supported_boot_loaders = utils.get_supported_distro_boot_loaders( self ) return self._supported_boot_loaders @InheritableProperty def boot_loaders(self) -> list: """ Represents the boot loaders which are able to boot this image. :getter: The bootloaders. May be an emtpy list. :setter: A list with the supported boot loaders for this image. """ if self._boot_loaders == enums.VALUE_INHERITED: return self.supported_boot_loaders return self._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 image. :raises TypeError: In case this was of a not allowed type. :raises ValueError: In case the str which contained the list could not be successfully split. """ # allow the magic inherit string to persist 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) if not isinstance(boot_loaders_split, list): raise TypeError("boot_loaders needs to be of type list!") if not set(boot_loaders_split).issubset(self.supported_boot_loaders): raise ValueError("Error with image %s - not all boot_loaders %s are supported %s" % (self.name, boot_loaders_split, self.supported_boot_loaders)) self._boot_loaders = boot_loaders_split else: self._boot_loaders = []