Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: Installs the given GardenLinux Python library
inputs:
version:
description: GardenLinux Python library version
default: "0.10.13"
default: "0.10.14"
python_version:
description: Python version to setup
default: "3.13"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "gardenlinux"
version = "0.10.13"
version = "0.10.14"
description = "Contains tools to work with the features directory of gardenlinux, for example deducting dependencies from feature sets or validating cnames"
authors = ["Garden Linux Maintainers <contact@gardenlinux.io>"]
license = "Apache-2.0"
Expand Down
1 change: 1 addition & 0 deletions src/gardenlinux/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
GL_DEB_REPO_BASE_URL = "https://packages.gardenlinux.io/gardenlinux"
GL_DISTRIBUTION_NAME = "Garden Linux"
GL_HOME_URL = "https://gardenlinux.io"
GL_PLATFORM_FRANKENSTEIN = "frankenstein"
GL_RELEASE_ID = "gardenlinux"
GL_REPOSITORY_URL = "https://github.com/gardenlinux/gardenlinux"
GL_SUPPORT_URL = "https://github.com/gardenlinux/gardenlinux"
Expand Down
110 changes: 67 additions & 43 deletions src/gardenlinux/features/cname.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
from configparser import UNNAMED_SECTION, ConfigParser
from os import PathLike, environ
from pathlib import Path
from typing import List, Optional, Self
from typing import Any, Dict, List, Optional, Self

