diff --git a/anaconda.spec.in b/anaconda.spec.in index 9509393bd7c..d0f83b478d7 100644 --- a/anaconda.spec.in +++ b/anaconda.spec.in @@ -35,13 +35,14 @@ Source0: %{name}-%{version}.tar.bz2 %define libxklavierver 5.4 %define mehver 0.23-1 %define nmver 1.0 -%define pykickstartver 3.32.10-1 +%define pykickstartver 3.32.11-1 %define pypartedver 2.5-2 %define pythonblivetver 1:3.6.0-4 %define rpmver 4.10.0 %define simplelinever 1.8.3-1 %define subscriptionmanagerver 1.29.31 %define utillinuxver 2.15.1 +%define rpmostreever 2023.2 BuildRequires: audit-libs-devel BuildRequires: libtool @@ -100,6 +101,11 @@ Requires: python3-systemd Requires: python3-productmd Requires: python3-dasbus >= %{dasbusver} Requires: flatpak-libs +# dependencies for rpm-ostree payload module +Requires: rpm-ostree >= %{rpmostreever} +Requires: ostree +# used by ostree command for native containers +Requires: skopeo %if %{defined rhel} && %{undefined centos} Requires: subscription-manager >= %{subscriptionmanagerver} %endif diff --git a/configure.ac b/configure.ac index 59f76bbe6b9..7d61cd0f6f4 100644 --- a/configure.ac +++ b/configure.ac @@ -182,6 +182,7 @@ AC_CONFIG_FILES([Makefile pyanaconda/modules/payloads/source/nfs/Makefile pyanaconda/modules/payloads/source/repo_files/Makefile pyanaconda/modules/payloads/source/rpm_ostree/Makefile + pyanaconda/modules/payloads/source/rpm_ostree_container/Makefile pyanaconda/modules/payloads/source/url/Makefile pyanaconda/modules/storage/Makefile pyanaconda/modules/storage/bootloader/Makefile diff --git a/pyanaconda/core/constants.py b/pyanaconda/core/constants.py index df360a0b8b8..1496f8c5272 100644 --- a/pyanaconda/core/constants.py +++ b/pyanaconda/core/constants.py @@ -430,6 +430,7 @@ class DisplayModes(Enum): SOURCE_TYPE_LIVE_OS_IMAGE = "LIVE_OS_IMAGE" SOURCE_TYPE_LIVE_IMAGE = "LIVE_IMAGE" SOURCE_TYPE_RPM_OSTREE = "RPM_OSTREE" +SOURCE_TYPE_RPM_OSTREE_CONTAINER = "RPM_OSTREE_CONTAINER" SOURCE_TYPE_HMC = "HMC" SOURCE_TYPE_CDROM = "CDROM" SOURCE_TYPE_CLOSEST_MIRROR = "CLOSEST_MIRROR" diff --git a/pyanaconda/core/kickstart/commands.py b/pyanaconda/core/kickstart/commands.py index e01b402163a..181045f4e90 100644 --- a/pyanaconda/core/kickstart/commands.py +++ b/pyanaconda/core/kickstart/commands.py @@ -55,6 +55,7 @@ from pykickstart.commands.nfs import FC6_NFS as NFS from pykickstart.commands.nvdimm import F28_Nvdimm as Nvdimm from pykickstart.commands.ostreesetup import RHEL9_OSTreeSetup as OSTreeSetup +from pykickstart.commands.ostreecontainer import RHEL9_OSTreeContainer as OSTreeContainer from pykickstart.commands.partition import RHEL9_Partition as Partition from pykickstart.commands.raid import RHEL9_Raid as Raid from pykickstart.commands.realm import F19_Realm as Realm diff --git a/pyanaconda/modules/common/constants/interfaces.py b/pyanaconda/modules/common/constants/interfaces.py index ec031b9e0c8..01c39474314 100644 --- a/pyanaconda/modules/common/constants/interfaces.py +++ b/pyanaconda/modules/common/constants/interfaces.py @@ -79,6 +79,11 @@ basename="RPMOSTree" ) +PAYLOAD_SOURCE_RPM_OSTREE_CONTAINER = DBusInterfaceIdentifier( + namespace=SOURCE_NAMESPACE, + basename="RPMOSTreeContainer" +) + PAYLOAD_SOURCE = DBusInterfaceIdentifier( namespace=SOURCE_NAMESPACE ) diff --git a/pyanaconda/modules/common/structures/rpm_ostree.py b/pyanaconda/modules/common/structures/rpm_ostree.py index 9a09b77e300..38db37dcc8c 100644 --- a/pyanaconda/modules/common/structures/rpm_ostree.py +++ b/pyanaconda/modules/common/structures/rpm_ostree.py @@ -20,7 +20,7 @@ from dasbus.structure import DBusData from dasbus.typing import Str, Bool -__all__ = ["RPMOSTreeConfigurationData"] +__all__ = ["RPMOSTreeConfigurationData", "RPMOSTreeContainerConfigurationData"] class RPMOSTreeConfigurationData(DBusData): @@ -33,6 +33,11 @@ def __init__(self): self._ref = "" self._gpg_verification_enabled = True + @staticmethod + def is_container(): + """Is this native container source?""" + return False + @property def osname(self) -> Str: """Management root for OS installation.""" @@ -77,3 +82,70 @@ def gpg_verification_enabled(self) -> Bool: @gpg_verification_enabled.setter def gpg_verification_enabled(self, value: Bool): self._gpg_verification_enabled = value + + +class RPMOSTreeContainerConfigurationData(DBusData): + """Structure to hold RPM OSTree from container configuration.""" + + def __init__(self): + self._stateroot = "" + self._remote = "" + self._transport = "" + self._url = "" + self._signature_verification_enabled = True + + @staticmethod + def is_container(): + """Is this native container source?""" + return True + + @property + def stateroot(self) -> Str: + """Name for the state directory, also known as "osname". + + This could be optional. + """ + return self._stateroot + + @stateroot.setter + def stateroot(self, value: Str): + self._stateroot = value + + @property + def transport(self) -> Str: + """Ostree transport protocol used. + + This could be optional (default will be 'repository'). + """ + return self._transport + + @transport.setter + def transport(self, value: Str): + self._transport = value + + @property + def remote(self) -> Str: + """Name of the OSTree remote.""" + return self._remote + + @remote.setter + def remote(self, value: Str): + self._remote = value + + @property + def url(self) -> Str: + """URL of the repository to install from.""" + return self._url + + @url.setter + def url(self, value: Str): + self._url = value + + @property + def signature_verification_enabled(self) -> Bool: + """Is the GPG key verification enabled?""" + return self._signature_verification_enabled + + @signature_verification_enabled.setter + def signature_verification_enabled(self, value: Bool): + self._signature_verification_enabled = value diff --git a/pyanaconda/modules/payloads/constants.py b/pyanaconda/modules/payloads/constants.py index aa9632aa772..43d3feeb4ac 100644 --- a/pyanaconda/modules/payloads/constants.py +++ b/pyanaconda/modules/payloads/constants.py @@ -22,7 +22,8 @@ PAYLOAD_TYPE_DNF, PAYLOAD_TYPE_LIVE_OS, PAYLOAD_TYPE_LIVE_IMAGE, \ SOURCE_TYPE_LIVE_OS_IMAGE, SOURCE_TYPE_HMC, SOURCE_TYPE_CDROM, SOURCE_TYPE_REPO_FILES, \ SOURCE_TYPE_NFS, SOURCE_TYPE_URL, SOURCE_TYPE_HDD, SOURCE_TYPE_CDN, \ - SOURCE_TYPE_CLOSEST_MIRROR, PAYLOAD_TYPE_RPM_OSTREE, SOURCE_TYPE_RPM_OSTREE, \ + SOURCE_TYPE_CLOSEST_MIRROR, PAYLOAD_TYPE_RPM_OSTREE, \ + SOURCE_TYPE_RPM_OSTREE, SOURCE_TYPE_RPM_OSTREE_CONTAINER, \ SOURCE_TYPE_LIVE_IMAGE @@ -48,6 +49,7 @@ class SourceType(Enum): LIVE_OS_IMAGE = SOURCE_TYPE_LIVE_OS_IMAGE LIVE_IMAGE = SOURCE_TYPE_LIVE_IMAGE RPM_OSTREE = SOURCE_TYPE_RPM_OSTREE + RPM_OSTREE_CONTAINER = SOURCE_TYPE_RPM_OSTREE_CONTAINER HMC = SOURCE_TYPE_HMC CDROM = SOURCE_TYPE_CDROM CLOSEST_MIRROR = SOURCE_TYPE_CLOSEST_MIRROR diff --git a/pyanaconda/modules/payloads/kickstart.py b/pyanaconda/modules/payloads/kickstart.py index af16f38249e..2b3d0838f22 100644 --- a/pyanaconda/modules/payloads/kickstart.py +++ b/pyanaconda/modules/payloads/kickstart.py @@ -66,6 +66,7 @@ class PayloadKickstartSpecification(KickstartSpecification): "hmc": COMMANDS.Hmc, "liveimg": COMMANDS.Liveimg, "nfs": COMMANDS.NFS, + "ostreecontainer": COMMANDS.OSTreeContainer, "ostreesetup": COMMANDS.OSTreeSetup, "url": COMMANDS.Url } diff --git a/pyanaconda/modules/payloads/payload/factory.py b/pyanaconda/modules/payloads/payload/factory.py index c32c9ebd311..c6097287af2 100644 --- a/pyanaconda/modules/payloads/payload/factory.py +++ b/pyanaconda/modules/payloads/payload/factory.py @@ -58,7 +58,7 @@ def get_type_for_kickstart(cls, data): :param data: a kickstart data :return: a payload type """ - if data.ostreesetup.seen: + if data.ostreesetup.seen or data.ostreecontainer.seen: return PayloadType.RPM_OSTREE if data.liveimg.seen: diff --git a/pyanaconda/modules/payloads/payload/rpm_ostree/installation.py b/pyanaconda/modules/payloads/payload/rpm_ostree/installation.py index 63959a5c631..d995a4d8353 100644 --- a/pyanaconda/modules/payloads/payload/rpm_ostree/installation.py +++ b/pyanaconda/modules/payloads/payload/rpm_ostree/installation.py @@ -51,6 +51,60 @@ def safe_exec_with_redirect(cmd, argv, successful_return_codes=(0,), **kwargs): ) +def _get_ref(data): + """Get ref or name based on source. + + OSTree container don't have ref because it's specified by the container. In that case let's + return just url for reporting. + + :param data: OSTree source structure + :return str: ref or name based on source + """ + # Variable substitute the ref: https://pagure.io/atomic-wg/issue/299 + if data.is_container(): + # we don't have ref with container; there are not multiple references in one container + return data.url + else: + return RpmOstree.varsubst_basearch(data.ref) + + +def _get_stateroot(data): + """Get stateroot. + + The OSTree renamed old osname to stateroot for containers. + + :param data: OSTree source structure + :return str: stateroot or osname value based on source + """ + if data.is_container(): + # osname was renamed to stateroot so let's use the new name + if data.stateroot: + return data.stateroot + else: + # The stateroot doesn't have to be defined + # https://github.com/ostreedev/ostree-rs-ext/pull/462/files + # However, it's working just for a subset of calls now. + # TODO: Remove this when all ostree commands undestarstands this + return "default" + else: + return data.osname + + +def _get_verification_enabled(data): + """Find out if source has enabled verification. + + OSTree sources has different names for enabled verification. This helper function + will make the access consistent. + + :param data: OSTree source structure + :return bool: True if verification is enabled + """ + if data.is_container(): + return data.signature_verification_enabled + else: + return data.gpg_verification_enabled + + class PrepareOSTreeMountTargetsTask(Task): """Task to prepare OSTree mount targets.""" @@ -119,7 +173,10 @@ def _handle_var_mount_point(self, existing_mount_points): :param [] existing_mount_points: a list of existing mount points """ - var_root = '/ostree/deploy/' + self._source_config.osname + '/var' + # osname was used for ostreesetup but ostreecontainer renamed it to stateroot + stateroot = _get_stateroot(self._source_config) + + var_root = '/ostree/deploy/' + stateroot + '/var' if existing_mount_points.get("/var") is None: self._setup_internal_bindmount(var_root, dest='/var', recurse=False) else: @@ -325,7 +382,7 @@ def run(self): remote_options = {} - if not self._data.gpg_verification_enabled: + if not _get_verification_enabled(self._data): remote_options['gpg-verify'] = Variant('b', False) if not conf.payload.verify_ssl: @@ -336,9 +393,12 @@ def run(self): else: root = None + # Remote is set or it should be named as stateroot is + remote = self._data.remote or _get_stateroot(self._data) + repo.remote_change(root, OSTree.RepoRemoteChange.ADD_IF_NOT_EXISTS, - self._data.remote, + remote, self._data.url, Variant('a{sv}', remote_options), cancellable) @@ -413,7 +473,8 @@ def name(self): def run(self): # Variable substitute the ref: https://pagure.io/atomic-wg/issue/299 - ref = RpmOstree.varsubst_basearch(self._data.ref) + ref = _get_ref(self._data) + stateroot = _get_stateroot(self._data) self.report_progress(_("Deployment starting: {}").format(ref)) @@ -422,21 +483,39 @@ def run(self): ["admin", "--sysroot=" + self._sysroot, "os-init", - self._data.osname] + stateroot] ) - log.info("ostree admin deploy starting") + if self._data.is_container(): + log.info("ostree image deploy starting") - safe_exec_with_redirect( - "ostree", - ["admin", - "--sysroot=" + self._sysroot, - "deploy", - "--os=" + self._data.osname, - self._data.remote + ':' + ref] - ) + args = ["container", "image", "deploy", + "--sysroot=" + self._sysroot, + "--image=" + ref] + + if self._data.transport: + args.append("--transport=" + self._data.transport) + if self._data.stateroot: + args.append("--stateroot=" + self._data.stateroot) + if not self._data.signature_verification_enabled: + args.append("--no-signature-verification") + + safe_exec_with_redirect( + "ostree", + args + ) + else: + log.info("ostree admin deploy starting") + safe_exec_with_redirect( + "ostree", + ["admin", + "--sysroot=" + self._sysroot, + "deploy", + "--os=" + stateroot, + self._data.remote + ':' + ref] + ) - log.info("ostree admin deploy complete") + log.info("ostree deploy complete") self.report_progress(_("Deployment complete: {}").format(ref)) diff --git a/pyanaconda/modules/payloads/payload/rpm_ostree/rpm_ostree.py b/pyanaconda/modules/payloads/payload/rpm_ostree/rpm_ostree.py index 5d4758ccf82..024753d4215 100644 --- a/pyanaconda/modules/payloads/payload/rpm_ostree/rpm_ostree.py +++ b/pyanaconda/modules/payloads/payload/rpm_ostree/rpm_ostree.py @@ -45,7 +45,8 @@ def type(self): def supported_source_types(self): """Get list of sources supported by the RPM OSTree module.""" return [ - SourceType.RPM_OSTREE + SourceType.RPM_OSTREE, + SourceType.RPM_OSTREE_CONTAINER, ] def process_kickstart(self, data): diff --git a/pyanaconda/modules/payloads/source/Makefile.am b/pyanaconda/modules/payloads/source/Makefile.am index 582c9f66ec2..839d3b0109b 100644 --- a/pyanaconda/modules/payloads/source/Makefile.am +++ b/pyanaconda/modules/payloads/source/Makefile.am @@ -14,7 +14,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . -SUBDIRS = live_os hmc cdrom repo_files nfs url harddrive cdn closest_mirror rpm_ostree live_image +SUBDIRS = live_os hmc cdrom repo_files nfs url harddrive cdn closest_mirror rpm_ostree \ + rpm_ostree_container live_image pkgpyexecdir = $(pyexecdir)/py$(PACKAGE_NAME) payload_moduledir = $(pkgpyexecdir)/modules/payloads/source diff --git a/pyanaconda/modules/payloads/source/factory.py b/pyanaconda/modules/payloads/source/factory.py index cc55815d55b..898f75eb55c 100644 --- a/pyanaconda/modules/payloads/source/factory.py +++ b/pyanaconda/modules/payloads/source/factory.py @@ -70,6 +70,10 @@ def create_source(source_type: SourceType): from pyanaconda.modules.payloads.source.rpm_ostree.rpm_ostree import \ RPMOSTreeSourceModule return RPMOSTreeSourceModule() + elif source_type == SourceType.RPM_OSTREE_CONTAINER: + from pyanaconda.modules.payloads.source.rpm_ostree_container.rpm_ostree_container import \ + RPMOSTreeContainerSourceModule + return RPMOSTreeContainerSourceModule() raise ValueError("Unknown source type: {}".format(source_type)) @@ -104,6 +108,8 @@ def get_rpm_ostree_type_for_kickstart(ks_data): :param ks_data: kickstart data from DNF payload :return: SourceType value """ + if ks_data.ostreecontainer.seen: + return SourceType.RPM_OSTREE_CONTAINER if ks_data.ostreesetup.seen: return SourceType.RPM_OSTREE diff --git a/pyanaconda/modules/payloads/source/rpm_ostree/rpm_ostree.py b/pyanaconda/modules/payloads/source/rpm_ostree/rpm_ostree.py index 9bcb7eb25e7..50f25528fb5 100644 --- a/pyanaconda/modules/payloads/source/rpm_ostree/rpm_ostree.py +++ b/pyanaconda/modules/payloads/source/rpm_ostree/rpm_ostree.py @@ -110,6 +110,7 @@ def setup_kickstart(self, data): data.ostreesetup.url = self.configuration.url data.ostreesetup.ref = self.configuration.ref data.ostreesetup.nogpg = not self.configuration.gpg_verification_enabled + data.ostreesetup.seen = True def set_up_with_tasks(self): """Set up the installation source for installation. diff --git a/pyanaconda/modules/payloads/source/rpm_ostree_container/Makefile.am b/pyanaconda/modules/payloads/source/rpm_ostree_container/Makefile.am new file mode 100644 index 00000000000..e7098184d21 --- /dev/null +++ b/pyanaconda/modules/payloads/source/rpm_ostree_container/Makefile.am @@ -0,0 +1,22 @@ +# +# Copyright (C) 2024 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +pkgpyexecdir = $(pyexecdir)/py$(PACKAGE_NAME) +payload_moduledir = $(pkgpyexecdir)/modules/payloads/source/rpm_ostree_container +payload_module_PYTHON = $(srcdir)/*.py + +MAINTAINERCLEANFILES = Makefile.in + diff --git a/pyanaconda/modules/payloads/source/rpm_ostree_container/__init__.py b/pyanaconda/modules/payloads/source/rpm_ostree_container/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pyanaconda/modules/payloads/source/rpm_ostree_container/rpm_ostree_container.py b/pyanaconda/modules/payloads/source/rpm_ostree_container/rpm_ostree_container.py new file mode 100644 index 00000000000..d4c29171ff9 --- /dev/null +++ b/pyanaconda/modules/payloads/source/rpm_ostree_container/rpm_ostree_container.py @@ -0,0 +1,96 @@ +# +# The RPM OSTree source module. +# +# Copyright (C) 2024 Red Hat, Inc. +# +# 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, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties 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. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +from pyanaconda.anaconda_loggers import get_module_logger +from pyanaconda.core.i18n import _ +from pyanaconda.modules.common.structures.rpm_ostree import RPMOSTreeContainerConfigurationData +from pyanaconda.modules.payloads.constants import SourceType +from pyanaconda.modules.payloads.source.rpm_ostree.rpm_ostree import \ + RPMOSTreeSourceModule +from pyanaconda.modules.payloads.source.rpm_ostree_container.rpm_ostree_container_interface import \ + RPMOSTreeContainerSourceInterface + +log = get_module_logger(__name__) + +__all__ = ["RPMOSTreeContainerSourceModule"] + + +class RPMOSTreeContainerSourceModule(RPMOSTreeSourceModule): + """The RPM OSTree from container source module.""" + + def __init__(self): + super().__init__() + self._configuration = RPMOSTreeContainerConfigurationData() + + @property + def type(self): + """Get type of this source.""" + return SourceType.RPM_OSTREE_CONTAINER + + @property + def description(self): + """Get description of this source.""" + return _("RPM OSTree Container") + + def for_publication(self): + """Return a DBus representation.""" + return RPMOSTreeContainerSourceInterface(self) + + @property + def network_required(self): + """Does the source require a network? + + :return: True or False + """ + # the 'registry' transport value will most probably require network settings + # other transport ways shouldn't require that + if self._configuration.transport == "registry": + return True + + return False + + def process_kickstart(self, data): + """Process the kickstart data.""" + configuration = RPMOSTreeContainerConfigurationData() + + configuration.stateroot = data.ostreecontainer.stateroot or "" + configuration.url = data.ostreecontainer.url or "" + configuration.remote = data.ostreecontainer.remote or "" + configuration.transport = data.ostreecontainer.transport or "" + configuration.signature_verification_enabled = not data.ostreecontainer.noSignatureVerification + + self.set_configuration(configuration) + + def setup_kickstart(self, data): + """Setup the kickstart data.""" + data.ostreecontainer.stateroot = self.configuration.stateroot + data.ostreecontainer.remote = self.configuration.remote + data.ostreecontainer.transport = self.configuration.transport + data.ostreecontainer.url = self.configuration.url + data.ostreecontainer.noSignatureVerification = not self.configuration.signature_verification_enabled + data.ostreecontainer.seen = True + + def __repr__(self): + """Return a string representation of the source.""" + return "Source(type='{}', stateroot='{}', transport='{}', url='{}')".format( + self.type.value, + self.configuration.stateroot, + self.configuration.transport, + self.configuration.url + ) diff --git a/pyanaconda/modules/payloads/source/rpm_ostree_container/rpm_ostree_container_interface.py b/pyanaconda/modules/payloads/source/rpm_ostree_container/rpm_ostree_container_interface.py new file mode 100644 index 00000000000..00811599151 --- /dev/null +++ b/pyanaconda/modules/payloads/source/rpm_ostree_container/rpm_ostree_container_interface.py @@ -0,0 +1,57 @@ +# +# DBus interface for the RPM OSTree source module. +# +# Copyright (C) 2023 Red Hat, Inc. +# +# 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, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties 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. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +from dasbus.server.property import emits_properties_changed +from dasbus.typing import * # pylint: disable=wildcard-import +from dasbus.server.interface import dbus_interface +from pyanaconda.modules.common.constants.interfaces import PAYLOAD_SOURCE_RPM_OSTREE_CONTAINER +from pyanaconda.modules.common.structures.rpm_ostree import RPMOSTreeContainerConfigurationData +from pyanaconda.modules.payloads.source.source_base_interface import PayloadSourceBaseInterface + +__all__ = ["RPMOSTreeContainerSourceInterface"] + + +@dbus_interface(PAYLOAD_SOURCE_RPM_OSTREE_CONTAINER.interface_name) +class RPMOSTreeContainerSourceInterface(PayloadSourceBaseInterface): + """DBus interface for the RPM OSTree source module.""" + + def connect_signals(self): + """Connect the signals.""" + super().connect_signals() + self.watch_property("Configuration", self.implementation.configuration_changed) + + @property + def Configuration(self) -> Structure: + """The source configuration. + + :return: a structure of the type RPMOSTreeConfigurationData + """ + return RPMOSTreeContainerConfigurationData.to_structure( + self.implementation.configuration + ) + + @emits_properties_changed + def SetConfiguration(self, data: Structure): + """Set the source configuration. + + :param data: a structure of the type RPMOSTreeConfigurationData + """ + self.implementation.set_configuration( + RPMOSTreeContainerConfigurationData.from_structure(data) + ) diff --git a/pyanaconda/payload/rpmostreepayload.py b/pyanaconda/payload/rpmostreepayload.py index f2fe7ee1537..b929396f3ed 100644 --- a/pyanaconda/payload/rpmostreepayload.py +++ b/pyanaconda/payload/rpmostreepayload.py @@ -20,8 +20,10 @@ from subprocess import CalledProcessError -from pyanaconda.core.constants import PAYLOAD_TYPE_RPM_OSTREE, SOURCE_TYPE_RPM_OSTREE -from pyanaconda.modules.common.structures.rpm_ostree import RPMOSTreeConfigurationData +from pyanaconda.core.constants import PAYLOAD_TYPE_RPM_OSTREE, SOURCE_TYPE_RPM_OSTREE, \ + SOURCE_TYPE_RPM_OSTREE_CONTAINER +from pyanaconda.modules.common.structures.rpm_ostree import RPMOSTreeConfigurationData, \ + RPMOSTreeContainerConfigurationData from pyanaconda.progress import progressQ from pyanaconda.payload.base import Payload from pyanaconda.payload import utils as payload_utils @@ -62,13 +64,18 @@ def source_type(self): def _get_source_configuration(self): """Get the configuration of the RPM OSTree source. - :return: an instance of RPMOSTreeConfigurationData + :return: an instance of RPMOSTreeConfigurationData or RPMOSTreeContainerConfigurationData """ source_proxy = self.get_source_proxy() - return RPMOSTreeConfigurationData.from_structure( - source_proxy.Configuration - ) + if self.source_type == SOURCE_TYPE_RPM_OSTREE_CONTAINER: + return RPMOSTreeContainerConfigurationData.from_structure( + source_proxy.Configuration + ) + else: + return RPMOSTreeConfigurationData.from_structure( + source_proxy.Configuration + ) @property def kernel_version_list(self): @@ -124,11 +131,12 @@ def _install(self, data): ) task.run() - from pyanaconda.modules.payloads.payload.rpm_ostree.installation import \ - PullRemoteAndDeleteTask - task = PullRemoteAndDeleteTask(data) - task.progress_changed_signal.connect(self._progress_cb) - task.run() + if not data.is_container(): + from pyanaconda.modules.payloads.payload.rpm_ostree.installation import \ + PullRemoteAndDeleteTask + task = PullRemoteAndDeleteTask(data) + task.progress_changed_signal.connect(self._progress_cb) + task.run() from pyanaconda.modules.payloads.payload.rpm_ostree.installation import DeployOSTreeTask task = DeployOSTreeTask(data, conf.target.physical_root) diff --git a/tests/unit_tests/pyanaconda_tests/modules/payloads/payload/test_rpm_ostree.py b/tests/unit_tests/pyanaconda_tests/modules/payloads/payload/test_rpm_ostree.py index 7c4704187d2..c25e926b18a 100644 --- a/tests/unit_tests/pyanaconda_tests/modules/payloads/payload/test_rpm_ostree.py +++ b/tests/unit_tests/pyanaconda_tests/modules/payloads/payload/test_rpm_ostree.py @@ -17,7 +17,7 @@ # import unittest -from pyanaconda.core.constants import SOURCE_TYPE_RPM_OSTREE +from pyanaconda.core.constants import SOURCE_TYPE_RPM_OSTREE, SOURCE_TYPE_RPM_OSTREE_CONTAINER from pyanaconda.modules.payloads.constants import PayloadType from pyanaconda.modules.payloads.payload.rpm_ostree.rpm_ostree import RPMOSTreeModule from pyanaconda.modules.payloads.payload.rpm_ostree.rpm_ostree_interface import RPMOSTreeInterface @@ -47,7 +47,8 @@ def test_type(self): def test_supported_sources(self): """Test the SupportedSourceTypes property.""" assert self.interface.SupportedSourceTypes == [ - SOURCE_TYPE_RPM_OSTREE + SOURCE_TYPE_RPM_OSTREE, + SOURCE_TYPE_RPM_OSTREE_CONTAINER, ] @@ -85,6 +86,17 @@ def test_ostree_kickstart(self): self.shared_ks_tests.check_kickstart(ks_in, ks_out) self._check_properties(SOURCE_TYPE_RPM_OSTREE) + def test_ostree_container_kickstart(self): + ks_in = """ + ostreecontainer --stateroot="fedora-coreos" --transport="repository" --remote="fedora" --url="quay.io/fedora/coreos:stable" --no-signature-verification + """ + ks_out = """ + # OSTree container setup + ostreecontainer --stateroot="fedora-coreos" --remote="fedora" --no-signature-verification --transport="repository" --url="quay.io/fedora/coreos:stable" + """ + self.shared_ks_tests.check_kickstart(ks_in, ks_out) + self._check_properties(SOURCE_TYPE_RPM_OSTREE_CONTAINER) + def test_priority_kickstart(self): ks_in = """ ostreesetup --osname="fedora-iot" --url="https://compose/iot/" --ref="fedora/iot" @@ -96,3 +108,15 @@ def test_priority_kickstart(self): """ self.shared_ks_tests.check_kickstart(ks_in, ks_out) self._check_properties(SOURCE_TYPE_RPM_OSTREE) + + def test_ostreecontainer_priority_kickstart(self): + ks_in = """ + url --url="https://compose/Everything" + ostreecontainer --stateroot="fedora-coreos" --remote="fedora" --url="quay.io/fedora/coreos:stable" + """ + ks_out = """ + # OSTree container setup + ostreecontainer --stateroot="fedora-coreos" --remote="fedora" --url="quay.io/fedora/coreos:stable" + """ + self.shared_ks_tests.check_kickstart(ks_in, ks_out) + self._check_properties(SOURCE_TYPE_RPM_OSTREE_CONTAINER) diff --git a/tests/unit_tests/pyanaconda_tests/modules/payloads/payload/test_rpm_ostree_tasks.py b/tests/unit_tests/pyanaconda_tests/modules/payloads/payload/test_rpm_ostree_tasks.py index 9b8ef10068b..862f6c1f640 100644 --- a/tests/unit_tests/pyanaconda_tests/modules/payloads/payload/test_rpm_ostree_tasks.py +++ b/tests/unit_tests/pyanaconda_tests/modules/payloads/payload/test_rpm_ostree_tasks.py @@ -23,7 +23,8 @@ from unittest.mock import patch, call, MagicMock from pyanaconda.core.glib import Variant, GError -from pyanaconda.modules.common.structures.rpm_ostree import RPMOSTreeConfigurationData +from pyanaconda.modules.common.structures.rpm_ostree import RPMOSTreeConfigurationData, \ + RPMOSTreeContainerConfigurationData from pyanaconda.payload.errors import PayloadInstallError from pyanaconda.modules.payloads.payload.rpm_ostree.installation import \ @@ -46,17 +47,43 @@ def _make_config_data(): return data +def _make_container_config_data(): + """Create OSTree container configuration data for testing + + :return RPMOSTreeContainerConfigurationData: a data instance with all fields filled + """ + data = RPMOSTreeContainerConfigurationData() + data.url = "url" + data.stateroot = "osname" + data.signature_verification_enabled = True + data.transport = "oci" + data.remote = "remote" + return data + + class PrepareOSTreeMountTargetsTaskTestCase(unittest.TestCase): @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.execWithRedirect") def test_setup_internal_bindmount(self, exec_mock): - """Test OSTree mount target prepare task _setup_internal_bindmount()""" + """Test OSTree mount target prepare task _setup_internal_bindmount""" exec_mock.return_value = 0 data = _make_config_data() task = PrepareOSTreeMountTargetsTask("/sysroot", "/physroot", data) assert len(task._internal_mounts) == 0 + self._check_setup_internal_bindmount(task, exec_mock) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.execWithRedirect") + def test_container_setup_internal_bindmount(self, exec_mock): + """Test OSTree mount target prepare task _setup_internal_bindmount with ostreecontainer""" + exec_mock.return_value = 0 + data = _make_container_config_data() + task = PrepareOSTreeMountTargetsTask("/sysroot", "/physroot", data) + assert len(task._internal_mounts) == 0 + self._check_setup_internal_bindmount(task, exec_mock) + + def _check_setup_internal_bindmount(self, task, exec_mock): # everything left out task._setup_internal_bindmount("/src") exec_mock.assert_called_once_with("mount", ["--rbind", "/physroot/src", "/sysroot/src"]) @@ -104,6 +131,19 @@ def test_run_with_var(self, storage_mock, mkdir_mock, exec_mock): exec_mock.return_value = 0 data = _make_config_data() + self._check_run_with_var(data, storage_mock, mkdir_mock, exec_mock) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.execWithRedirect") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.mkdirChain") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.STORAGE") + def test_container_run_with_var(self, storage_mock, mkdir_mock, exec_mock): + """Test OSTree mount target prepare task run() with /var""" + exec_mock.return_value = 0 + + data = _make_container_config_data() + self._check_run_with_var(data, storage_mock, mkdir_mock, exec_mock) + + def _check_run_with_var(self, data, storage_mock, mkdir_mock, exec_mock): devicetree_mock = storage_mock.get_proxy() devicetree_mock.GetMountPoints.return_value = { @@ -159,6 +199,19 @@ def test_run_without_var(self, storage_mock, mkdir_mock, exec_mock): exec_mock.side_effect = [0] * 7 + [0, 65] * 5 + [0] * 3 data = _make_config_data() + self._check_run_without_var(data, storage_mock, mkdir_mock, exec_mock) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.execWithRedirect") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.mkdirChain") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.STORAGE") + def test_container_run_without_var(self, storage_mock, mkdir_mock, exec_mock): + """Test OSTree mount target prepare task run() without /var""" + exec_mock.side_effect = [0] * 7 + [0, 65] * 5 + [0] * 3 + + data = _make_container_config_data() + self._check_run_without_var(data, storage_mock, mkdir_mock, exec_mock) + + def _check_run_without_var(self, data, storage_mock, mkdir_mock, exec_mock): devicetree_mock = storage_mock.get_proxy() devicetree_mock.GetMountPoints.return_value = { @@ -213,6 +266,19 @@ def test_run_failed(self, storage_mock, mkdir_mock, exec_mock): exec_mock.return_value = 1 data = _make_config_data() + self._check_run_failed(data, storage_mock, mkdir_mock, exec_mock) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.execWithRedirect") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.mkdirChain") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.STORAGE") + def test_container_run_failed(self, storage_mock, mkdir_mock, exec_mock): + """Test the failed OSTree mount target prepare task.""" + exec_mock.return_value = 1 + + data = _make_container_config_data() + self._check_run_failed(data, storage_mock, mkdir_mock, exec_mock) + + def _check_run_failed(self, data, storage_mock, mkdir_mock, exec_mock): devicetree_mock = storage_mock.get_proxy() devicetree_mock.GetMountPoints.return_value = { @@ -351,57 +417,178 @@ def test_run(self, exec_mock): class ChangeOSTreeRemoteTaskTestCase(unittest.TestCase): + + def _get_repo(self, sysroot_cls): + """Create up the OSTree repo mock.""" + repo_mock = MagicMock() + sysroot_mock = sysroot_cls.new() + sysroot_mock.get_repo.return_value = [None, repo_mock] + return repo_mock + + def _check_remote_changed(self, repo, remote="remote", sysroot_file=None, options=None): + """Check the remote_changed method.""" + repo.remote_change.assert_called_once() + args, kwargs = repo.remote_change.call_args + + print(args) + assert len(args) == 6 + assert len(kwargs) == 0 + + assert args[0] == sysroot_file + assert args[2] == remote + assert args[3] == "url" + assert args[4].unpack() == (options or {}) + assert args[5] is None + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.Gio.File") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.OSTree.Sysroot") + def test_install(self, sysroot_cls, gio_file_cls): + """Test the ChangeOSTreeRemoteTask installation task.""" + data = _make_config_data() + repo = self._get_repo(sysroot_cls) + + task = ChangeOSTreeRemoteTask(data, False, "/physroot") + task.run() + + self._check_remote_changed(repo, sysroot_file=None) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.Gio.File") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.OSTree.Sysroot") + def test_container_install(self, sysroot_cls, gio_file_cls): + """Test the ChangeOSTreeRemoteTask installation task with ostreecontainer.""" + data = _make_container_config_data() + repo = self._get_repo(sysroot_cls) + + task = ChangeOSTreeRemoteTask(data, False, "/physroot") + task.run() + + self._check_remote_changed(repo, sysroot_file=None) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.Gio.File") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.OSTree.Sysroot") + def test_container_install_no_remote(self, sysroot_cls, gio_file_cls): + """Test the ChangeOSTreeRemoteTask task with ostreecontainer no remote.""" + data = _make_container_config_data() + data.remote = None + repo = self._get_repo(sysroot_cls) + + task = ChangeOSTreeRemoteTask(data, False, "/physroot") + task.run() + + # remote is taken from the stateroot value + self._check_remote_changed(repo, remote="osname", sysroot_file=None) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.Gio.File") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.OSTree.Sysroot") + def test_container_install_no_remote_and_stateroot(self, sysroot_cls, gio_file_cls): + """Test the ChangeOSTreeRemoteTask task with ostreecontainer no remote + stateroot.""" + data = _make_container_config_data() + data.remote = None + data.stateroot = None + repo = self._get_repo(sysroot_cls) + + task = ChangeOSTreeRemoteTask(data, False, "/physroot") + task.run() + + # remote is taken from the stateroot value which when empty will be "default" + self._check_remote_changed(repo, remote="default", sysroot_file=None) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.Gio.File") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.OSTree.Sysroot") + def test_post_install(self, sysroot_cls, gio_file_cls): + """Test the ChangeOSTreeRemoteTask post-installation task.""" + data = _make_config_data() + repo = self._get_repo(sysroot_cls) + sysroot_file = gio_file_cls.new_for_path("/sysroot") + + task = ChangeOSTreeRemoteTask(data, True, "/sysroot") + task.run() + + self._check_remote_changed(repo, sysroot_file=sysroot_file) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.Gio.File") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.OSTree.Sysroot") + def test_container_post_install(self, sysroot_cls, gio_file_cls): + """Test the ChangeOSTreeRemoteTask post-installation task with ostreecontainer.""" + data = _make_container_config_data() + repo = self._get_repo(sysroot_cls) + sysroot_file = gio_file_cls.new_for_path("/sysroot") + + task = ChangeOSTreeRemoteTask(data, True, "/sysroot") + task.run() + + self._check_remote_changed(repo, sysroot_file=sysroot_file) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.Gio.File") @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.OSTree.Sysroot") + def test_container_post_install_no_remote(self, sysroot_cls, gio_file_cls): + """Test the ChangeOSTreeRemoteTask post task with ostreecontainer no remote.""" + data = _make_container_config_data() + data.remote = None + repo = self._get_repo(sysroot_cls) + sysroot_file = gio_file_cls.new_for_path("/sysroot") + + task = ChangeOSTreeRemoteTask(data, True, "/sysroot") + task.run() + + # remote is taken from the stateroot value + self._check_remote_changed(repo, remote="osname", sysroot_file=sysroot_file) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.Gio.File") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.OSTree.Sysroot") + def test_container_post_install_no_remote_and_stateroot(self, sysroot_cls, gio_file_cls): + """Test the ChangeOSTreeRemoteTask post task with ostreecontainer no remote + stateroot.""" + data = _make_container_config_data() + data.remote = None + data.stateroot = None + repo = self._get_repo(sysroot_cls) + sysroot_file = gio_file_cls.new_for_path("/sysroot") + + task = ChangeOSTreeRemoteTask(data, True, "/sysroot") + task.run() + + # remote is taken from the stateroot value which when empty will be "default" + self._check_remote_changed(repo, remote="default", sysroot_file=sysroot_file) + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.conf") @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.Gio.File") - def _execute_run_once(self, use_sysroot, gpg_verify, verify_ssl, - gio_file_mock, conf_mock, sysroot_mock): - new_mock = sysroot_mock.new() - repo_mock = MagicMock() - new_mock.get_repo.return_value = [None, repo_mock] - conf_mock.payload.verify_ssl = verify_ssl - path_mock = gio_file_mock.new_for_path() - - data = RPMOSTreeConfigurationData() - data.url = "url" - data.osname = "osname" - data.gpg_verification_enabled = gpg_verify - data.ref = "ref" - data.remote = "remote" - - task = ChangeOSTreeRemoteTask(data, use_sysroot, "/physroot") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.OSTree.Sysroot") + def test_options(self, sysroot_cls, gio_file_cls, conf_mock): + """Test the remote options of the ChangeOSTreeRemoteTask task.""" + options = { + "gpg-verify": False, + "tls-permissive": True, + } + + data = _make_config_data() + repo = self._get_repo(sysroot_cls) + conf_mock.payload.verify_ssl = False + data.gpg_verification_enabled = False + + task = ChangeOSTreeRemoteTask(data, False, "/physroot") task.run() - repo_mock.remote_change.assert_called_once() - the_call = repo_mock.remote_change.mock_calls[0] - name, args, kwargs = the_call - print(the_call, name, args, kwargs) - assert len(args) == 6 + self._check_remote_changed(repo, sysroot_file=None, options=options) - if use_sysroot: - assert args[0] == path_mock - else: - assert args[0] is None - assert args[2] == "remote" - assert args[3] == "url" + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.conf") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.Gio.File") + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.OSTree.Sysroot") + def test_container_options(self, sysroot_cls, gio_file_cls, conf_mock): + """Test the remote options of the ChangeOSTreeRemoteTask task with ostreecontainer.""" + options = { + "gpg-verify": False, + "tls-permissive": True, + } + + data = _make_container_config_data() + repo = self._get_repo(sysroot_cls) + conf_mock.payload.verify_ssl = False + data.signature_verification_enabled = False + + task = ChangeOSTreeRemoteTask(data, False, "/physroot") + task.run() - expected = {} - if not gpg_verify: - expected["gpg-verify"] = False - if not verify_ssl: - expected["tls-permissive"] = True - var = args[4] - assert type(var) == Variant - assert var.unpack() == expected - - def test_run(self): - """Test OSTree remote change task""" - # pylint: disable=no-value-for-parameter - # check all combinations of all inputs - for use_sysroot in (True, False): - for verify_ssl in (True, False): - for gpg_verify in (True, False): - self._execute_run_once(use_sysroot, gpg_verify, verify_ssl) + self._check_remote_changed(repo, sysroot_file=None, options=options) class ConfigureBootloaderTaskTestCase(unittest.TestCase): @@ -527,6 +714,81 @@ def test_run(self, exec_mock): ]) # no need to mock RpmOstree.varsubst_basearch(), since "ref" won't change + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.execWithRedirect") + def test_container_run(self, exec_mock): + """Test OSTree deploy task""" + exec_mock.return_value = 0 + data = _make_container_config_data() + + task = DeployOSTreeTask(data, "/sysroot") + task.run() + + exec_mock.assert_has_calls([ + call("ostree", ["admin", "--sysroot=/sysroot", "os-init", "osname"]), + call("ostree", ["container", "image", "deploy", + "--sysroot=/sysroot", + "--image=url", + "--transport=oci", + "--stateroot=osname"]), + ]) + # no need to mock RpmOstree.varsubst_basearch(), since "ref" won't change + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.execWithRedirect") + def test_container_run_with_no_stateroot(self, exec_mock): + """Test OSTree deploy task ostreecontainer without stateroot.""" + exec_mock.return_value = 0 + data = _make_container_config_data() + data.stateroot = None + + task = DeployOSTreeTask(data, "/sysroot") + task.run() + + exec_mock.assert_has_calls([ + call("ostree", ["admin", "--sysroot=/sysroot", "os-init", "default"]), + call("ostree", ["container", "image", "deploy", + "--sysroot=/sysroot", + "--image=url", + "--transport=oci"]), + ]) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.execWithRedirect") + def test_container_run_with_no_transport(self, exec_mock): + """Test OSTree deploy task ostreecontainer without transport.""" + exec_mock.return_value = 0 + data = _make_container_config_data() + data.transport = None + + task = DeployOSTreeTask(data, "/sysroot") + task.run() + + exec_mock.assert_has_calls([ + call("ostree", ["admin", "--sysroot=/sysroot", "os-init", "osname"]), + call("ostree", ["container", "image", "deploy", + "--sysroot=/sysroot", + "--image=url", + "--stateroot=osname"]), + ]) + + @patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.execWithRedirect") + def test_container_run_with_no_verification(self, exec_mock): + """Test OSTree deploy task ostreecontainer without signature verification.""" + exec_mock.return_value = 0 + data = _make_container_config_data() + data.signature_verification_enabled = False + + task = DeployOSTreeTask(data, "/sysroot") + task.run() + + exec_mock.assert_has_calls([ + call("ostree", ["admin", "--sysroot=/sysroot", "os-init", "osname"]), + call("ostree", ["container", "image", "deploy", + "--sysroot=/sysroot", + "--image=url", + "--transport=oci", + "--stateroot=osname", + "--no-signature-verification"]), + ]) + class PullRemoteAndDeleteTaskTestCase(unittest.TestCase): # pylint: disable=unused-variable diff --git a/tests/unit_tests/pyanaconda_tests/modules/payloads/source/test_source_rpm_ostree_container.py b/tests/unit_tests/pyanaconda_tests/modules/payloads/source/test_source_rpm_ostree_container.py new file mode 100644 index 00000000000..e07d50d2bcf --- /dev/null +++ b/tests/unit_tests/pyanaconda_tests/modules/payloads/source/test_source_rpm_ostree_container.py @@ -0,0 +1,135 @@ +# +# Copyright (C) 2024 Red Hat, Inc. +# +# 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, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties 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. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +import unittest + +from pyanaconda.core.constants import SOURCE_TYPE_RPM_OSTREE_CONTAINER +from pyanaconda.modules.common.constants.interfaces import PAYLOAD_SOURCE_RPM_OSTREE_CONTAINER +from pyanaconda.modules.payloads.constants import SourceType, SourceState +from pyanaconda.modules.common.structures.rpm_ostree import RPMOSTreeContainerConfigurationData +from pyanaconda.modules.payloads.source.rpm_ostree_container.rpm_ostree_container import \ + RPMOSTreeContainerSourceModule +from pyanaconda.modules.payloads.source.rpm_ostree_container.rpm_ostree_container_interface \ + import RPMOSTreeContainerSourceInterface + +from tests.unit_tests.pyanaconda_tests import check_dbus_property + + +class OSTreeContainerSourceInterfaceTestCase(unittest.TestCase): + """Test the DBus interface of the OSTree source.""" + + def setUp(self): + self.module = RPMOSTreeContainerSourceModule() + self.interface = RPMOSTreeContainerSourceInterface(self.module) + + def _check_dbus_property(self, *args, **kwargs): + check_dbus_property( + PAYLOAD_SOURCE_RPM_OSTREE_CONTAINER, + self.interface, + *args, **kwargs + ) + + def test_type(self): + """Test the Type property.""" + assert SOURCE_TYPE_RPM_OSTREE_CONTAINER == self.interface.Type + + def test_description(self): + """Test the Description property.""" + assert "RPM OSTree Container" == self.interface.Description + + def test_configuration(self): + """Test the configuration property.""" + data = RPMOSTreeContainerConfigurationData() + data.stateroot = "fedora-coreos" + data.remote = "fcos-28" + data.transport = "registry" + data.url = "quay.io/fedora/coreos:stable" + data._signature_verification_enabled = False + + self._check_dbus_property( + "Configuration", + RPMOSTreeContainerConfigurationData.to_structure(data) + ) + + +class OSTreeContainerSourceTestCase(unittest.TestCase): + """Test the OSTree source module.""" + + def setUp(self): + self.module = RPMOSTreeContainerSourceModule() + + def test_type(self): + """Test the type property.""" + assert SourceType.RPM_OSTREE_CONTAINER == self.module.type + + def test_network_required(self): + """Test the network_required property.""" + assert self.module.network_required is False + + data = RPMOSTreeContainerConfigurationData() + + data.transport = "oci" + self.module.set_configuration(data) + assert self.module.network_required is False + + data.transport = "oci-archive" + self.module.set_configuration(data) + assert self.module.network_required is False + + data.transport = "registry" + self.module.set_configuration(data) + assert self.module.network_required is True + + def test_required_space(self): + """Test the required_space property.""" + assert self.module.required_space == 0 + + def test_get_state(self): + """Test the source state.""" + assert SourceState.NOT_APPLICABLE == self.module.get_state() + + def test_set_up_with_tasks(self): + """Test the set-up tasks.""" + assert self.module.set_up_with_tasks() == [] + + def test_tear_down_with_tasks(self): + """Test the tear-down tasks.""" + assert self.module.tear_down_with_tasks() == [] + + def test_repr(self): + """Test the string representation.""" + assert repr(self.module) == str( + "Source(" + "type='RPM_OSTREE_CONTAINER', " + "stateroot='', " + "transport='', " + "url=''" + ")" + ) + + self.module.configuration.stateroot = "fcos" + self.module.configuration.transport = "registry" + self.module.configuration.url = "quay.io/fedora/coreos:stable" + + assert repr(self.module) == str( + "Source(" + "type='RPM_OSTREE_CONTAINER', " + "stateroot='fcos', " + "transport='registry', " + "url='quay.io/fedora/coreos:stable'" + ")" + ) diff --git a/tests/unit_tests/pyanaconda_tests/modules/payloads/test_module_payloads.py b/tests/unit_tests/pyanaconda_tests/modules/payloads/test_module_payloads.py index 4c97929ad7e..3d53b9b2136 100644 --- a/tests/unit_tests/pyanaconda_tests/modules/payloads/test_module_payloads.py +++ b/tests/unit_tests/pyanaconda_tests/modules/payloads/test_module_payloads.py @@ -59,6 +59,7 @@ def test_kickstart_properties(self): "hmc", "liveimg", "nfs", + "ostreecontainer", "ostreesetup", "url" ]