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