Skip to content

Commit

Permalink
Introduce a branch_version concept in BCI
Browse files Browse the repository at this point in the history
Now that we have build_flavors, we can add branch_versions, describing
the full hierachy:

Each container can have a single `name`, one or many `branch_versions`
and each of them one or many `build_flavors`.

this means our naming should become

{name}-{branch_version}-{build_flavor}

Introduce this for the AppCollection containers first.  This should
eventually replace tag_version, which is a broken concept as it
mixes namings, versions and flavors into and hence makes everything
convoluted.

This commit mixes also a hook in Registry to use version, rather than
tag_version for the build_version, which is a must-have for the
AppCollection integration.

Add tests for ApplicationStackContainers in unit test, which was
entirely untested so far.

Removal of the deprecated tag_version will happen in a later followup.
  • Loading branch information
dirkmueller committed Oct 11, 2024
1 parent 0fad76c commit cce5677
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 23 deletions.
60 changes: 40 additions & 20 deletions src/bci_build/package/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from typing import overload

import jinja2
from packaging import version

from bci_build.container_attributes import Arch
from bci_build.container_attributes import BuildType
Expand Down Expand Up @@ -1135,10 +1134,14 @@ class DevelopmentContainer(BaseContainerImage):
#: used for `org.opencontainers.image.version`
version: str | int = ""

#: the version-$variant to use in the first build_tag. defaults to version
#: if not set.
#: the version-$variant to use in the first build_tag. defaults to version(-$build_flavor)
#: if not set. Deprecated; use branch_version instead.
tag_version: str | None = None

#: the version branch for this container to disambiguate the build name and uid
#: defaults to tag_version if not set.
branch_version: str | None = None

# a rolling stability tag like 'stable' or 'oldstable' that will be added first
stability_tag: str | None = None

Expand All @@ -1152,7 +1155,11 @@ def __post_init__(self) -> None:
super().__post_init__()

if self.version and not self.tag_version:
self.tag_version = self.version
self.tag_version = (
f"{self.version}-{self.build_flavor}"
if self.build_flavor
else self.version
)
if not self.tag_version:
raise ValueError("A development container requires a tag_version")

Expand All @@ -1174,9 +1181,21 @@ def image_type(self) -> ImageType:
def oci_version(self) -> str:
return str(self.version)

@property
def _variant(self) -> str:
"""return the variant of this container in the name/branch/flavor hierarchy."""
branch_version = (
self.branch_version if self.branch_version else self.tag_version
)
return (
f"{branch_version}-{self.build_flavor}"
if self.build_flavor
else branch_version
)

@property
def uid(self) -> str:
return f"{self.name}-{self.tag_version}" if self.version_in_uid else self.name
return f"{self.name}-{self._variant}" if self.version_in_uid else self.name

@property
def _stability_suffix(self) -> str:
Expand Down Expand Up @@ -1256,26 +1275,27 @@ def reference(self) -> str:

@property
def pretty_reference(self) -> str:
return f"{self.registry}/{self.registry_prefix}/{self.name}:{self.tag_version}"
return f"{self.registry}/{self.registry_prefix}/{self.name}:{self._variant}"

@property
def build_name(self) -> str | None:
"""Handles a build_name with respecting branch_version."""
if self.build_tags:
build_name: str = self.build_tags[0]
# Prefer branch_version over build_tags[0] (which is tag_version derived)
if self.branch_version:
build_name = f"{self.registry_prefix}/{self.name}-{self._variant}"
if self.is_singleton_image:
build_name = build_name.partition(":")[0]
return build_name.replace("/", ":").replace(":", "-")

return None

@property
def build_version(self) -> str | None:
build_ver = super().build_version
if build_ver:
container_version: str = self.tag_version
# if container_version is a numeric version and not a macro, then
# version.parse() returns a `Version` object => then we concatenate
# it with the existing build_version
# for non PEP440 versions, we'll get an exception and just return
# the parent's classes build_version
try:
version.parse(str(container_version))
stability_suffix: str = ""
if self._stability_suffix:
stability_suffix = "." + self._stability_suffix
return f"{build_ver}.{container_version}{stability_suffix}"
except version.InvalidVersion:
return build_ver
return self.publish_registry.build_version(build_ver, self)
return None


