"""
All code belonging to Cobbler systems.
Changelog (System):
V3.4.0 (unreleased):
* Added:
* ``display_name``: str
* ``redhat_management_org``: enums.VALUE_INHERITED
* ``redhat_management_user``: enums.VALUE_INHERITED
* ``redhat_management_password``: enums.VALUE_INHERITED
* Changes:
* Constructor: ``kwargs`` can now be used to seed the item during creation.
* ``from_dict()``: The method was moved to the base class.
* ``parent``: The property was moved to the base class.
V3.3.4 (unreleased):
* Changed:
* The network interface ``default`` is not created on object creation.
V3.3.3:
* Changed:
* ``boot_loaders``: Can now be set to ``<<inherit>>``
* ``next_server_v4``: Can now be set to ``<<inhertit>>``
* ``next_server_v6``: Can now be set to ``<<inhertit>>``
* ``virt_cpus``: Can now be set to ``<<inhertit>>``
* ``virt_file_size``: Can now be set to ``<<inhertit>>``
* ``virt_disk_driver``: Can now be set to ``<<inhertit>>``
* ``virt_auto_boot``: Can now be set to ``<<inhertit>>``
* ``virt_ram``: Can now be set to ``<<inhertit>>``
* ``virt_type``: Can now be set to ``<<inhertit>>``
* ``virt_path``: Can now be set to ``<<inhertit>>``
V3.3.2:
* No changes
V3.3.1:
* Changed:
* ``serial_device``: Default value is now ``-1``
V3.3.0:
* This release switched from pure attributes to properties (getters/setters).
* Added:
* ``next_server_v4``
* ``next_server_v6``
* Changed:
* ``virt_*``: Cannot be set to inherit anymore
* ``enable_gpxe``: Renamed to ``enable_ipxe``
* Removed:
* ``get_fields()``
* ``next_server`` - Please use one of ``next_server_v4`` or ``next_server_v6``
* ``set_boot_loader()`` - Moved to ``boot_loader`` property
* ``set_server()`` - Moved to ``server`` property
* ``set_next_server()`` - Moved to ``next_server`` property
* ``set_filename()`` - Moved to ``filename`` property
* ``set_proxy()`` - Moved to ``proxy`` property
* ``set_redhat_management_key()`` - Moved to ``redhat_management_key`` property
* ``get_redhat_management_key()`` - Moved to ``redhat_management_key`` property
* ``set_dhcp_tag()`` - Moved to ``NetworkInterface`` class property ``dhcp_tag``
* ``set_cnames()`` - Moved to ``NetworkInterface`` class property ``cnames``
* ``set_status()`` - Moved to ``status`` property
* ``set_static()`` - Moved to ``NetworkInterface`` class property ``static``
* ``set_management()`` - Moved to ``NetworkInterface`` class property ``management``
* ``set_dns_name()`` - Moved to ``NetworkInterface`` class property ``dns_name``
* ``set_hostname()`` - Moved to ``hostname`` property
* ``set_ip_address()`` - Moved to ``NetworkInterface`` class property ``ip_address``
* ``set_mac_address()`` - Moved to ``NetworkInterface`` class property ``mac_address``
* ``set_gateway()`` - Moved to ``gateway`` property
* ``set_name_servers()`` - Moved to ``name_servers`` property
* ``set_name_servers_search()`` - Moved to ``name_servers_search`` property
* ``set_netmask()`` - Moved to ``NetworkInterface`` class property ``netmask``
* ``set_if_gateway()`` - Moved to ``NetworkInterface`` class property ``if_gateway``
* ``set_virt_bridge()`` - Moved to ``NetworkInterface`` class property ``virt_bridge``
* ``set_interface_type()`` - Moved to ``NetworkInterface`` class property ``interface_type``
* ``set_interface_master()`` - Moved to ``NetworkInterface`` class property ``interface_master``
* ``set_bonding_opts()`` - Moved to ``NetworkInterface`` class property ``bonding_opts``
* ``set_bridge_opts()`` - Moved to ``NetworkInterface`` class property ``bridge_opts``
* ``set_ipv6_autoconfiguration()`` - Moved to ``ipv6_autoconfiguration`` property
* ``set_ipv6_default_device()`` - Moved to ``ipv6_default_device`` property
* ``set_ipv6_address()`` - Moved to ``NetworkInterface`` class property ``ipv6_address``
* ``set_ipv6_prefix()`` - Moved to ``NetworkInterface`` class property ``ipv6_prefix``
* ``set_ipv6_secondaries()`` - Moved to ``NetworkInterface`` class property ``ipv6_secondaries``
* ``set_ipv6_default_gateway()`` - Moved to ``NetworkInterface`` class property ``ipv6_default_gateway``
* ``set_ipv6_static_routes()`` - Moved to ``NetworkInterface`` class property ``ipv6_static_routes``
* ``set_ipv6_mtu()`` - Moved to ``NetworkInterface`` class property ``ipv6_mtu``
* ``set_mtu()`` - Moved to ``NetworkInterface`` class property ``mtu``
* ``set_connected_mode()`` - Moved to ``NetworkInterface`` class property ``connected_mode``
* ``set_enable_gpxe()`` - Moved to ``enable_gpxe`` property
* ``set_profile()`` - Moved to ``profile`` property
* ``set_image()`` - Moved to ``image`` property
* ``set_virt_cpus()`` - Moved to ``virt_cpus`` property
* ``set_virt_file_size()`` - Moved to ``virt_file_size`` property
* ``set_virt_disk_driver()`` - Moved to ``virt_disk_driver`` property
* ``set_virt_auto_boot()`` - Moved to ``virt_auto_boot`` property
* ``set_virt_pxe_boot()`` - Moved to ``virt_pxe_boot`` property
* ``set_virt_ram()`` - Moved to ``virt_ram`` property
* ``set_virt_type()`` - Moved to ``virt_type`` property
* ``set_virt_path()`` - Moved to ``virt_path`` property
* ``set_netboot_enabled()`` - Moved to ``netboot_enabled`` property
* ``set_autoinstall()`` - Moved to ``autoinstall`` property
* ``set_power_type()`` - Moved to ``power_type`` property
* ``set_power_identity_file()`` - Moved to ``power_identity_file`` property
* ``set_power_options()`` - Moved to ``power_options`` property
* ``set_power_user()`` - Moved to ``power_user`` property
* ``set_power_pass()`` - Moved to ``power_pass`` property
* ``set_power_address()`` - Moved to ``power_address`` property
* ``set_power_id()`` - Moved to ``power_id`` property
* ``set_repos_enabled()`` - Moved to ``repos_enabled`` property
* ``set_serial_device()`` - Moved to ``serial_device`` property
* ``set_serial_baud_rate()`` - Moved to ``serial_baud_rate`` property
V3.2.2:
* No changes
V3.2.1:
* Added:
* ``kickstart``: Resolves as a proxy to ``autoinstall``
V3.2.0:
* No changes
V3.1.2:
* Added:
* ``filename``: str - Inheritable
* ``set_filename()``
V3.1.1:
* No changes
V3.1.0:
* No changes
V3.0.1:
* File was moved from ``cobbler/item_system.py`` to ``cobbler/items/system.py``.
V3.0.0:
* Field definitions for network interfaces moved to own ``FIELDS`` array
* Added:
* ``boot_loader``: str - Inheritable
* ``next_server``: str - Inheritable
* ``power_options``: str
* ``power_identity_file``: str
* ``serial_device``: int
* ``serial_baud_rate``: int - One of "", "2400", "4800", "9600", "19200", "38400", "57600", "115200"
* ``set_next_server()``
* ``set_serial_device()``
* ``set_serial_baud_rate()``
* ``get_config_filename()``
* ``set_power_identity_file()``
* ``set_power_options()``
* Changed:
* ``kickstart``: Renamed to ``autoinstall``
* ``ks_meta``: Renamed to ``autoinstall_meta``
* ``from_datastruct``: Renamed to ``from_dict()``
* ``set_kickstart()``: Renamed to ``set_autoinstall()``
* Removed:
* ``redhat_management_server``
* ``set_ldap_enabled()``
* ``set_monit_enabled()``
* ``set_template_remote_kickstarts()``
* ``set_redhat_management_server()``
* ``set_name()``
V2.8.5:
* Inital tracking of changes for the changelog.
* Network interface defintions part of this class
* Added:
* ``name``: str
* ``uid``: str
* ``owners``: List[str] - Inheritable
* ``profile``: str - Name of the profile
* ``image``: str - Name of the image
* ``status``: str - One of "", "development", "testing", "acceptance", "production"
* ``kernel_options``: Dict[str, Any]
* ``kernel_options_post``: Dict[str, Any]
* ``ks_meta``: Dict[str, Any]
* ``enable_gpxe``: bool - Inheritable
* ``proxy``: str - Inheritable
* ``netboot_enabled``: bool
* ``kickstart``: str - Inheritable
* ``comment``: str
* ``depth``: int
* ``server``: str - Inheritable
* ``virt_path``: str - Inheritable
* ``virt_type``: str - Inheritable; One of "xenpv", "xenfv", "qemu", "kvm", "vmware", "openvz"
* ``virt_cpus``: int - Inheritable
* ``virt_file_size``: float - Inheritable
* ``virt_disk_driver``: str - Inheritable; One of "<<inherit>>", "raw", "qcow", "qcow2", "aio", "vmdk", "qed"
* ``virt_ram``: int - Inheritable
* ``virt_auto_boot``: bool - Inheritable
* ``virt_pxe_boot``: bool
* ``ctime``: float
* ``mtime``: float
* ``power_type``: str - Default loaded from settings key ``power_management_default_type``
* ``power_address``: str
* ``power_user``: str
* ``power_pass``: str
* ``power_id``: str
* ``hostname``: str
* ``gateway``: str
* ``name_servers``: List[str]
* ``name_servers_search``: List[str]
* ``ipv6_default_device``: str
* ``ipv6_autoconfiguration``: bool
* ``mgmt_classes``: List[Any] - Inheritable
* ``mgmt_parameters``: str - Inheritable
* ``boot_files``: Dict[str, Any]/List (Not reverse engineeriable) - Inheritable
* ``fetchable_files``: Dict[str, Any] - Inheritable
* ``template_files``: Dict[str, Any] - Inheritable
* ``redhat_management_key``: str - Inheritable
* ``redhat_management_server``: str - Inheritable
* ``template_remote_kickstarts``: bool - Default loaded from settings key ``template_remote_kickstarts``
* ``repos_enabled``: bool
* ``ldap_enabled``: - bool
* ``ldap_type``: str - Default loaded from settings key ``ldap_management_default_type``
* ``monit_enabled``: bool
"""
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: Copyright 2006-2008, Red Hat, Inc and Others
# SPDX-FileCopyrightText: Michael DeHaan <michael.dehaan AT gmail>
import copy
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union
from cobbler import enums, utils, validate
from cobbler.cexceptions import CX
from cobbler.items.abstract.bootable_item import BootableItem, KernelOptionsDict
from cobbler.items.network_interface import NetworkInterface
from cobbler.items.options.dns import DNSOption
from cobbler.items.options.power import PowerOption
from cobbler.items.options.tftp import TFTPOption
from cobbler.items.options.virt import VirtOption
from cobbler.items.profile import Profile
from cobbler.utils import input_converters
if TYPE_CHECKING:
from cobbler.api import CobblerAPI
from cobbler.items.system_group import SystemGroup
from cobbler.items.template import Template
InheritableProperty = property
LazyProperty = property
else:
from cobbler.decorator import InheritableProperty, LazyProperty
[docs]
class System(BootableItem):
"""
A Cobbler system object.
"""
# Constants
TYPE_NAME = "system"
COLLECTION_TYPE = "system"
def __init__(self, api: "CobblerAPI", *args: Any, **kwargs: Any) -> None:
"""
Constructor
:param api: The Cobbler API
"""
super().__init__(api)
# Prevent attempts to clear the to_dict cache before the object is initialized.
self._has_initialized = False
self._ipv6_autoconfiguration = False
self._repos_enabled = False
self._autoinstall = enums.VALUE_INHERITED
self._boot_loaders = [enums.BootLoader.INHERITED]
self._dns = DNSOption(api=api, item=self)
self._enable_ipxe: Union[bool, str] = enums.VALUE_INHERITED
self._gateway = ""
self._hostname = ""
self._image = ""
self._ipv6_default_device = ""
self._netboot_enabled = False
self._tftp = TFTPOption(api=api, item=self)
self._filename = enums.VALUE_INHERITED
self._power = PowerOption(api=api, item=self)
self._profile = ""
self._proxy = enums.VALUE_INHERITED
self._redhat_management_key = enums.VALUE_INHERITED
self._redhat_management_org = enums.VALUE_INHERITED
self._redhat_management_user = enums.VALUE_INHERITED
self._redhat_management_password = enums.VALUE_INHERITED
self._server = enums.VALUE_INHERITED
self._status = ""
self._virt = VirtOption(api=api, item=self)
self._serial_device = -1
self._serial_baud_rate = enums.BaudRates.DISABLED
self._display_name = ""
# Overwrite defaults from bootable_item.py
self._owners = enums.VALUE_INHERITED
self._autoinstall_meta = enums.VALUE_INHERITED
self._kernel_options: Union[KernelOptionsDict, str] = enums.VALUE_INHERITED
self._kernel_options_post: Union[KernelOptionsDict, str] = enums.VALUE_INHERITED
if len(kwargs) > 0:
self.from_dict(kwargs)
if not self._has_initialized:
self._has_initialized = True
def __getattr__(self, name: str) -> Any:
if name == "kickstart":
return self.autoinstall
if name == "ks_meta":
return self.autoinstall_meta
raise AttributeError(f'Attribute "{name}" did not exist on object type System.')
#
# override some base class methods first (BootableItem)
#
[docs]
def make_clone(self):
_dict = copy.deepcopy(self.to_dict())
_dict.pop("uid", None)
if isinstance(_dict.get("autoinstall"), dict):
# This is a concrete dict object, not inherited.
autoinstall = _dict.pop("autoinstall")
_dict["autoinstall"] = autoinstall["uid"]
return System(self.api, **_dict)
[docs]
def check_if_valid(self):
"""
Checks if the current item passes logical validation.
:raises CX: In case name is missing. Additionally either image or profile is required.
"""
super().check_if_valid()
if not self.inmemory:
return
# System specific validation
if self.profile == "":
if self.image == "":
raise CX(
f"Error with system {self.name} - profile or image is required"
)
#
# specific methods for item.System
#
@property
def interfaces(self) -> Dict[str, NetworkInterface]:
r"""
Represents all interfaces owned by the system.
:getter: The interfaces present. Has at least the ``default`` one.
:setter: Accepts not only the correct type but also a dict with dicts which will then be converted by the
setter.
"""
search_result = self.api.find_network_interface(
return_list=True, system_uid=self.uid
)
result: Dict[str, NetworkInterface] = {}
if search_result is None:
return result
if not isinstance(search_result, list):
raise TypeError("Result must be of type list!")
for item in search_result:
result[item.name] = item
return result
@property
def tftp(self) -> TFTPOption:
"""
Property for the TFTPOptions.
:getter: The TFTPOptions of the System.
"""
return self._tftp
@property
def power(self) -> PowerOption:
"""
Property for the PowerOptions.
:getter: The PowerOptions of the System.
"""
return self._power
@property
def virt(self) -> VirtOption:
"""
Property for the VirtOptions.
:getter: The VirtOptions of the System.
"""
return self._virt
@property
def dns(self) -> DNSOption:
"""
Property for the DNSOptions.
:getter: The DNSOptions of the System.
"""
return self._dns
@LazyProperty
def hostname(self) -> str:
"""
hostname property.
:getter: Returns the value for ``hostname``.
:setter: Sets the value for the property ``hostname``.
"""
return self._hostname
@hostname.setter
def hostname(self, value: str):
"""
Setter for the hostname of the System class.
:param value: The new hostname
"""
if not isinstance(value, str): # type: ignore
raise TypeError("Field hostname of object system needs to be of type str!")
self._hostname = value
@LazyProperty
def status(self) -> str:
"""
status property.
:getter: Returns the value for ``status``.
:setter: Sets the value for the property ``status``.
"""
return self._status
@status.setter
def status(self, status: str):
"""
Setter for the status of the System class.
:param status: The new system status.
"""
if not isinstance(status, str): # type: ignore
raise TypeError("Field status of object system needs to be of type str!")
self._status = status
@InheritableProperty
def boot_loaders(self) -> List[enums.BootLoader]:
"""
boot_loaders property.
.. note:: This property can be set to ``<<inherit>>``.
:getter: Returns the value for ``boot_loaders``.
:setter: Sets the value for the property ``boot_loaders``.
"""
return self._resolve_enum(["boot_loaders"], enums.BootLoader)
@boot_loaders.setter
def boot_loaders(
self,
boot_loaders: Union[str, List[str], List[enums.BootLoader]],
):
"""
Setter of the boot loaders.
:param boot_loaders: The boot loaders for the system.
:raises CX: This is risen in case the bootloaders set are not valid ones.
"""
boot_loaders_split: List[enums.BootLoader] = []
if isinstance(boot_loaders, list):
if all([isinstance(value, str) for value in boot_loaders]):
boot_loaders_split = [
enums.BootLoader.to_enum(value) for value in boot_loaders
]
elif all([isinstance(value, enums.BootLoader) for value in boot_loaders]):
boot_loaders_split = boot_loaders # type: ignore
else:
raise TypeError(
"The items inside the list of boot_loaders must all be of type str or cobbler.enums.BootLoader"
)
elif isinstance(boot_loaders, str): # type: ignore
if boot_loaders == "":
self._boot_loaders = []
return
if boot_loaders == enums.VALUE_INHERITED:
self._boot_loaders = [enums.BootLoader.INHERITED]
return
boot_loaders_split = [
enums.BootLoader.to_enum(value)
for value in input_converters.input_string_or_list_no_inherit(
boot_loaders
)
]
else:
raise TypeError("The bootloaders need to be either a str or list")
parent = self.logical_parent
if parent is not None:
# This can only be an item type that has the boot loaders property
parent_boot_loaders: List[enums.BootLoader] = parent.boot_loaders # type: ignore
else:
self.logger.warning(
'Parent of System "%s" could not be found for resolving the parent bootloaders.',
self.name,
)
parent_boot_loaders = []
if (
len(boot_loaders_split) == 1
and boot_loaders_split[0] == enums.BootLoader.INHERITED
):
self._boot_loaders = [enums.BootLoader.INHERITED]
return
if not set(boot_loaders_split).issubset(parent_boot_loaders):
raise CX(
f'Error with system "{self.name}" - not all boot_loaders are supported (given:'
f'"{str(boot_loaders_split)}"; supported: "{str(parent_boot_loaders)}")'
)
self._boot_loaders = boot_loaders_split
@InheritableProperty
def server(self) -> str:
"""
server property.
.. note:: This property can be set to ``<<inherit>>``.
:getter: Returns the value for ``server``.
:setter: Sets the value for the property ``server``.
"""
return self._resolve(["server"])
@server.setter
def server(self, server: str):
"""
If a system can't reach the boot server at the value configured in settings
because it doesn't have the same name on it's subnet this is there for an override.
:param server: The new value for the ``server`` property.
:raises TypeError: In case server is no string.
"""
if not isinstance(server, str): # type: ignore
raise TypeError("Field server of object system needs to be of type str!")
if server == "":
server = enums.VALUE_INHERITED
self._server = server
@InheritableProperty
def filename(self) -> str:
"""
filename property.
:getter: Returns the value for ``filename``.
:setter: Sets the value for the property ``filename``.
"""
if self.image != "":
return ""
return self._resolve(["filename"])
@filename.setter
def filename(self, filename: str):
"""
Setter for the filename of the System class.
:param filename: The new value for the ``filename`` property.
:raises TypeError: In case filename is no string.
"""
if not isinstance(filename, str): # type: ignore
raise TypeError("Field filename of object system needs to be of type str!")
if not filename:
self._filename = enums.VALUE_INHERITED
else:
self._filename = filename.strip()
@InheritableProperty
def proxy(self) -> str:
"""
proxy property. This corresponds per default to the setting``proxy_url_int``.
.. note:: This property can be set to ``<<inherit>>``.
:getter: Returns the value for ``proxy``.
:setter: Sets the value for the property ``proxy``.
"""
if self.profile != "":
return self._resolve(["proxy"])
return self._resolve(["proxy_url_int"])
@proxy.setter
def proxy(self, proxy: str):
"""
Setter for the proxy of the System class.
:param proxy: The new value for the proxy.
:raises TypeError: In case proxy is no string.
"""
if not isinstance(proxy, str): # type: ignore
raise TypeError("Field proxy of object system needs to be of type str!")
self._proxy = proxy
@InheritableProperty
def redhat_management_key(self) -> str:
"""
redhat_management_key property.
.. note:: This property can be set to ``<<inherit>>``.
:getter: Returns the value for ``redhat_management_key``.
:setter: Sets the value for the property ``redhat_management_key``.
"""
return self._resolve(["redhat_management_key"])
@redhat_management_key.setter
def redhat_management_key(self, management_key: str):
"""
Setter for the redhat_management_key of the System class.
:param management_key: The new value for the redhat management key
:raises TypeError: In case management_key is no string.
"""
if not isinstance(management_key, str): # type: ignore
raise TypeError(
"Field redhat_management_key of object system needs to be of type str!"
)
if management_key == "":
self._redhat_management_key = enums.VALUE_INHERITED
self._redhat_management_key = management_key
@InheritableProperty
def redhat_management_org(self) -> str:
"""
redhat_management_org property.
.. note:: This property can be set to ``<<inherit>>``.
:getter: Returns the value for ``redhat_management_org``.
:setter: Sets the value for the property ``redhat_management_org``.
"""
return self._resolve(["redhat_management_org"])
@redhat_management_org.setter # type: ignore[no-redef]
def redhat_management_org(self, management_org: str):
"""
Setter for the redhat_management_org of the System class.
:param management_org: The new value for the redhat management org
:raises TypeError: In case management_org is no string.
"""
if not isinstance(management_org, str): # type: ignore
raise TypeError(
"Field redhat_management_org of object system needs to be of type str!"
)
if management_org == "":
self._redhat_management_org = enums.VALUE_INHERITED
self._redhat_management_org = management_org
@InheritableProperty
def redhat_management_user(self) -> str:
"""
redhat_management_user property.
.. note:: This property can be set to ``<<inherit>>``.
:getter: Returns the value for ``redhat_management_user``.
:setter: Sets the value for the property ``redhat_management_user``.
"""
return self._resolve(["redhat_management_user"])
@redhat_management_user.setter # type: ignore[no-redef]
def redhat_management_user(self, management_user: str):
"""
Setter for the redhat_management_user of the System class.
:param management_user: The new value for the redhat management user
:raises TypeError: In case management_user is no string.
"""
if not isinstance(management_user, str): # type: ignore
raise TypeError(
"Field redhat_management_user of object system needs to be of type str!"
)
if management_user == "":
self._redhat_management_user = enums.VALUE_INHERITED
self._redhat_management_user = management_user
@InheritableProperty
def redhat_management_password(self) -> str:
"""
redhat_management_password property.
.. note:: This property can be set to ``<<inherit>>``.
:getter: Returns the value for ``redhat_management_password``.
:setter: Sets the value for the property ``redhat_management_password``.
"""
return self._resolve(["redhat_management_password"])
@redhat_management_password.setter # type: ignore[no-redef]
def redhat_management_password(self, management_password: str):
"""
Setter for the redhat_management_password of the System class.
:param management_password: The new value for the redhat management password
:raises TypeError: In case management_password is no string.
"""
if not isinstance(management_password, str): # type: ignore
raise TypeError(
"Field redhat_management_password of object system needs to be of type str!"
)
if management_password == "":
self._redhat_management_password = enums.VALUE_INHERITED
self._redhat_management_password = management_password
[docs]
def get_mac_address(self, interface: str = "default"):
"""
Get the mac address, which may be implicit in the object name or explicit with --mac-address.
Use the explicit location first.
:param interface: The name of the interface to get the MAC of.
"""
search_result = self.api.find_network_interface(
False, False, system_uid=self.uid, name=interface
)
if search_result is None or isinstance(search_result, list):
return None
if search_result.mac_address != "":
return search_result.mac_address.strip()
return None
@property
def get_mac_addresses(self) -> Set[str]:
"""
Get the set of system mac addresses.
"""
macs: Set[str] = set()
for intf in self.interfaces.values():
mac = intf.mac_address
if mac:
mac = mac.strip()
macs.add(mac)
return macs
[docs]
def get_ip_address(self, interface: str) -> str:
"""
Get the IP address for the given interface.
:param interface: The name of the interface to get the IP address of.
"""
search_result = self.api.find_network_interface(
False, False, system_uid=self.uid, name=interface
)
if search_result is None or isinstance(search_result, list):
return ""
if search_result.ipv4.address:
return search_result.ipv4.address.strip()
return ""
[docs]
def is_management_supported(self, cidr_ok: bool = True) -> bool:
"""
Can only add system PXE records if a MAC or IP address is available, else it's a koan only record.
:param cidr_ok: Deprecated parameter which is not used anymore.
"""
if self.name == "default":
return True
for interface in self.interfaces.values():
mac = interface.mac_address
ip_v4 = interface.ipv4.address
ip_v6 = interface.ipv6.address
if mac or ip_v4 or ip_v6:
return True
return False
@LazyProperty
def gateway(self):
"""
gateway property.
:getter: Returns the value for ``gateway``.
:setter: Sets the value for the property ``gateway``.
"""
return self._gateway
@gateway.setter
def gateway(self, gateway: str):
"""
Set a gateway IPv4 address.
:param gateway: IP address
:returns: True or CX
"""
self._gateway = validate.ipv4_address(gateway)
@LazyProperty
def ipv6_autoconfiguration(self) -> bool:
"""
ipv6_autoconfiguration property.
:getter: Returns the value for ``ipv6_autoconfiguration``.
:setter: Sets the value for the property ``ipv6_autoconfiguration``.
"""
return self._ipv6_autoconfiguration
@ipv6_autoconfiguration.setter
def ipv6_autoconfiguration(self, value: bool):
"""
Setter for the ipv6_autoconfiguration of the System class.
:param value: The new value for the ``ipv6_autoconfiguration`` property.
"""
value = input_converters.input_boolean(value)
if not isinstance(value, bool): # type: ignore
raise TypeError("ipv6_autoconfiguration needs to be of type bool")
self._ipv6_autoconfiguration = value
@LazyProperty
def ipv6_default_device(self) -> str:
"""
ipv6_default_device property.
:getter: Returns the value for ``ipv6_default_device``.
:setter: Sets the value for the property ``ipv6_default_device``.
"""
return self._ipv6_default_device
@ipv6_default_device.setter
def ipv6_default_device(self, interface_name: str):
"""
Setter for the ipv6_default_device of the System class.
:param interface_name: The new value for the ``ipv6_default_device`` property.
"""
if not isinstance(interface_name, str): # type: ignore
raise TypeError(
"Field ipv6_default_device of object system needs to be of type str!"
)
self._ipv6_default_device = interface_name
@InheritableProperty
def enable_ipxe(self) -> bool:
"""
enable_ipxe property.
.. note:: This property can be set to ``<<inherit>>``.
:getter: Returns the value for ``enable_ipxe``.
:setter: Sets the value for the property ``enable_ipxe``.
"""
return self._resolve(["enable_ipxe"])
@enable_ipxe.setter
def enable_ipxe(self, enable_ipxe: Union[str, bool]):
"""
Sets whether the system will use iPXE for booting.
:param enable_ipxe: If ipxe should be enabled or not.
:raises TypeError: In case enable_ipxe is not a boolean.
"""
if enable_ipxe == enums.VALUE_INHERITED:
self._enable_ipxe = enums.VALUE_INHERITED
return
enable_ipxe = input_converters.input_boolean(enable_ipxe)
if not isinstance(enable_ipxe, bool): # type: ignore
raise TypeError("enable_ipxe needs to be of type bool")
self._enable_ipxe = enable_ipxe
@LazyProperty
def profile(self) -> str:
"""
profile property.
:getter: Returns the value for ``profile``.
:setter: Sets the value for the property ``profile``.
"""
return self._profile
@profile.setter
def profile(self, profile_uid: Union["Profile", str]):
"""
Set the system to use a certain named profile. The profile must have already been loaded into the profiles
collection.
:param uid: The uid of the profile which the system is underneath.
:raises TypeError: In case profile_uid is no string.
:raises ValueError: In case profile_uid does not exist.
"""
profile = None
if isinstance(profile_uid, Profile):
profile = profile_uid
profile_uid = profile.uid
elif not isinstance(profile_uid, str): # type: ignore
raise TypeError("The name of a profile needs to be of type str.")
items = self.api.systems()
old_profile = self._profile
if profile_uid in ["delete", "None", "~", ""]:
self._profile = ""
items.update_index_value(self, "profile", old_profile, "")
return
if profile is None:
profile = self.api.profiles().find(uid=profile_uid, return_list=False) # type: ignore
if isinstance(profile, list):
raise ValueError("Search returned ambigous match!")
if profile is None:
raise ValueError(f'Profile with the uid "{profile_uid}" is not existing')
self.image = "" # mutual exclusion rule
self._profile = profile_uid
self.depth = profile.depth + 1 # subprofiles have varying depths.
items.update_index_value(self, "profile", old_profile, profile_uid)
@LazyProperty
def image(self) -> str:
"""
image property.
:getter: Returns the value for ``image``.
:setter: Sets the value for the property ``image``.
"""
return self._image
@image.setter
def image(self, image_uid: str):
"""
Set the system to use a certain named image. Works like ``set_profile()`` but cannot be used at the same time.
It's one or the other.
:param image_uid: The uid of the image which will act as a parent.
:raises ValueError: In case the image name was invalid.
:raises TypeError: In case image_name is no string.
"""
if not isinstance(image_uid, str): # type: ignore
raise TypeError("The uid of an image must be of type str.")
items = self.api.systems()
old_image = self._image
if image_uid in ["delete", "None", "~", ""]:
self._image = ""
items.update_index_value(self, "image", old_image, "")
return
img = self.api.images().find(uid=image_uid)
if isinstance(img, list):
raise ValueError("Search returned ambigous match!")
if img is None:
raise ValueError(f'Image with the name "{image_uid}" is not existing')
self.profile = "" # mutual exclusion rule
self._image = image_uid
self.depth = img.depth + 1
items.update_index_value(self, "image", old_image, image_uid)
@LazyProperty
def netboot_enabled(self) -> bool:
"""
netboot_enabled property.
:getter: Returns the value for ``netboot_enabled``.
:setter: Sets the value for the property ``netboot_enabled``.
"""
return self._netboot_enabled
@netboot_enabled.setter
def netboot_enabled(self, netboot_enabled: bool):
"""
If true, allows per-system PXE files to be generated on sync (or add). If false, these files are not generated,
thus eliminating the potential for an infinite install loop when systems are set to PXE boot first in the boot
order. In general, users who are PXE booting first in the boot order won't create system definitions, so this
feature primarily comes into play for programmatic users of the API, who want to initially create a system with
netboot enabled and then disable it after the system installs, as triggered by some action in automatic
installation file's %post section. For this reason, this option is not surfaced in the CLI, output, or
documentation (yet).
Use of this option does not affect the ability to use PXE menus. If an admin has machines set up to PXE only
after local boot fails, this option isn't even relevant.
:param: netboot_enabled:
:raises TypeError: In case netboot_enabled is not a boolean.
"""
netboot_enabled = input_converters.input_boolean(netboot_enabled)
if not isinstance(netboot_enabled, bool): # type: ignore
raise TypeError("netboot_enabled needs to be a bool")
self._netboot_enabled = netboot_enabled
@InheritableProperty
def autoinstall(self) -> Optional["Template"]:
"""
Represents the automatic OS installation template object.
:getter: The template object that is configured.
:setter: The name, UID or Template object. This property may be set to ``<<inherit>>``.
"""
if self._autoinstall == "":
return None
autoinstall = self._resolve(["autoinstall"])
if validate.validate_uuid(autoinstall):
search_result = self.api.find_template(False, False, uid=autoinstall)
elif hasattr(autoinstall, "TYPE_NAME"):
return autoinstall
else:
# Built-In Templates save the names to survive serialization.
search_result = self.api.find_template(False, False, name=autoinstall)
if search_result is None:
raise ValueError("No search result for given template UID/Name!")
if isinstance(search_result, list):
raise ValueError("Ambigous template match name detected!")
return search_result
@autoinstall.setter
def autoinstall(self, autoinstall: Union[str, "Template"]):
"""
Setter for the ``autoinstall`` property.
:param autoinstall: local automatic installation template name, UID or Template object.
"""
self._autoinstall = validate.validate_template(self.api, autoinstall)
@LazyProperty
def repos_enabled(self) -> bool:
"""
repos_enabled property.
:getter: Returns the value for ``repos_enabled``.
:setter: Sets the value for the property ``repos_enabled``.
"""
return self._repos_enabled
@repos_enabled.setter
def repos_enabled(self, repos_enabled: bool):
"""
Setter for the repos_enabled of the System class.
:param repos_enabled: The new value for the ``repos_enabled`` property.
:raises TypeError: In case is no string.
"""
repos_enabled = input_converters.input_boolean(repos_enabled)
if not isinstance(repos_enabled, bool): # type: ignore
raise TypeError(
"Field repos_enabled of object system needs to be of type bool!"
)
self._repos_enabled = repos_enabled
@LazyProperty
def serial_device(self) -> int:
"""
serial_device property. "-1" disables the serial device functionality completely.
:getter: Returns the value for ``serial_device``.
:setter: Sets the value for the property ``serial_device``.
"""
return self._serial_device
@serial_device.setter
def serial_device(self, device_number: int):
"""
Setter for the serial_device of the System class.
:param device_number: The number of the device which is going
"""
self._serial_device = validate.validate_serial_device(device_number)
@LazyProperty
def serial_baud_rate(self) -> enums.BaudRates:
"""
serial_baud_rate property. The value "disabled" will disable the functionality completely.
:getter: Returns the value for ``serial_baud_rate``.
:setter: Sets the value for the property ``serial_baud_rate``.
"""
return self._serial_baud_rate
@serial_baud_rate.setter
def serial_baud_rate(self, baud_rate: int):
"""
Setter for the serial_baud_rate of the System class.
:param baud_rate: The new value for the ``baud_rate`` property.
"""
self._serial_baud_rate = validate.validate_serial_baud_rate(baud_rate)
[docs]
def get_config_filename(
self, interface: str, loader: Optional[enums.BootLoader] = None
) -> Optional[str]:
"""
The configuration file for each system pxe uses is either a form of the MAC address or the hex version or the
IP address. If none of that is available, just use the given name, though the name given will be unsuitable for
PXE
configuration (For this, check system.is_management_supported()). This same file is used to store system config
information in the Apache tree, so it's still relevant.
:param interface: Name of the interface.
:param loader: Bootloader type.
"""
boot_loaders = self.boot_loaders
if loader is None:
if (
enums.BootLoader.GRUB in boot_loaders or len(boot_loaders) < 1
): # pylint: disable=unsupported-membership-test
loader = enums.BootLoader.GRUB
else:
loader = boot_loaders[0] # pylint: disable=unsubscriptable-object
if interface not in self.interfaces:
self.logger.warning(
'System "%s" did not have an interface with the name "%s" attached to it.',
self.name,
interface,
)
return None
if self.name == "default":
if loader == enums.BootLoader.GRUB:
return None
return "default"
mac = self.get_mac_address(interface)
ip_address = self.get_ip_address(interface)
if mac is not None and mac != "":
if loader == enums.BootLoader.GRUB:
return mac.lower()
return "01-" + "-".join(mac.split(":")).lower()
if ip_address != "":
return utils.get_host_ip(ip_address)
return self.name
@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
@property
def system_groups(self) -> List["SystemGroup"]:
"""
Finds all SystemGroups that this system is a member of.
"""
groups: List["SystemGroup"] = []
group_collection = self.api.system_groups()
for group in group_collection.listing.values():
if self.uid in getattr(group, "members", []):
groups.append(group)
return groups