From 6967aeee7aaa1a73d839b40dcbe66129848dd96f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dirk=20M=C3=BCller?= Date: Fri, 29 Nov 2024 16:49:07 +0100 Subject: [PATCH] Split ContainerCrate into ContainerCrateAssigner This way ContainerCrate can be instantiated per (os_version, package_name) and we can handle service_replacements properly. --- src/bci_build/containercrate.py | 90 ++++++++++++++++++-------- src/bci_build/package/apache_tomcat.py | 4 +- src/bci_build/package/python.py | 5 +- src/bci_build/service.py | 3 +- src/bci_build/templates.py | 10 +-- tests/test_crate.py | 5 +- tests/test_service.py | 44 ++----------- 7 files changed, 77 insertions(+), 84 deletions(-) diff --git a/src/bci_build/containercrate.py b/src/bci_build/containercrate.py index 40531ae5c..8011e2331 100644 --- a/src/bci_build/containercrate.py +++ b/src/bci_build/containercrate.py @@ -1,40 +1,56 @@ """Crate to handle multibuild containers in the generator.""" -import asyncio import os -from bci_build.templates import SERVICE_TEMPLATE from bci_build.util import write_to_file -class ContainerCrate: - """ContainerCrate is combining multiple container build flavors. - - This provides package-central functions like generating _service and - _multibuild files. - """ +class ContainerCrateAssigner: + """ContainerCrateAssigner is allocating ContainerCrates for a given list of containers.""" def __init__(self, containers: list): - """Assign the crate for every container.""" - self._all_build_flavors: dict[tuple, set] = {} + """Assign the ContainerCrate for every container in the list.""" + all_build_flavors: dict[tuple, set] = {} + all_containers: dict[tuple, list] = {} + for container in containers: if container.build_flavor: - self._all_build_flavors.setdefault( + all_build_flavors.setdefault( (container.os_version, container.package_name), set() ).add(container.build_flavor) + all_containers.setdefault( + (container.os_version, container.package_name), [] + ).append(container) - for container in containers: - if container.crate is not None: - raise ValueError("Container is already part of a ContainerCrate") - container.crate = self + for os_version, package_name in all_containers: + print(f"os_version {os_version} package_name {package_name}") + crate = ContainerCrate( + sorted(all_build_flavors.get((os_version, package_name), [])), + all_containers[(os_version, package_name)], + ) + print(f"created crate with {crate._all_build_flavors}") + for container in all_containers[(os_version, package_name)]: + if container.crate is not None: + raise ValueError("Container is already part of a ContainerCrate") + container.crate = crate + + +class ContainerCrate: + """ContainerCrate represents a group of containers that share the same os_version/package_name and + only differ by flavor. - def all_build_flavors(self, container) -> list[str]: + This provides package-central functions like generating _service and + _multibuild files. + """ + + def __init__(self, all_build_flavors: list[str], all_containers: list): + """Assign the crate for every container.""" + self._all_build_flavors = all_build_flavors + self._all_containers = all_containers + + def all_build_flavors(self) -> 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), [""] - ) - ) + return self._all_build_flavors def default_dockerfile(self, container) -> str: buildrelease: str = "" @@ -47,14 +63,34 @@ def default_dockerfile(self, container) -> str: # For this container we only build the Dockerfile.$flavor builds. """ - def multibuild(self, container) -> str: + def multibuild(self) -> 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) + " " * 4 + f"{pkg}" for pkg in self.all_build_flavors() ) return f"\n{flavors}\n" + def services(self) -> str: + services = f""" + + +""" + for container in self._all_containers: + for replacement in container.replacements_via_service: + services += ( + " " + + "\n ".join( + str( + replacement.to_service( + f"Dockerfile.{container.build_flavor}" + ) + ).splitlines() + ) + + "\n" + ) + services = services + "" + return services + async def write_files_to_folder(self, dest: str, container) -> list[str]: """Write the files that this crate,container pair needs.""" files = [] @@ -66,12 +102,10 @@ async def write_files_to_folder(self, dest: str, container) -> list[str]: files.append(fname) fname = "_multibuild" - await write_to_file(os.path.join(dest, fname), self.multibuild(container)) + await write_to_file(os.path.join(dest, fname), self.multibuild()) files.append(fname) fname = "_service" - await write_to_file( - os.path.join(dest, fname), SERVICE_TEMPLATE.render(image=container) - ) + await write_to_file(os.path.join(dest, fname), self.services()) files.append(fname) return files diff --git a/src/bci_build/package/apache_tomcat.py b/src/bci_build/package/apache_tomcat.py index 414da83c4..42a5de859 100644 --- a/src/bci_build/package/apache_tomcat.py +++ b/src/bci_build/package/apache_tomcat.py @@ -4,7 +4,7 @@ from bci_build.container_attributes import TCP from bci_build.container_attributes import PackageType -from bci_build.containercrate import ContainerCrate +from bci_build.containercrate import ContainerCrateAssigner 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 @@ -150,4 +150,4 @@ def _get_java_packages(jre_major: int) -> list[str]: ) ] -TOMCAT_CRATE = ContainerCrate(TOMCAT_CONTAINERS) +ContainerCrateAssigner(TOMCAT_CONTAINERS) diff --git a/src/bci_build/package/python.py b/src/bci_build/package/python.py index 724fe52e3..fbbdb8ae6 100644 --- a/src/bci_build/package/python.py +++ b/src/bci_build/package/python.py @@ -5,7 +5,7 @@ from typing import Literal from bci_build.container_attributes import SupportLevel -from bci_build.containercrate import ContainerCrate +from bci_build.containercrate import ContainerCrateAssigner from bci_build.os_version import CAN_BE_LATEST_OS_VERSION from bci_build.os_version import _SUPPORTED_UNTIL_SLE from bci_build.os_version import OsVersion @@ -119,8 +119,7 @@ def _get_python_kwargs(py3_ver: _PYTHON_VERSIONS, os_version: OsVersion): ) for py_version in ("3.9", "3.11") ] - -SAC_PYTHON_CRATE = ContainerCrate(SAC_PYTHON_CONTAINERS) +ContainerCrateAssigner(SAC_PYTHON_CONTAINERS) PYTHON_3_6_CONTAINERS = ( diff --git a/src/bci_build/service.py b/src/bci_build/service.py index 8696c6337..11b285c0f 100644 --- a/src/bci_build/service.py +++ b/src/bci_build/service.py @@ -35,7 +35,8 @@ def as_xml_element(self) -> ET.Element: (p := ET.Element("param", attrib={"name": param[0]})).text = param[1] root.append(p) + ET.indent(root, space=" " * 4) return root def __str__(self) -> str: - return ET.tostring(self.as_xml_element()).decode("utf-8") + return ET.tostring(self.as_xml_element(), encoding="unicode") diff --git a/src/bci_build/templates.py b/src/bci_build/templates.py index a93d467ca..ddd075aaf 100644 --- a/src/bci_build/templates.py +++ b/src/bci_build/templates.py @@ -201,24 +201,18 @@ """ -{%- 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 %} +{%- elif (image.build_recipe_type|string) == "docker" %}Dockerfile {%- 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/tests/test_crate.py b/tests/test_crate.py index 15d0635ba..d584c4701 100644 --- a/tests/test_crate.py +++ b/tests/test_crate.py @@ -1,5 +1,5 @@ from bci_build.container_attributes import BuildType -from bci_build.containercrate import ContainerCrate +from bci_build.containercrate import ContainerCrateAssigner from bci_build.os_version import OsVersion from bci_build.package import DevelopmentContainer @@ -22,8 +22,9 @@ def test_multibuild_with_multi_flavor_docker(): ) for flavor in ("flavor1", "flavor2") ] + ContainerCrateAssigner(containers) assert ( - ContainerCrate(containers).multibuild(containers[0]) + containers[0].crate.multibuild() == """ flavor1 flavor2 diff --git a/tests/test_service.py b/tests/test_service.py index 7dd59d2c7..cd20157af 100644 --- a/tests/test_service.py +++ b/tests/test_service.py @@ -1,7 +1,6 @@ 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 @@ -15,10 +14,10 @@ def test_service_without_params_as_xml(): def test_service_with_params_as_xml(): - assert ( - """barfoo""" - == str(Service(name="foo", param=[("baz", "bar"), ("baz", "foo")])) - ) + assert """ + bar + foo +""" == str(Service(name="foo", param=[("baz", "bar"), ("baz", "foo")])) @pytest.mark.parametrize( @@ -167,38 +166,3 @@ 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(containers) - - assert ( - SERVICE_TEMPLATE.render( - image=containers[0], - ) - == """ - - - - Dockerfile.flavor1 - %%my_ver%% - sh - - - Dockerfile.flavor2 - %%my_ver%% - sh - -""" - )