diff --git a/src/bci_build/containercrate.py b/src/bci_build/containercrate.py deleted file mode 100644 index 6cd7046c9..000000000 --- a/src/bci_build/containercrate.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Crate to handle multibuild containers in the generator.""" - - -class ContainerCrate: - """ContainerCrate is combining multiple container build flavors. - - This provides package-central functions like generating _service and - _multibuild files. - """ - - def __init__(self, containers: list): - """Assign the crate for every container.""" - self._all_build_flavors: dict[tuple, set] = {} - for container in containers: - if container.build_flavor: - self._all_build_flavors.setdefault( - (container.os_version, container.package_name), set() - ).add(container.build_flavor) - - for container in containers: - if container.crate is not None: - raise ValueError("Container is already part of a ContainerCrate") - container.crate = self - - def all_build_flavors(self, container) -> list[str]: - """Return all build flavors for this container in the crate""" - return sorted( - self._all_build_flavors.get( - (container.os_version, container.package_name), [""] - ) - ) - - def default_dockerfile(self) -> str: - """Return a default Dockerfile to disable build on default flavor.""" - return """#!ExclusiveArch: do-not-build - -# For this container we only build the Dockerfile.$flavor builds. -""" - - def multibuild(self, container) -> str: - """Return the _multibuild file string to write for this ContainerCrate.""" - flavors: str = "\n".join( - " " * 4 + f"{pkg}" - for pkg in self.all_build_flavors(container) - ) - return f"\n{flavors}\n" diff --git a/src/bci_build/package/__init__.py b/src/bci_build/package/__init__.py index f33750fb3..644e9d066 100644 --- a/src/bci_build/package/__init__.py +++ b/src/bci_build/package/__init__.py @@ -21,10 +21,10 @@ from bci_build.container_attributes import PackageType from bci_build.container_attributes import ReleaseStage from bci_build.container_attributes import SupportLevel -from bci_build.containercrate import ContainerCrate from bci_build.os_version import ALL_OS_LTSS_VERSIONS from bci_build.os_version import RELEASED_OS_VERSIONS from bci_build.os_version import OsVersion +from bci_build.package.obs_package import ObsPackage from bci_build.registry import ApplicationCollectionRegistry from bci_build.registry import Registry from bci_build.registry import publish_registry @@ -32,7 +32,6 @@ from bci_build.templates import DOCKERFILE_TEMPLATE from bci_build.templates import INFOHEADER_TEMPLATE from bci_build.templates import KIWI_TEMPLATE -from bci_build.templates import SERVICE_TEMPLATE from bci_build.util import write_to_file _BASH_SET: str = "set -euo pipefail" @@ -135,24 +134,17 @@ def _build_tag_prefix(os_version: OsVersion) -> str: return "bci" -@dataclass -class BaseContainerImage(abc.ABC): +@dataclass(kw_only=True) +class BaseContainerImage(ObsPackage): """Base class for all Base Containers.""" #: Name of this image. It is used to generate the build tags, i.e. it #: defines under which name this image is published. name: str - #: The SLE service pack to which this package belongs - os_version: OsVersion - #: Human readable name that will be inserted into the image title and description pretty_name: str - #: Optional a package_name, used for creating the package name on OBS or IBS in - # ``devel:BCI:SLE-15-SP$ver`` (on OBS) or ``SUSE:SLE-15-SP$ver:Update:BCI`` (on IBS) - package_name: str | None = None - #: Epoch to use for handling os_version downgrades os_epoch: int | None = None @@ -210,9 +202,6 @@ class BaseContainerImage(abc.ABC): #: build flavors to produce for this container variant build_flavor: str | None = None - #: create that this container is part of - crate: ContainerCrate = None - #: Add any replacements via `obs-service-replace_using_package_version #: `_ #: that are used in this image into this list. @@ -316,6 +305,8 @@ def publish_registry(self) -> Registry: return self._publish_registry def __post_init__(self) -> None: + super().__post_init__() + self.pretty_name = self.pretty_name.strip() if not self.package_name: @@ -329,11 +320,6 @@ def __post_init__(self) -> None: "Cannot specify both a custom_end and a config.sh script! Use just config_sh_script." ) - if self.build_recipe_type is None: - self.build_recipe_type = ( - BuildType.KIWI if self.os_version == OsVersion.SP3 else BuildType.DOCKER - ) - if not self.maintainer: self.maintainer = ( "openSUSE (https://www.opensuse.org/)" @@ -364,12 +350,6 @@ def prepare_template(self) -> None: pass - @property - @abc.abstractmethod - def uid(self) -> str: - """unique identifier of this image, either its name or ``$name-$tag_version``.""" - pass - @property @abc.abstractmethod def oci_version(self) -> str: @@ -1047,12 +1027,34 @@ def kiwi_additional_tags(self) -> str | None: return ",".join(extra_tags) if extra_tags else None - async def write_files_to_folder(self, dest: str) -> list[str]: + @property + def services(self) -> tuple[Service, ...]: + if not self.replacements_via_service: + return () + + if self.build_recipe_type == BuildType.DOCKER: + if self.build_flavor: + default_file_name = f"Dockerfile.{self.build_flavor}" + else: + default_file_name = "Dockerfile" + elif self.build_recipe_type == BuildType.KIWI: + default_file_name = f"{self.package_name}.kiwi" + else: + raise ValueError(f"invalid build recipe type: {self.build_recipe_type}") + + return tuple( + replacement.to_service(default_file_name) + for replacement in self.replacements_via_service + ) + + async def write_files_to_folder( + self, dest: str, *, with_service_file: bool = True + ) -> list[str]: """Writes all files required to build this image into the destination folder and returns the filenames (not full paths) that were written to the disk. """ - files = ["_service"] + files = ["_service"] if with_service_file else [] tasks = [] self.prepare_template() @@ -1098,18 +1100,8 @@ async def write_file_to_dest(fname: str, contents: str | bytes) -> None: False ), f"got an unexpected build_recipe_type: '{self.build_recipe_type}'" - if self.build_flavor: - dfile = "Dockerfile" - tasks.append(write_file_to_dest(dfile, self.crate.default_dockerfile())) - files.append(dfile) - - mname = "_multibuild" - tasks.append(write_file_to_dest(mname, self.crate.multibuild(self))) - files.append(mname) - - tasks.append( - write_file_to_dest("_service", SERVICE_TEMPLATE.render(image=self)) - ) + if with_service_file: + tasks.append(self._write_service_file(dest)) changes_file_name = self.package_name + ".changes" if not (Path(dest) / changes_file_name).exists(): @@ -1475,7 +1467,7 @@ def generate_disk_size_constraints(size_gb: int) -> str: from .rust import RUST_CONTAINERS # noqa: E402 from .spack import SPACK_CONTAINERS # noqa: E402 -ALL_CONTAINER_IMAGE_NAMES: dict[str, BaseContainerImage] = { +ALL_CONTAINER_IMAGE_NAMES: dict[str, ObsPackage] = { f"{bci.uid}-{bci.os_version.pretty_print.lower()}": bci for bci in ( *BASE_CONTAINERS, @@ -1522,7 +1514,7 @@ def generate_disk_size_constraints(size_gb: int) -> str: SORTED_CONTAINER_IMAGE_NAMES = sorted( ALL_CONTAINER_IMAGE_NAMES, - key=lambda bci: f"{ALL_CONTAINER_IMAGE_NAMES[bci].os_version}-{ALL_CONTAINER_IMAGE_NAMES[bci].name}", + key=lambda bci: f"{ALL_CONTAINER_IMAGE_NAMES[bci].os_version}-{ALL_CONTAINER_IMAGE_NAMES[bci].uid}", ) diff --git a/src/bci_build/package/apache_tomcat.py b/src/bci_build/package/apache_tomcat.py index e0cbf9428..244bb01e0 100644 --- a/src/bci_build/package/apache_tomcat.py +++ b/src/bci_build/package/apache_tomcat.py @@ -3,7 +3,6 @@ import datetime from bci_build.container_attributes import PackageType -from bci_build.containercrate import ContainerCrate from bci_build.os_version import CAN_BE_LATEST_OS_VERSION from bci_build.os_version import OsVersion from bci_build.package import DOCKERFILE_RUN @@ -12,6 +11,7 @@ from bci_build.package import Package from bci_build.package import Replacement from bci_build.package import _build_tag_prefix +from bci_build.package.obs_package import MultiBuildObsPackage from bci_build.registry import publish_registry # last version needs to be the newest @@ -50,14 +50,11 @@ def _get_sac_supported_until( ) -TOMCAT_CONTAINERS = [ - ApplicationStackContainer( +def _create_tomcat_container( + os_version: OsVersion, tomcat_ver: str, jre_version: int +) -> ApplicationStackContainer: + return ApplicationStackContainer( name="apache-tomcat", - package_name=( - f"apache-tomcat-{tomcat_ver.partition('.')[0]}-image" - if os_version.is_tumbleweed - else f"sac-apache-tomcat-{tomcat_ver.partition('.')[0]}-image" - ), _publish_registry=publish_registry(os_version, app_collection=True), pretty_name="Apache Tomcat", custom_description=( @@ -125,15 +122,29 @@ def _get_sac_supported_until( entrypoint_user="tomcat", logo_url="https://tomcat.apache.org/res/images/tomcat.png", ) - for tomcat_ver, os_version, jre_version in ( - ("10.1", OsVersion.TUMBLEWEED, 22), - ("10.1", OsVersion.TUMBLEWEED, 21), - ("10.1", OsVersion.TUMBLEWEED, 17), - ("9", OsVersion.TUMBLEWEED, 17), - ("10.1", OsVersion.SP6, 21), - ("10.1", OsVersion.SP6, 17), - # (10.1, OsVersion.SP7, 21), - ) -] -TOMCAT_CRATE = ContainerCrate(TOMCAT_CONTAINERS) + +TOMCAT_CONTAINERS: list[MultiBuildObsPackage | ApplicationStackContainer] = [ + MultiBuildObsPackage.from_bcis( + bcis=[ + _create_tomcat_container(os_version, tomcat_ver, jre_version) + for tomcat_ver, os_version, jre_version in ( + ("10.1", OsVersion.TUMBLEWEED, 22), + ("10.1", OsVersion.TUMBLEWEED, 21), + ("10.1", OsVersion.TUMBLEWEED, 17), + ) + ], + package_name="apache-tomcat-10-image", + ), + _create_tomcat_container(OsVersion.TUMBLEWEED, "9", 17), + MultiBuildObsPackage.from_bcis( + package_name="sac-apache-tomcat-image", + bcis=[ + _create_tomcat_container(os_version, tomcat_ver, jre_version) + for tomcat_ver, os_version, jre_version in ( + ("10.1", OsVersion.SP6, 21), + ("10.1", OsVersion.SP6, 17), + ) + ], + ), +] diff --git a/src/bci_build/package/obs_package.py b/src/bci_build/package/obs_package.py new file mode 100644 index 000000000..0b8900fdd --- /dev/null +++ b/src/bci_build/package/obs_package.py @@ -0,0 +1,217 @@ +"""This module contains the classes for the ObsPackage container, which bundles +together multiple base container images into a single package. + +""" + +import abc +import asyncio +import os.path +import xml.etree.ElementTree as ET +from dataclasses import dataclass +from io import BytesIO +from typing import TYPE_CHECKING +from typing import Coroutine +from typing import Sequence + +from bci_build.container_attributes import BuildType +from bci_build.os_version import OsVersion +from bci_build.service import Service +from bci_build.util import write_to_file + +if TYPE_CHECKING: + from bci_build.package import BaseContainerImage + + +@dataclass(kw_only=True) +class ObsPackage(abc.ABC): + """Abstract base class of the ObsPackage and the BaseContainerImage.""" + + #: The name of the package in the Build Service + package_name: str | None = None + + #: The OS version to which this package belongs + os_version: OsVersion + + #: Define whether this container image is built using docker or kiwi. + #: If not set, then the build type will default to docker from SP4 onwards. + build_recipe_type: BuildType | None = None + + def __post_init__(self) -> None: + if self.build_recipe_type is None: + self.build_recipe_type = ( + BuildType.KIWI if self.os_version == OsVersion.SP3 else BuildType.DOCKER + ) + + @property + @abc.abstractmethod + def uid(self) -> str: + """unique identifier of this package, either its name or ``$name-$tag_version``.""" + + @property + @abc.abstractmethod + def services(self) -> tuple[Service, ...]: + """The source services that are part of this package.""" + + @property + @abc.abstractmethod + def title(self) -> str: + """The title of this package.""" + + @property + @abc.abstractmethod + def description(self) -> str: + """The description of this package.""" + + @abc.abstractmethod + async def write_files_to_folder( + self, dest: str, *, with_service_file: bool = True + ) -> list[str]: + """Write all files belonging to this package into the directory + ``dest``. + + If ``with_service_file`` is ``False``, then the :file:`_service` will + not be written to ``dest``. + + """ + + @property + def _service_file_contents(self) -> str: + root = ET.Element("services") + for service in [ + Service(name=f"{self.build_recipe_type}_label_helper"), + Service(name="kiwi_metainfo_helper"), + ] + list(self.services): + root.append(service.as_xml_element()) + + tree = ET.ElementTree(root) + ET.indent(tree) + io = BytesIO() + tree.write(io, encoding="utf-8") + io.seek(0) + return io.read().decode("utf-8") + + async def _write_service_file(self, dest: str) -> list[str]: + await write_to_file(os.path.join(dest, "_service"), self._service_file_contents) + return ["_service"] + + +@dataclass(kw_only=True) +class MultiBuildObsPackage(ObsPackage): + """ObsPackage is a container for combining multiple container images with + different build flavors into a single package. + + """ + + bcis: list["BaseContainerImage"] + + #: Optional custom title of this package. If unset, then the title of the + #: first bci is used. + custom_title: str | None = None + + #: Optional custom description of this package. If unset, then the + #: description of the first bci is used. + custom_description: str | None = None + + @staticmethod + def from_bcis( + bcis: Sequence["BaseContainerImage"], package_name: str | None = None + ) -> "MultiBuildObsPackage": + pkg_names: set[str] = set() + os_versions: set[OsVersion] = set() + multibuild_flavors: list[str | None] = [] + + for bci in bcis: + if bci.package_name: + pkg_names.add(bci.package_name) + os_versions.add(bci.os_version) + multibuild_flavors.append(bci.build_flavor) + + if len(pkg_names) != 1 and not package_name: + raise ValueError(f"got a non unique package name: {pkg_names}") + + if len(os_versions) != 1: + raise ValueError(f"got a non unique os_version: {os_versions}") + + if len(set(multibuild_flavors)) != len(multibuild_flavors): + raise ValueError( + f"The multibuild flavors are not unique: {multibuild_flavors}" + ) + + if not package_name: + package_name = pkg_names.pop() + + return MultiBuildObsPackage( + package_name=package_name, os_version=os_versions.pop(), bcis=list(bcis) + ) + + def __post_init__(self) -> None: + super().__post_init__() + + # we only support Dockerfile based multibuild at the moment + self.build_recipe_type = BuildType.DOCKER + + if not self.package_name: + raise ValueError("A package name must be provided") + + for bci in self.bcis: + if not bci.build_flavor: + raise ValueError(f"Container {bci.name} has no build flavor defined") + + if bci.build_recipe_type != BuildType.DOCKER: + raise ValueError(f"Container {bci.name} is not built from a Dockerfile") + + @property + def uid(self) -> str: + return self.package_name + + @property + def services(self) -> tuple[Service, ...]: + return tuple(service for bci in self.bcis for service in bci.services) + + async def write_files_to_folder( + self, dest: str, *, with_service_file=True + ) -> list[str]: + async def write_file_to_dest(fname: str, contents: str) -> list[str]: + await write_to_file(os.path.join(dest, fname), contents) + return [fname] + + tasks: list[Coroutine[None, None, list[str]]] = [] + for bci in self.bcis: + tasks.append(bci.write_files_to_folder(dest, with_service_file=False)) + + tasks.append(write_file_to_dest("Dockerfile", self.default_dockerfile)) + tasks.append(write_file_to_dest("_multibuild", self.multibuild)) + if with_service_file: + tasks.append(self._write_service_file(dest)) + + return [f for file_list in await asyncio.gather(*tasks) for f in file_list] + + @property + def title(self) -> str: + return self.custom_title or self.bcis[0].title + + @property + def description(self) -> str: + return self.custom_description or self.bcis[0].description + + @property + def default_dockerfile(self) -> str: + """Return a default :file:`Dockerfile` to disable the build for the + default flavor. + + """ + return """#!ExclusiveArch: do-not-build + +# For this container we only build the Dockerfile.$flavor builds. +""" + + @property + def multibuild(self) -> str: + """Return the contents of the :file:`_multibuild` file for this + package. + + """ + flavors: str = "\n".join( + " " * 4 + f"{pkg.build_flavor}" for pkg in self.bcis + ) + return f"\n{flavors}\n" diff --git a/src/bci_build/templates.py b/src/bci_build/templates.py index 4d3281832..e64a05bef 100644 --- a/src/bci_build/templates.py +++ b/src/bci_build/templates.py @@ -190,30 +190,3 @@ """ ) - -#: Jinja2 template used to generate :file:`_service`. -SERVICE_TEMPLATE = jinja2.Template( - """ - - -{%- set all_build_flavors = [""] %} -{%- if image.crate and image.build_flavor %} -{%- set all_build_flavors = image.crate.all_build_flavors(image) %} -{%- endif %} -{%- for flavor in all_build_flavors %} -{%- for replacement in image.replacements_via_service %} - - -{%- if replacement.file_name != None %}{{replacement.file_name}} -{%- elif (image.build_recipe_type|string) == "docker" %}{% if flavor %}Dockerfile.{{ flavor }}{% else %}Dockerfile{% endif %} -{%- else %}{{ image.package_name }}.kiwi -{%- endif %} - {{ replacement.regex_in_build_description }} - {{ replacement.package_name }}{% if replacement.parse_version %} - {{ replacement.parse_version }}{% endif %} - -{%- endfor -%} -{% endfor %} - -""" -) diff --git a/src/staging/bot.py b/src/staging/bot.py index cc0b56d0f..bb5e6e809 100644 --- a/src/staging/bot.py +++ b/src/staging/bot.py @@ -36,7 +36,7 @@ from bci_build.logger import LOGGER from bci_build.os_version import OsVersion from bci_build.package import ALL_CONTAINER_IMAGE_NAMES -from bci_build.package import BaseContainerImage +from bci_build.package.obs_package import ObsPackage from dotnet.updater import DOTNET_CONTAINERS from staging.build_result import PackageBuildResult from staging.build_result import PackageStatusCode @@ -218,7 +218,7 @@ def _devel_project_prjconf(self) -> bytes: ) @property - def _bcis(self) -> Generator[BaseContainerImage, None, None]: + def _bcis(self) -> Generator[ObsPackage, None, None]: """Generator yielding all :py:class:`~bci_build.package.BaseContainerImage` that have the same :py:attr:`~bci_build.package.BaseContainerImage.os_version` as this bot @@ -284,7 +284,7 @@ def package_names(self, pkgs: list[str] | None) -> None: self._packages = pkgs @property - def bcis(self) -> Generator[BaseContainerImage, None, None]: + def bcis(self) -> Generator[ObsPackage, None, None]: """Generator for creating an iterable yielding all :py:class:`~bci_build.package.BaseContainerImage` that are in the bot's staging project. @@ -715,7 +715,7 @@ async def remove_branch(): await asyncio.gather(*tasks) async def _write_pkg_meta( - self, bci_pkg: BaseContainerImage, target_obs_project: str, git_branch_name + self, bci_pkg: ObsPackage, target_obs_project: str, git_branch_name ) -> None: """Write the package ``_meta`` of the package with the name of the ``bci_pkg`` in the ``target_obs_project`` to be synced from the git @@ -758,7 +758,7 @@ async def link_base_container_to_staging(self) -> None: async def write_pkg_configs( self, - packages: Iterable[BaseContainerImage], + packages: Iterable[ObsPackage], git_branch_name: str, target_obs_project: str, ) -> None: @@ -1008,7 +1008,7 @@ async def write_all_image_build_recipes( for bci in self.bcis: - async def write_files(bci_pkg: BaseContainerImage, dest: str) -> list[str]: + async def write_files(bci_pkg: ObsPackage, dest: str) -> list[str]: await aiofiles.os.makedirs(dest, exist_ok=True) # remove everything *but* the changes file (.changes is not diff --git a/tests/test_crate.py b/tests/test_obs_package.py similarity index 53% rename from tests/test_crate.py rename to tests/test_obs_package.py index 15d0635ba..2d5d1a46e 100644 --- a/tests/test_crate.py +++ b/tests/test_obs_package.py @@ -1,7 +1,7 @@ from bci_build.container_attributes import BuildType -from bci_build.containercrate import ContainerCrate from bci_build.os_version import OsVersion from bci_build.package import DevelopmentContainer +from bci_build.package.obs_package import MultiBuildObsPackage _BASE_KWARGS = { "name": "test", @@ -14,16 +14,20 @@ def test_multibuild_with_multi_flavor_docker(): - containers = [ - DevelopmentContainer( - **_BASE_KWARGS, - build_recipe_type=BuildType.DOCKER, - build_flavor=flavor, - ) - for flavor in ("flavor1", "flavor2") - ] + pkg = MultiBuildObsPackage( + package_name="test", + bcis=[ + DevelopmentContainer( + **_BASE_KWARGS, + build_recipe_type=BuildType.DOCKER, + build_flavor=flavor, + ) + for flavor in ("flavor1", "flavor2") + ], + os_version=_BASE_KWARGS["os_version"], + ) assert ( - ContainerCrate(containers).multibuild(containers[0]) + pkg.multibuild == """ flavor1 flavor2 diff --git a/tests/test_service.py b/tests/test_service.py index 7dd59d2c7..e1c28fcf2 100644 --- a/tests/test_service.py +++ b/tests/test_service.py @@ -1,13 +1,12 @@ import pytest from bci_build.container_attributes import BuildType -from bci_build.containercrate import ContainerCrate from bci_build.os_version import OsVersion from bci_build.package import DevelopmentContainer from bci_build.package import ParseVersion from bci_build.package import Replacement +from bci_build.package.obs_package import MultiBuildObsPackage from bci_build.service import Service -from bci_build.templates import SERVICE_TEMPLATE def test_service_without_params_as_xml(): @@ -76,37 +75,35 @@ def test_replacement_to_service( def test_service_without_replacement_kiwi(): assert ( - SERVICE_TEMPLATE.render( - image=DevelopmentContainer(**_BASE_KWARGS, build_recipe_type=BuildType.KIWI) - ) + DevelopmentContainer( + **_BASE_KWARGS, build_recipe_type=BuildType.KIWI + )._service_file_contents == """ - - + + """ ) def test_service_with_replacement_kiwi(): assert ( - SERVICE_TEMPLATE.render( - image=DevelopmentContainer( - **_BASE_KWARGS, - build_recipe_type=BuildType.KIWI, - replacements_via_service=[ - Replacement( - regex_in_build_description="%%re%%", package_name="coreutils" - ), - Replacement( - regex_in_build_description="%%re%%", - package_name="coreutils", - file_name="replacementfile", - ), - ], - ) - ) + DevelopmentContainer( + **_BASE_KWARGS, + build_recipe_type=BuildType.KIWI, + replacements_via_service=[ + Replacement( + regex_in_build_description="%%re%%", package_name="coreutils" + ), + Replacement( + regex_in_build_description="%%re%%", + package_name="coreutils", + file_name="replacementfile", + ), + ], + )._service_file_contents == """ - - + + test-image.kiwi %%re%% @@ -123,31 +120,27 @@ def test_service_with_replacement_kiwi(): def test_service_with_replacement_docker(): assert ( - SERVICE_TEMPLATE.render( - image=DevelopmentContainer( - **_BASE_KWARGS, - build_recipe_type=BuildType.DOCKER, - replacements_via_service=[ - Replacement( - regex_in_build_description="%%my_ver%%", package_name="sh" - ), - Replacement( - regex_in_build_description="%%minor_ver%%", - package_name="filesystem", - parse_version=ParseVersion.MINOR, - ), - Replacement( - regex_in_build_description="%%minor_ver%%", - file_name="replacementfile", - package_name="filesystem", - parse_version=ParseVersion.MINOR, - ), - ], - ) - ) + DevelopmentContainer( + **_BASE_KWARGS, + build_recipe_type=BuildType.DOCKER, + replacements_via_service=[ + Replacement(regex_in_build_description="%%my_ver%%", package_name="sh"), + Replacement( + regex_in_build_description="%%minor_ver%%", + package_name="filesystem", + parse_version=ParseVersion.MINOR, + ), + Replacement( + regex_in_build_description="%%minor_ver%%", + file_name="replacementfile", + package_name="filesystem", + parse_version=ParseVersion.MINOR, + ), + ], + )._service_file_contents == """ - - + + Dockerfile %%my_ver%% @@ -181,15 +174,11 @@ def test_service_with_multi_flavor_docker(): ) for flavor in ("flavor1", "flavor2") ] - ContainerCrate(containers) - assert ( - SERVICE_TEMPLATE.render( - image=containers[0], - ) + MultiBuildObsPackage.from_bcis(containers)._service_file_contents == """ - - + + Dockerfile.flavor1 %%my_ver%%