Expand Down
6 changes: 3 additions & 3 deletions src/bci_build/package/apache_tomcat.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ def _get_sac_supported_until(
and os_version.is_tumbleweed
),
version="%%tomcat_version%%",
branch_version=tomcat_ver,
build_flavor=f"openjdk{jre_version}",
additional_versions=[f"{tomcat_ver}-openjdk{jre_version}"],
_min_release_counter=None if not os_version.is_sle15 else 55,
tag_version=f"{tomcat_ver}-openjdk{jre_version}",
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=[
tomcat_pkg := (
Expand Down
26 changes: 26 additions & 0 deletions src/bci_build/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING

import packaging

from bci_build.os_version import OsVersion

if TYPE_CHECKING:
Expand Down Expand Up @@ -44,6 +46,26 @@ def registry_prefix(*, is_application: bool) -> str:
"""

@staticmethod
def build_version(base_version, container: "BaseContainerImage") -> str:
"""Return the build version to set for this container build."""
container_version: str = (
container._variant if container.branch_version else container.tag_version
)
# if container_version is a numeric version and not a macro, then
# version.parse() returns a `Version` object => then we concatenate
# it with the existing build_version
# for non PEP440 versions, we'll get an exception and just return
# the parent's classes build_version
try:
packaging.version.parse(str(container_version))
stability_suffix: str = ""
if container._stability_suffix:
stability_suffix = "." + container._stability_suffix
return f"{base_version}.{container_version}{stability_suffix}"
except packaging.version.InvalidVersion:
return base_version


class ApplicationCollectionRegistry(Registry):
"""Registry for the Rancher Application Collection Distribution Platform."""
Expand All @@ -61,6 +83,10 @@ def url(container: "BaseContainerImage") -> str:
def registry_prefix(*, is_application: bool) -> str:
return "containers"

@staticmethod
def build_version(base_version, container: "BaseContainerImage") -> str:
return container.version


class SUSERegistry(Registry):
"""Registry for the SUSE Registry."""
Expand Down
80 changes: 80 additions & 0 deletions tests/test_build_recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
from bci_build.container_attributes import PackageType
from bci_build.container_attributes import SupportLevel
from bci_build.os_version import OsVersion
from bci_build.package import ApplicationStackContainer
from bci_build.package import DevelopmentContainer
from bci_build.package import OsContainer
from bci_build.package import Package
from bci_build.package import _build_tag_prefix
from bci_build.registry import publish_registry
from bci_build.templates import DOCKERFILE_TEMPLATE
from bci_build.templates import KIWI_TEMPLATE

Expand Down Expand Up @@ -606,3 +609,80 @@ def test_build_recipe_templates(
)
def test_os_build_recipe_templates(kiwi_xml: str, image: OsContainer) -> None:
assert KIWI_TEMPLATE.render(image=image, INFOHEADER="Copyright header") == kiwi_xml


@pytest.mark.parametrize(
"dockerfile,image",
[
(
"""# SPDX-License-Identifier: MIT
# Copyright header
#!UseOBSRepositories
#!ExclusiveArch: aarch64 x86_64
#!BuildTag: containers/test:%%emacs_version%%
#!BuildTag: containers/test:%%emacs_version%%-%RELEASE%
#!ForceMultiVersion
#!BuildName: containers-test-42
#!BuildVersion: %%emacs_version%%
#!BuildRelease: 35
FROM registry.suse.com/bci/bci-micro:15.6 AS target
FROM bci/bci-base:15.6 AS builder
COPY --from=target / /target
RUN \\
zypper -n --installroot /target --gpg-auto-import-keys install --no-recommends emacs; \\
zypper -n clean; \\
##LOGCLEAN##
FROM registry.suse.com/bci/bci-micro:15.6
COPY --from=builder /target /
# Define labels according to https://en.opensuse.org/Building_derived_containers
# labelprefix=com.suse.application.test
LABEL org.opencontainers.image.title="Test"
LABEL org.opencontainers.image.description="Test container based on the SLE Base Container Image."
LABEL org.opencontainers.image.version="%%emacs_version%%"
LABEL org.opencontainers.image.url="https://apps.rancher.io/applications/test"
LABEL org.opencontainers.image.created="%BUILDTIME%"
LABEL org.opencontainers.image.vendor="SUSE LLC"
LABEL org.opencontainers.image.source="%SOURCEURL%"
LABEL org.opencontainers.image.ref.name="%%emacs_version%%-%RELEASE%"
LABEL org.opensuse.reference="dp.apps.rancher.io/containers/test:%%emacs_version%%-%RELEASE%"
LABEL org.openbuildservice.disturl="%DISTURL%"
LABEL com.suse.supportlevel="techpreview"
LABEL com.suse.supportlevel.until="2024-02-01"
LABEL com.suse.eula="sle-eula"
LABEL com.suse.lifecycle-url="https://www.suse.com/lifecycle#suse-linux-enterprise-server-15"
LABEL com.suse.release-stage="released"
# endlabelprefix
LABEL org.opencontainers.image.base.name="%BASE_REFNAME%"
LABEL org.opencontainers.image.base.digest="%BASE_DIGEST%"
LABEL io.artifacthub.package.readme-url="%SOURCEURL%/README.md"
""",
ApplicationStackContainer(
name="test",
pretty_name="Test",
supported_until=date(2024, 2, 1),
package_list=["emacs"],
package_name="test-image",
os_version=(os_version := OsVersion.SP6),
_publish_registry=publish_registry(os_version, app_collection=True),
from_target_image=f"{_build_tag_prefix(os_version)}/bci-micro:{OsContainer.version_to_container_os_version(os_version)}",
version="%%emacs_version%%",
branch_version=42,
),
)
],
)
def test_appcollection_app_templates(
dockerfile: str, image: ApplicationStackContainer
) -> None:
assert (
DOCKERFILE_TEMPLATE.render(
DOCKERFILE_RUN="RUN",
image=image,
INFOHEADER="# Copyright header",
LOG_CLEAN="##LOGCLEAN##",
)
== dockerfile
)

0 comments on commit cce5677

Please sign in to comment.