from ..constants import (
ARCHS,
GL_BUG_REPORT_URL,
GL_DISTRIBUTION_NAME,
GL_HOME_URL,
GL_PLATFORM_FRANKENSTEIN,
GL_RELEASE_ID,
GL_SUPPORT_URL,
)
Expand Down Expand Up @@ -59,14 +60,21 @@ def __init__(
self._feature_flags_cached: Optional[List[str]] = None
self._feature_platforms_cached: Optional[List[str]] = None
self._feature_set_cached: Optional[str] = None
self._features_cached: Optional[Dict[str, Any]] = None
self._platform_cached: Optional[str] = None
self._platform_variant_cached: Optional[str] = None
self._flavor = ""
self._version = None

self._flag_frankenstein = bool(environ.get("GL_ALLOW_FRANKENSTEIN", False))

self._flag_multiple_platforms = bool(
environ.get("GL_ALLOW_FRANKENSTEIN", False)
environ.get("GL_ALLOW_MULTIPLE_PLATFORMS", False)
)

if self._flag_frankenstein:
self._flag_multiple_platforms = True

commit_id_or_hash = None

if version is not None:
Expand Down Expand Up @@ -213,6 +221,20 @@ def flavor(self) -> str:

return self._flavor

@property
def features(self) -> Dict[str, Any]:
"""
Returns the features for the cname parsed.

:return: (dict) Features of the cname
:since: 0.10.14
"""

if self._features_cached is None:
self._features_cached = Parser().filter_as_dict(self.flavor)

return self._features_cached

@property
def feature_set(self) -> str:
"""
Expand All @@ -239,7 +261,7 @@ def feature_set_element(self) -> str:
if self._feature_elements_cached is not None:
return ",".join(self._feature_elements_cached)

return ",".join(Parser().filter_as_dict(self.flavor)["element"])
return ",".join(self.features["element"])

@property
def feature_set_flag(self) -> str:
Expand All @@ -253,7 +275,7 @@ def feature_set_flag(self) -> str:
if self._feature_flags_cached is not None:
return ",".join(self._feature_flags_cached)

return ",".join(Parser().filter_as_dict(self.flavor)["flag"])
return ",".join(self.features["flag"])

@property
def feature_set_platform(self) -> str:
Expand All @@ -265,7 +287,7 @@ def feature_set_platform(self) -> str:
"""

if self._feature_platforms_cached is None:
platforms = Parser().filter_as_dict(self.flavor)["platform"]
platforms = self.features["platform"]
else:
platforms = self._feature_platforms_cached

Expand All @@ -274,7 +296,7 @@ def feature_set_platform(self) -> str:

assert len(platforms) < 2
"Only one platform is supported"
return platforms[0]
return platforms[0] # type: ignore[no-any-return]

@property
def feature_set_list(self) -> List[str]:
Expand All @@ -293,19 +315,25 @@ def feature_set_list(self) -> List[str]:
@property
def platform(self) -> str:
"""
Returns the feature set of type "platform" for the cname parsed.
Returns the platform for the cname parsed.

:return: (str) Feature set platforms
:return: (str) Platform
:since: 0.7.0
"""

if self._feature_platforms_cached is None:
platforms = Parser().filter_as_dict(self.flavor)["platform"]
else:
if self._platform_cached is not None:
platforms = [self._platform_cached]
elif self._feature_platforms_cached is not None:
platforms = self._feature_platforms_cached
else:
platforms = self.features["platform"]

if self._flag_frankenstein and len(platforms) > 1:
return GL_PLATFORM_FRANKENSTEIN

if not self._flag_multiple_platforms:
assert len(platforms) < 2
"Only one platform is supported"

return platforms[0]

Expand Down Expand Up @@ -345,18 +373,8 @@ def release_metadata_string(self) -> str:
:since: 1.0.0
"""

features = Parser().filter_as_dict(self.flavor)

if not self._flag_multiple_platforms:
assert len(features["platform"]) < 2
"Only one platform is supported"

commit_hash = self.commit_hash
commit_id = self.commit_id
elements = ",".join(features["element"])
flags = ",".join(features["flag"])
platform = features["platform"][0]
platforms = ",".join(features["platform"])
platform_variant = self.platform_variant
version = self.version

Expand Down Expand Up @@ -387,10 +405,10 @@ def release_metadata_string(self) -> str:
BUG_REPORT_URL="{GL_BUG_REPORT_URL}"
GARDENLINUX_CNAME="{self.cname}"
GARDENLINUX_FEATURES="{self.feature_set}"
GARDENLINUX_FEATURES_PLATFORMS="{platforms}"
GARDENLINUX_FEATURES_ELEMENTS="{elements}"
GARDENLINUX_FEATURES_FLAGS="{flags}"
GARDENLINUX_PLATFORM="{platform}"
GARDENLINUX_FEATURES_PLATFORMS="{self.feature_set_platform}"
GARDENLINUX_FEATURES_ELEMENTS="{self.feature_set_element}"
GARDENLINUX_FEATURES_FLAGS="{self.feature_set_flag}"
GARDENLINUX_PLATFORM="{self.platform}"
GARDENLINUX_PLATFORM_VARIANT="{platform_variant}"
GARDENLINUX_VERSION="{version}"
GARDENLINUX_COMMIT_ID="{commit_id}"
Expand Down Expand Up @@ -456,6 +474,7 @@ def _copy_from_cname_object(self, cname_object: Self) -> None:
self._feature_elements_cached = cname_object.feature_set_element.split(",")
self._feature_flags_cached = cname_object.feature_set_flag.split(",")
self._feature_platforms_cached = cname_object.feature_set_platform.split(",")
self._platform_cached = cname_object.platform
self._platform_variant_cached = cname_object.platform_variant
self._version = cname_object.version

Expand Down Expand Up @@ -531,9 +550,7 @@ def new_from_release_file(release_file: PathLike[str] | str) -> "CName":
"GARDENLINUX_CNAME",
"GARDENLINUX_COMMIT_ID_LONG",
"GARDENLINUX_FEATURES",
"GARDENLINUX_FEATURES_ELEMENTS",
"GARDENLINUX_FEATURES_FLAGS",
"GARDENLINUX_FEATURES_PLATFORMS",
"GARDENLINUX_PLATFORM",
"GARDENLINUX_VERSION",
):
if not release_config.has_option(UNNAMED_SECTION, release_field):
Expand All @@ -559,23 +576,30 @@ def new_from_release_file(release_file: PathLike[str] | str) -> "CName":
UNNAMED_SECTION, "GARDENLINUX_FEATURES"
).strip("\"'")

cname_object._feature_elements_cached = (
release_config.get(UNNAMED_SECTION, "GARDENLINUX_FEATURES_ELEMENTS")
.strip("\"'")
.split(",")
)
if release_config.has_option(UNNAMED_SECTION, "GARDENLINUX_FEATURES_ELEMENTS"):
cname_object._feature_elements_cached = (
release_config.get(UNNAMED_SECTION, "GARDENLINUX_FEATURES_ELEMENTS")
.strip("\"'")
.split(",")
)

cname_object._feature_flags_cached = (
release_config.get(UNNAMED_SECTION, "GARDENLINUX_FEATURES_FLAGS")
.strip("\"'")
.split(",")
)
if release_config.has_option(UNNAMED_SECTION, "GARDENLINUX_FEATURES_FLAGS"):
cname_object._feature_flags_cached = (
release_config.get(UNNAMED_SECTION, "GARDENLINUX_FEATURES_FLAGS")
.strip("\"'")
.split(",")
)

cname_object._feature_platforms_cached = (
release_config.get(UNNAMED_SECTION, "GARDENLINUX_FEATURES_PLATFORMS")
.strip("\"'")
.split(",")
)
if release_config.has_option(UNNAMED_SECTION, "GARDENLINUX_FEATURES_PLATFORMS"):
cname_object._feature_platforms_cached = (
release_config.get(UNNAMED_SECTION, "GARDENLINUX_FEATURES_PLATFORMS")
.strip("\"'")
.split(",")
)

cname_object._platform_cached = release_config.get(
UNNAMED_SECTION, "GARDENLINUX_PLATFORM"
).strip("\"'")

if release_config.has_option(UNNAMED_SECTION, "GARDENLINUX_PLATFORM_VARIANT"):
cname_object._platform_variant_cached = release_config.get(
Expand Down
23 changes: 21 additions & 2 deletions src/gardenlinux/s3/s3_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,26 @@ def upload_from_directory(

release_file = artifacts_dir.joinpath(f"{base_name}.release")

cname_object = CName.new_from_release_file(release_file)
try:
cname_object = CName.new_from_release_file(release_file)
except RuntimeError:
if not release_file.exists():
raise RuntimeError(
f"Release metadata file given is invalid: {release_file}"
)

release_config = ConfigParser(allow_unnamed_section=True)
release_config.read(release_file)

cname_object = CName(
release_config.get(UNNAMED_SECTION, "GARDENLINUX_CNAME").strip("\"'"),
commit_hash=release_config.get(
UNNAMED_SECTION, "GARDENLINUX_COMMIT_ID_LONG"
).strip("\"'"),
version=release_config.get(
UNNAMED_SECTION, "GARDENLINUX_VERSION"
).strip("\"'"),
)

if cname_object.version_and_commit_id is None:
raise RuntimeError(
Expand Down Expand Up @@ -170,7 +189,7 @@ def upload_from_directory(
commit_id_or_hash = cname_object.commit_id

metadata = {
"platform": cname_object.feature_set_platform,
"platform": cname_object.platform,
"architecture": arch,
"base_image": None,
"build_committish": commit_id_or_hash,
Expand Down
2 changes: 1 addition & 1 deletion tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
CONTAINER_NAME_ZOT_EXAMPLE = f"{REGISTRY}/{REPO_NAME}"
GARDENLINUX_ROOT_DIR_EXAMPLE = f"{TEST_DATA_DIR}/gardenlinux/.build"

TEST_PLATFORMS = ["aws", "azure", "gcp", "openstack", "openstackbaremetal", "metal"]
TEST_PLATFORMS = ["aws", "azure", "baremetal", "gcp", "openstack"]
TEST_ARCHITECTURES = ["arm64", "amd64"]
TEST_FEATURE_STRINGS_SHORT = ["gardener_prod"]
TEST_FEATURE_SET = "_slim,base,container"
Expand Down
2 changes: 1 addition & 1 deletion tests/s3/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class S3Env:


def make_cname(
flavor: str = "container",
flavor: str = "container_trustedboot_usi",
arch: str = "amd64",
version: str = "1234.1",
commit: str = "abc123long",
Expand Down
13 changes: 9 additions & 4 deletions tests/s3/constants.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-

RELEASE_DATA = """
GARDENLINUX_CNAME="container-amd64-1234.1"
GARDENLINUX_CNAME="container_trustedboot_usi-amd64-1234.1"
GARDENLINUX_VERSION=1234.1
GARDENLINUX_COMMIT_ID="abc123lo"
GARDENLINUX_COMMIT_ID_LONG="abc123long"
GARDENLINUX_PLATFORM="container"
GARDENLINUX_FEATURES="_usi,_trustedboot"
GARDENLINUX_FEATURES_ELEMENTS=
GARDENLINUX_FEATURES_FLAGS="_usi,_trustedboot"
Expand All @@ -19,19 +20,23 @@
build_timestamp: {build_timestamp}
logs: null
modifiers:
- _ephemeral
- _slim
- base
- container
- _usi
- _trustedboot
require_uefi: true
secureboot: true
published_image_metadata: null
s3_bucket: test-bucket
s3_key: meta/singles/container-amd64-1234.1-abc123lo
s3_key: meta/singles/container_trustedboot_usi-amd64-1234.1-abc123lo
test_result: null
version: '1234.1'
paths:
- name: container-amd64-1234.1-abc123lo.release
- name: container_trustedboot_usi-amd64-1234.1-abc123lo.release
s3_bucket_name: test-bucket
s3_key: objects/container-amd64-1234.1-abc123lo/container-amd64-1234.1-abc123lo.release
s3_key: objects/container_trustedboot_usi-amd64-1234.1-abc123lo/container_trustedboot_usi-amd64-1234.1-abc123lo.release
suffix: .release
md5sum: {md5sum}
sha256sum: {sha256sum}
Expand Down
24 changes: 24 additions & 0 deletions tests/s3/test_s3_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,30 @@ def test_upload_from_directory_invalid_artifact_name(s3_setup: S3Env) -> None:
assert len(list(bucket.objects.filter(Prefix=f"meta/singles/{env.cname}"))) == 1


def test_upload_from_directory_invalid_release_file_with_valid_cname(
s3_setup: S3Env,
) -> None:
"""
Raise RuntimeError if artifact release file is invalid but contains a valid cname.
"""
# Arrange
env = s3_setup
release_path = env.tmp_path / f"{env.cname}.release"
bad_data = RELEASE_DATA.replace(
"GARDENLINUX_FEATURES_PLATFORMS=", "GARDENLINUX_FEATURES_PLATFORMS_UNDEFINED="
)
release_path.write_text(bad_data)

artifacts = S3Artifacts(env.bucket_name)

# Act
artifacts.upload_from_directory(env.cname, env.tmp_path)

# Assert
bucket = env.s3.Bucket(env.bucket_name)
assert len(list(bucket.objects.filter(Prefix=f"meta/singles/{env.cname}"))) == 1


def test_upload_from_directory_commit_mismatch(s3_setup: S3Env) -> None:
"""
Validate that the release file may contain a different commit hash not matching the artifact name.
Expand Down
Loading