diff --git a/src/bci_build/containercrate.py b/src/bci_build/containercrate.py new file mode 100644 index 000000000..a0fc87ebb --- /dev/null +++ b/src/bci_build/containercrate.py @@ -0,0 +1,40 @@ +"""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 + container.all_build_flavors = self.all_build_flavors(container) + + def all_build_flavors(self, container): + """Return all build flavors for this container in the crate""" + return sorted( + self._all_build_flavors.get( + (container.os_version, container.package_name), [""] + ) + ) + + def multibuild(self, container): + """Return the _multibuild file string to write for this ContainerCrate.""" + flavors: str = "\n".join( + " " * 4 + f"{pkg}" + for pkg in sorted(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 a05d6c55d..009eae5d3 100644 --- a/src/bci_build/package/__init__.py +++ b/src/bci_build/package/__init__.py @@ -16,6 +16,7 @@ import jinja2 from packaging import version +from bci_build.containercrate import ContainerCrate from bci_build.templates import DOCKERFILE_TEMPLATE from bci_build.templates import INFOHEADER_TEMPLATE from bci_build.templates import KIWI_TEMPLATE @@ -458,6 +459,12 @@ class BaseContainerImage(abc.ABC): default_factory=dict ) + #: 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. @@ -1137,9 +1144,13 @@ def title(self) -> str: """ return f"{self.os_version.distribution_base_name} BCI {self.pretty_name}" + @property + def readme_name(self) -> str: + return f"README.{self.build_flavor}.md" if self.build_flavor else "README.md" + @property def readme_path(self) -> str: - return f"{self.package_name}/README.md" + return f"{self.package_name}/{self.readme_name}" @property def readme_url(self) -> str: @@ -1150,7 +1161,7 @@ def readme_url(self) -> str: if self.os_version.is_tumbleweed: return f"https://raw.githubusercontent.com/SUSE/BCI-dockerfile-generator/{self.os_version.deployment_branch_name}/{self.readme_path}" - return "%SOURCEURL%/README.md" + return f"%SOURCEURL%/{self.readme_name}" @property def readme(self) -> str: @@ -1278,14 +1289,17 @@ async def write_file_to_dest(fname: str, contents: str | bytes) -> None: await write_to_file(os.path.join(dest, fname), contents) if self.build_recipe_type == BuildType.DOCKER: - fname = "Dockerfile" infoheader = textwrap.indent(INFOHEADER_TEMPLATE, "# ") + fname = ( + f"Dockerfile.{self.build_flavor}" if self.build_flavor else "Dockerfile" + ) dockerfile = DOCKERFILE_TEMPLATE.render( image=self, INFOHEADER=infoheader, DOCKERFILE_RUN=DOCKERFILE_RUN, LOG_CLEAN=LOG_CLEAN, + BUILD_FLAVOR=self.build_flavor, ) if dockerfile[-1] != "\n": dockerfile += "\n" @@ -1320,9 +1334,21 @@ 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: + mname = "_multibuild" + tasks.append( + asyncio.ensure_future( + write_file_to_dest(mname, self.crate.multibuild(self)) + ) + ) + files.append(mname) + tasks.append( asyncio.ensure_future( - write_file_to_dest("_service", SERVICE_TEMPLATE.render(image=self)) + write_file_to_dest( + "_service", + SERVICE_TEMPLATE.render(image=self), + ) ) ) @@ -1361,8 +1387,8 @@ async def write_file_to_dest(fname: str, contents: str | bytes) -> None: tasks.append(write_file_to_dest(fname, contents)) if "README.md" not in self.extra_files: - files.append("README.md") - tasks.append(write_file_to_dest("README.md", self.readme)) + files.append(self.readme_name) + tasks.append(write_file_to_dest(self.readme_name, self.readme)) await asyncio.gather(*tasks) diff --git a/src/bci_build/package/apache_tomcat.py b/src/bci_build/package/apache_tomcat.py index 62dfd81be..31d849e8a 100644 --- a/src/bci_build/package/apache_tomcat.py +++ b/src/bci_build/package/apache_tomcat.py @@ -2,6 +2,7 @@ import datetime +from bci_build.containercrate import ContainerCrate from bci_build.package import CAN_BE_LATEST_OS_VERSION from bci_build.package import DOCKERFILE_RUN from bci_build.package import OsContainer @@ -52,9 +53,9 @@ def _get_sac_supported_until( TOMCAT_CONTAINERS = [ ApplicationCollectionContainer( name="apache-tomcat", - package_name=f"apache-tomcat-{tomcat_ver.partition('.')[0]}-java-{jre_version}-image" + package_name=f"apache-tomcat-{tomcat_ver.partition('.')[0]}-image" if os_version.is_tumbleweed - else f"sac-apache-tomcat-{tomcat_ver.partition('.')[0]}-java{jre_version}-image", + else f"sac-apache-tomcat-{tomcat_ver.partition('.')[0]}-image", pretty_name="Apache Tomcat", custom_description=( "Apache Tomcat is a free and open-source implementation of the Jakarta Servlet, " @@ -73,6 +74,7 @@ def _get_sac_supported_until( supported_until=_get_sac_supported_until( os_version=os_version, tomcat_ver=tomcat_ver, jre_major=jre_version ), + build_flavor=f"openjdk{jre_version}", additional_versions=[f"%%tomcat_version%%-openjdk{jre_version}"], from_target_image=f"{_build_tag_prefix(os_version)}/bci-micro:{OsContainer.version_to_container_os_version(os_version)}", package_list=[ @@ -124,6 +126,9 @@ def _get_sac_supported_until( ("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) diff --git a/src/bci_build/templates.py b/src/bci_build/templates.py index 42ce416c0..85e6a8588 100644 --- a/src/bci_build/templates.py +++ b/src/bci_build/templates.py @@ -194,13 +194,24 @@ """ +{%- 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" %}Dockerfile{% else %}{{ image.package_name }}.kiwi{% endif %} + +{%- 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 -%} +{% endfor %} """ ) diff --git a/src/staging/bot.py b/src/staging/bot.py index c7ddded7a..3a82ecd75 100644 --- a/src/staging/bot.py +++ b/src/staging/bot.py @@ -759,23 +759,18 @@ async def write_pkg_configs( will be added """ - tasks = [ - self._write_pkg_meta( - bci, - git_branch_name=git_branch_name, - target_obs_project=target_obs_project, - ) - for bci in packages - ] - + tasks = [] + pkg_metas_to_generate = set() for bci in packages: - tasks.append( - self._write_pkg_meta( - bci, - git_branch_name=git_branch_name, - target_obs_project=target_obs_project, + if bci.package_name not in pkg_metas_to_generate: + pkg_metas_to_generate.add(bci.package_name) + tasks.append( + self._write_pkg_meta( + bci, + git_branch_name=git_branch_name, + target_obs_project=target_obs_project, + ) ) - ) await asyncio.gather(*tasks) diff --git a/tests/test_service.py b/tests/test_service.py index b6e5795f7..da9ac19fb 100644 --- a/tests/test_service.py +++ b/tests/test_service.py @@ -1,3 +1,4 @@ +from bci_build.containercrate import ContainerCrate from bci_build.package import BuildType from bci_build.package import DevelopmentContainer from bci_build.package import OsVersion @@ -108,3 +109,38 @@ def test_service_with_replacement_docker(): """ ) + + +def test_service_with_multi_flavor_docker(): + containers = [ + DevelopmentContainer( + **_BASE_KWARGS, + build_recipe_type=BuildType.DOCKER, + build_flavor=flavor, + replacements_via_service=[ + Replacement(regex_in_build_description="%%my_ver%%", package_name="sh"), + ], + ) + for flavor in ("flavor1", "flavor2") + ] + containercrate = ContainerCrate(containers) + + assert ( + SERVICE_TEMPLATE.render( + image=containers[0], + ) + == """ + + + + Dockerfile.flavor1 + %%my_ver%% + sh + + + Dockerfile.flavor2 + %%my_ver%% + sh + +""" + )