diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 61f5764f..2e16b371 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -4,7 +4,7 @@ description: Installs the given GardenLinux Python library inputs: version: description: GardenLinux Python library version - default: "0.10.16" + default: "0.10.19" python_version: description: Python version to setup default: "3.13" diff --git a/poetry.lock b/poetry.lock index 14e51f2a..86c56063 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.0 and should not be changed by hand. [[package]] name = "alabaster" @@ -1061,40 +1061,6 @@ files = [ {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, ] -[[package]] -name = "gitdb" -version = "4.0.12" -description = "Git Object Database" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, - {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, -] - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.46" -description = "GitPython is a Python library used to interact with Git repositories" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058"}, - {file = "gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f"}, -] - -[package.dependencies] -gitdb = ">=4.0.1,<5" - -[package.extras] -doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy (==1.18.2) ; python_version >= \"3.9\"", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] - [[package]] name = "identify" version = "2.6.16" @@ -2349,6 +2315,18 @@ botocore = ">=1.37.4,<2.0a.0" [package.extras] crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] +[[package]] +name = "semver" +version = "3.0.4" +description = "Python helper for Semantic Versioning (https://semver.org)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746"}, + {file = "semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602"}, +] + [[package]] name = "six" version = "1.17.0" @@ -2361,18 +2339,6 @@ files = [ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] -[[package]] -name = "smmap" -version = "5.0.2" -description = "A pure Python implementation of a sliding window memory map manager" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, - {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, -] - [[package]] name = "snowballstemmer" version = "3.0.1" @@ -2745,4 +2711,4 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = ">=3.13,!=3.14.1" -content-hash = "370ae7612ae7e2b0fa13dad41fc8ab6b7b4ee6d4a837cfb33684142dfb6ad30d" +content-hash = "40674ce2106c417aafd13d0190f3078ef04a3c8d8a6678fe1b624e268307586b" diff --git a/pyproject.toml b/pyproject.toml index 559cbcd9..294d6316 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "gardenlinux" -version = "0.10.16" +version = "0.10.19" 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 "] license = "Apache-2.0" @@ -21,7 +21,7 @@ pygit2 = "^1.19.1" pygments = "^2.19.2" PyGithub = "^2.8.1" PyYAML = "^6.0.2" -gitpython = "^3.1.45" +semver = "^3.0.4" [tool.poetry.group.dev.dependencies] bandit = "^1.9.3" diff --git a/src/gardenlinux/constants.py b/src/gardenlinux/constants.py index fb242093..fef11566 100644 --- a/src/gardenlinux/constants.py +++ b/src/gardenlinux/constants.py @@ -145,6 +145,7 @@ GL_BUG_REPORT_URL = "https://github.com/gardenlinux/gardenlinux/issues" GL_COMMIT_SPECIAL_VALUES = ("local",) +GL_CONTAINER_REGISTRY_BASE_URL = "ghcr.io/gardenlinux/gardenlinux" GL_DEB_REPO_BASE_URL = "https://packages.gardenlinux.io/gardenlinux" GL_DISTRIBUTION_NAME = "Garden Linux" GL_HOME_URL = "https://gardenlinux.io" @@ -157,9 +158,7 @@ OCI_ANNOTATION_SIGNED_STRING_KEY = "io.gardenlinux.oci.signed-string" OCI_IMAGE_INDEX_MEDIA_TYPE = "application/vnd.oci.image.index.v1+json" -RELEASE_ID_FILE = ".github_release_id" - -REQUESTS_TIMEOUTS = (5, 30) # connect, read +REQUESTS_TIMEOUTS = (5, 60) # connect, read S3_DOWNLOADS_DIR = Path(os.path.dirname(__file__)) / ".." / "s3_downloads" @@ -167,7 +166,3 @@ GLVD_BASE_URL = "https://security.gardenlinux.org/v1" PODMAN_CONNECTION_MAX_IDLE_SECONDS = 3 - -# https://github.com/gardenlinux/gardenlinux/issues/3044 -# Empty string is the 'legacy' variant with traditional root fs and still needed/supported -IMAGE_VARIANTS = ["", "_usi", "_tpm2_trustedboot"] diff --git a/src/gardenlinux/distro_version.py b/src/gardenlinux/distro_version.py new file mode 100644 index 00000000..779df239 --- /dev/null +++ b/src/gardenlinux/distro_version.py @@ -0,0 +1,63 @@ +from semver import Version + + +class UnsupportedDistroVersion(Exception): + pass + + +class NotAPatchRelease(Exception): + pass + + +class DistroVersion(Version): # type: ignore[misc] + def __init__(self, version: str | Version): + self._version_format_without_patch_number = False + + try: + if isinstance(version, Version): + version_parsed = version + elif len(version.split(".")) == 2: + # Support version strings without patch numbers + version_parsed = Version.parse(f"{version}.0") + self._version_format_without_patch_number = True + else: + version_parsed = Version.parse(version) + except Exception as exc: + raise UnsupportedDistroVersion(exc) + + Version.__init__( + self, + version_parsed.major, + version_parsed.minor, + version_parsed.patch, + version_parsed.prerelease, + version_parsed.build, + ) + + @property + def is_patch_release(self) -> bool: + if self._version_format_without_patch_number: + return self.minor > 0 # type: ignore[no-any-return] + + return self.patch > 0 # type: ignore[no-any-return] + + @property + def previous_patch_release(self) -> str: + if not self.is_patch_release: + raise NotAPatchRelease(f"{self} is not a patch release") + + if self._version_format_without_patch_number: + previous_version = DistroVersion( + Version(self.major, self.minor - 1, self.patch) + ) + return f"{previous_version.major}.{previous_version.minor}" + + return str( + Version( + self.major, + self.minor, + self.patch - 1, + prerelease=self.prerelease, + build=self.build, + ) + ) diff --git a/src/gardenlinux/distro_version/__init__.py b/src/gardenlinux/distro_version/__init__.py deleted file mode 100644 index 5a9a6c8c..00000000 --- a/src/gardenlinux/distro_version/__init__.py +++ /dev/null @@ -1,73 +0,0 @@ -from typing import Self - - -class UnsupportedDistroVersion(Exception): - pass - - -class NotAPatchRelease(Exception): - pass - - -class BaseDistroVersion: - major: int = 0 - minor: int = 0 - patch: int = 0 - - def is_patch_release(self) -> int: - return self.patch and self.patch > 0 - - -class LegacyDistroVersion(BaseDistroVersion): - def __init__(self: Self, major: int, patch: int) -> None: - self.major = major - self.patch = patch - - def __str__(self) -> str: - return f"{self.major}.{self.patch}" - - def previous_patch_release(self) -> "LegacyDistroVersion": - if not self.is_patch_release(): - raise NotAPatchRelease(f"{self} is not a patch release") - - return LegacyDistroVersion(self.major, self.patch - 1) - - -class SemverDistroVersion(BaseDistroVersion): - def __init__(self, major: int, minor: int, patch: int) -> None: - self.major = major - self.minor = minor - self.patch = patch - - def __str__(self) -> str: - return f"{self.major}.{self.minor}.{self.patch}" - - def previous_patch_release(self) -> "SemverDistroVersion": - if not self.is_patch_release(): - raise NotAPatchRelease(f"{self} is not a patch release") - - return SemverDistroVersion(self.major, self.minor, self.patch - 1) - - -def DistroVersion( - maybe_distro_version: str, -) -> LegacyDistroVersion | SemverDistroVersion: - version_components = maybe_distro_version.split(".") - if len(version_components) > 3 or len(version_components) < 2: - raise UnsupportedDistroVersion( - f"Unexpected version number format {maybe_distro_version}" - ) - - if not all(map(lambda x: x.isdigit(), version_components)): - raise UnsupportedDistroVersion( - f"Unexpected version number format {maybe_distro_version}" - ) - - if len(version_components) == 2: - return LegacyDistroVersion(*(int(c) for c in version_components)) - elif len(version_components) == 3: - return SemverDistroVersion(*(int(c) for c in version_components)) - else: - raise UnsupportedDistroVersion( - f"Unexpected number of version components: {maybe_distro_version}" - ) diff --git a/src/gardenlinux/github/release/__init__.py b/src/gardenlinux/github/release/__init__.py index ac7d6fff..efaf4e00 100644 --- a/src/gardenlinux/github/release/__init__.py +++ b/src/gardenlinux/github/release/__init__.py @@ -1,109 +1,5 @@ -import json -import logging -import os -import sys - -import requests - -from ...constants import RELEASE_ID_FILE, REQUESTS_TIMEOUTS -from ...logger import LoggerSetup +from .deployment_platform import DeploymentPlatform from .release import Release +from .release_images_metadata import ReleaseImagesMetadata -LOGGER = LoggerSetup.get_logger("gardenlinux.github.release", logging.INFO) - - -def create_github_release( - owner: str, repo: str, tag: str, commitish: str, latest: bool, body: str -) -> int | None: - token = os.environ.get("GITHUB_TOKEN") - if not token: - raise ValueError("GITHUB_TOKEN environment variable not set") - - headers = { - "Authorization": f"token {token}", - "Accept": "application/vnd.github.v3+json", - } - - data = { - "tag_name": tag, - "target_commitish": commitish, - "name": tag, - "body": body, - "draft": False, - "prerelease": False, - "make_latest": "true" if latest else "false", - } - - response = requests.post( - f"https://api.github.com/repos/{owner}/{repo}/releases", - headers=headers, - data=json.dumps(data), - timeout=REQUESTS_TIMEOUTS, - ) - - if response.status_code == 201: - LOGGER.info("Release created successfully") - response_json = response.json() - return int(response_json.get("id")) # Will raise KeyError if missing - else: - LOGGER.error("Failed to create release") - LOGGER.debug(response.json()) - response.raise_for_status() - - return None # Simply to make mypy happy. should not be reached. - - -def write_to_release_id_file(release_id: str | int) -> None: - try: - with open(RELEASE_ID_FILE, "w") as file: - file.write(str(release_id)) - LOGGER.info(f"Created {RELEASE_ID_FILE} successfully.") - except IOError as e: - LOGGER.error(f"Could not create {RELEASE_ID_FILE} file: {e}") - sys.exit(1) - - -def upload_to_github_release_page( - github_owner: str, - github_repo: str, - gardenlinux_release_id: str | int, - file_to_upload: str, - dry_run: bool, -) -> None: - if dry_run: - LOGGER.info( - f"Dry run: would upload {file_to_upload} to release {gardenlinux_release_id} in repo {github_owner}/{github_repo}" - ) - return - - token = os.environ.get("GITHUB_TOKEN") - if not token: - raise ValueError("GITHUB_TOKEN environment variable not set") - - headers = { - "Authorization": f"token {token}", - "Content-Type": "application/octet-stream", - } - - upload_url = f"https://uploads.github.com/repos/{github_owner}/{github_repo}/releases/{gardenlinux_release_id}/assets?name={os.path.basename(file_to_upload)}" - - try: - with open(file_to_upload, "rb") as f: - file_contents = f.read() - except IOError as e: - LOGGER.error(f"Error reading file {file_to_upload}: {e}") - return - - response = requests.post( - upload_url, headers=headers, data=file_contents, timeout=REQUESTS_TIMEOUTS - ) - if response.status_code == 201: - LOGGER.info("Upload successful") - else: - LOGGER.error( - f"Upload failed with status code {response.status_code}: {response.text}" - ) - response.raise_for_status() - - -__all__ = ["Release", "write_to_release_id_file", "upload_to_github_release_page"] +__all__ = ["DeploymentPlatform", "Release", "ReleaseImagesMetadata"] diff --git a/src/gardenlinux/github/release/__main__.py b/src/gardenlinux/github/release/__main__.py index 013108b9..b18c5440 100644 --- a/src/gardenlinux/github/release/__main__.py +++ b/src/gardenlinux/github/release/__main__.py @@ -4,11 +4,7 @@ from gardenlinux.constants import GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME from gardenlinux.logger import LoggerSetup -from ..release_notes import create_github_release_notes -from . import ( - upload_to_github_release_page, - write_to_release_id_file, -) +from .notes import MarkdownGenerator from .release import Release LOGGER = LoggerSetup.get_logger("gardenlinux.github", logging.INFO) @@ -149,6 +145,10 @@ def get_parser() -> argparse.ArgumentParser: help="Perform a dry run without actually uploading the file.", ) + upload_parser.add_argument( + "--overwrite-same-name", action="store_true", default=False + ) + return parser @@ -166,27 +166,35 @@ def main() -> None: release.is_latest = args.latest release.create() elif args.command == "create-with-gl-release-notes": - body = create_github_release_notes( - args.tag, args.commit, GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME - ) + release = Release(args.repo, args.owner) + release.tag = args.tag + release.commitish = args.commit + release.is_latest = args.latest + + generator = MarkdownGenerator(release, GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME) + if args.dry_run: print("Dry Run ...") print("This release would be created:") - print(body) + print(str(generator)) else: - release = Release(args.repo, args.owner) - release.tag = args.tag - release.body = body - release.commitish = args.commit - release.is_latest = args.latest + release.body = str(generator) release_id = release.create() - write_to_release_id_file(f"{release_id}") LOGGER.info(f"Release created with ID: {release_id}") + + print(f"{release_id}") elif args.command == "upload": - upload_to_github_release_page( - args.owner, args.repo, args.release_id, args.file_path, args.dry_run - ) + release = Release.get(args.release_id, repo=args.repo, owner=args.owner) + + if args.dry_run: + print("Dry Run ...") + + print( + f"The file {args.file_path} would be uploaded for release: {release.name}" + ) + else: + release.upload_asset(args.file_path, args.overwrite_same_name) else: parser.print_help() diff --git a/src/gardenlinux/github/release/ali_cloud.platform.json b/src/gardenlinux/github/release/ali_cloud.platform.json new file mode 100644 index 00000000..f5b3059f --- /dev/null +++ b/src/gardenlinux/github/release/ali_cloud.platform.json @@ -0,0 +1,8 @@ +{ + "short_name": "ali", + "full_name": "Alibaba Cloud", + "image_extension": "qcow2", + + "mapping_type": "regions_list", + "mapping_entry_json": "{\"region\": \"{region_id}\", \"image_id\": \"{image_id}\"}" +} diff --git a/src/gardenlinux/github/release/amazon_web_services.platform.json b/src/gardenlinux/github/release/amazon_web_services.platform.json new file mode 100644 index 00000000..6717377d --- /dev/null +++ b/src/gardenlinux/github/release/amazon_web_services.platform.json @@ -0,0 +1,8 @@ +{ + "short_name": "aws", + "full_name": "Amazon Web Services", + "image_extension": "raw", + + "mapping_type": "regions_list", + "mapping_entry_json": "{\"region\": \"{aws_region_id}\", \"image_id\": \"{ami_id}\"}" +} diff --git a/src/gardenlinux/github/release/azure.platform.json b/src/gardenlinux/github/release/azure.platform.json new file mode 100644 index 00000000..dae3ed1c --- /dev/null +++ b/src/gardenlinux/github/release/azure.platform.json @@ -0,0 +1,8 @@ +{ + "short_name": "azure", + "full_name": "Microsoft Azure", + "image_extension": "vhd", + + "mapping_type": "azure_gallery_and_marketplace_list", + "mapping_entry_json": "{\"hyper_v_generation\": \"{hyper_v_generation}\", \"azure_cloud\": \"{azure_cloud}\", \"image_id\": \"{community_gallery_image_id}\", \"urn\": \"{urn}\"}" +} diff --git a/src/gardenlinux/github/release/constants.py b/src/gardenlinux/github/release/constants.py new file mode 100644 index 00000000..5e83297e --- /dev/null +++ b/src/gardenlinux/github/release/constants.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +GL_RELEASE_CVE_PLACEHOLDER = """ +The following packages have been upgraded, to address the mentioned CVEs: +**todo release facilitator: fill this in** +""".strip() + +GL_RELEASE_CHARACTERS_LIMIT = 125000 + +GL_RELEASE_MINOR_TEMPLATE = """# Changes + +$changes + +## Software Component Versions + +``` +$components_versions +``` + +## Changes in Package Versions compared to $previous_release_version + +$compared_package_versions_table + +# Published images + +| Variant | Platform | Architecture | Flavor | Regions | Download Links | +|---------|----------|--------------|--------|---------|----------------| +$published_images_table + +## Kernel Module Build Container (kmodbuild) + +``` +$kmodbuild_registry_url +``` +""" + +HIGHLIGHT_PACKAGES = [ + "linux-image-amd64", + "linux-image-aarch64", + "systemd", + "containerd", + "runc", + "curl", + "openssl", + "openssh-server", + "libc-bin", +] + +IMAGE_VARIANTS = { + "legacy": "Default", + "usi": "USI (Unified System Image)", + "trustedboot": "TPM2 Trusted Boot", +} diff --git a/src/gardenlinux/github/release/deployment_platform.py b/src/gardenlinux/github/release/deployment_platform.py new file mode 100644 index 00000000..b8a13c44 --- /dev/null +++ b/src/gardenlinux/github/release/deployment_platform.py @@ -0,0 +1,196 @@ +import json +from collections.abc import MutableMapping, MutableSequence +from copy import copy +from importlib.resources import files as resource_files +from logging import Logger +from typing import Any, Dict, Optional + + +class _PermissiveDict(dict[str, Any]): + """ + "PermissiveDict" implements a dictionary returning empty strings for + non-existant keys. + + :author: Garden Linux Maintainers + :copyright: Copyright 2024 SAP SE + :package: gardenlinux + :subpackage: github + :since: 1.0.0 + :license: https://www.apache.org/licenses/LICENSE-2.0 + Apache License, Version 2.0 + """ + + def __missing__(self, key: str) -> str: + """ + python.org: Called by dict.__getitem__() to implement self[key] for dict subclasses when key is not in the dictionary. + + :return: (mixed) Value + :since: 1.0.0 + """ + + return "" + + +class DeploymentPlatform(object): + """ + "DeploymentPlatform" represents a Garden Linux release target platform. + + :author: Garden Linux Maintainers + :copyright: Copyright 2024 SAP SE + :package: gardenlinux + :subpackage: github + :since: 1.0.0 + :license: https://www.apache.org/licenses/LICENSE-2.0 + Apache License, Version 2.0 + """ + + _cached_platforms: Optional[Dict[str, Dict[str, Any]]] = None + + def __init__(self, image_metadata: Dict[str, Any], logger: Optional[Logger] = None): + self._image_metadata = image_metadata + + assert self.__class__._cached_platforms is not None + + if image_metadata.get("platform") in self.__class__._cached_platforms: + self._platform_data = self.__class__._cached_platforms[ + image_metadata["platform"] + ] + else: + self._platform_data = self.__class__._cached_platforms["generic"] + + self._logger = logger + + @property + def artifact_base_name(self) -> str: + return self._image_metadata["s3_key"].rsplit("/", 1)[1] # type: ignore[no-any-return] + + @property + def full_name(self) -> str: + return self._platform_data["full_name"] # type: ignore[no-any-return] + + @property + def image_extension(self) -> str: + return self._platform_data["image_extension"] # type: ignore[no-any-return] + + @property + def published_images_by_deployment(self) -> Dict[str, Any]: + image_metadata = self._image_metadata.get("published_image_metadata", {}) + result = {} + + match self._platform_data.get("mapping_type"): + case "azure_gallery_and_marketplace_list": + entry_template = self._platform_data["mapping_entry_json"][1:-1] + gallery_images_list = [] + marketplace_images_list = [] + + for pset in image_metadata: + match pset: + case "published_gallery_images": + for image_data in image_metadata[pset]: + mapping_entry = entry_template.format_map( + _PermissiveDict(image_data) + ) + published_image_data = json.loads( + f"{{{mapping_entry}}}" + ) + + gallery_images_list.append(published_image_data) + case "published_marketplace_images": + for image_data in image_metadata[pset]: + mapping_entry = entry_template.format_map( + _PermissiveDict(image_data) + ) + published_image_data = json.loads( + f"{{{mapping_entry}}}" + ) + + marketplace_images_list.append(published_image_data) + + if len(gallery_images_list) > 0: + result["gallery_images"] = gallery_images_list + + if len(marketplace_images_list) > 0: + result["marketplace_images"] = marketplace_images_list + case "metadata_root": + entry_template = self._platform_data["mapping_entry_json"][1:-1] + + mapping_entry = entry_template.format_map( + _PermissiveDict(image_metadata) + ) + result["details"] = json.loads(f"{{{mapping_entry}}}") + case "regions_list": + regions = [] + + for region_set in image_metadata: + for image_data in image_metadata[region_set]: + entry_template = self._platform_data["mapping_entry_json"][1:-1] + + mapping_entry = entry_template.format_map( + _PermissiveDict(image_data) + ) + regions.append(json.loads(f"{{{mapping_entry}}}")) + + result["regions"] = regions + + return DeploymentPlatform._remove_empty_mapping_values(result) # type: ignore[no-any-return] + + @property + def published_images_mapping_type(self) -> str: + return self._platform_data.get("mapping_type", "undefined") # type: ignore[no-any-return] + + @property + def short_name(self) -> str: + return self._platform_data["short_name"] # type: ignore[no-any-return] + + def generate_s3_image_url_for_bucket(self, s3_bucket_name: str) -> str: + s3_url = f"https://{s3_bucket_name}.s3.amazonaws.com/objects/{self.artifact_base_name}/{self.artifact_base_name}.{self.image_extension}" + return s3_url + + @classmethod + def new_instance(cls, image_metadata: Dict[str, Any]) -> "DeploymentPlatform": + if cls._cached_platforms is None: + cls._cached_platforms = {} + + for resource_file in resource_files(cls.__module__).iterdir(): + if resource_file.is_file() and resource_file.name.endswith( + ".platform.json" + ): + platform_data = json.loads(resource_file.read_text()) + + if platform_data.get("short_name") is None: + continue + + cls._cached_platforms[platform_data["short_name"]] = platform_data + + return DeploymentPlatform(image_metadata) + + @staticmethod + def _remove_empty_mapping_values(mapping_data: Any) -> Any: + if not isinstance(mapping_data, MutableMapping): + return mapping_data + + result_mapping = copy(mapping_data) + + for entry_key in mapping_data: + if ( + isinstance(result_mapping[entry_key], str) + and result_mapping[entry_key] == "" + ): + del result_mapping[entry_key] + elif isinstance(result_mapping[entry_key], MutableMapping): + result_mapping[entry_key] = ( + DeploymentPlatform._remove_empty_mapping_values( + result_mapping[entry_key] + ) + ) + elif isinstance(result_mapping[entry_key], MutableSequence): + result_list = [] + + for entry in result_mapping[entry_key]: + result_list.append( + DeploymentPlatform._remove_empty_mapping_values(entry) + ) + + result_mapping[entry_key] = result_list + + return result_mapping diff --git a/src/gardenlinux/github/release/generic.platform.json b/src/gardenlinux/github/release/generic.platform.json new file mode 100644 index 00000000..a12a1c5d --- /dev/null +++ b/src/gardenlinux/github/release/generic.platform.json @@ -0,0 +1,5 @@ +{ + "short_name": "generic", + "full_name": "Generic Deployment", + "image_extension": "raw" +} diff --git a/src/gardenlinux/github/release/google_cloud.platform.json b/src/gardenlinux/github/release/google_cloud.platform.json new file mode 100644 index 00000000..0c30af5b --- /dev/null +++ b/src/gardenlinux/github/release/google_cloud.platform.json @@ -0,0 +1,8 @@ +{ + "short_name": "gcp", + "full_name": "Google Cloud Platform", + "image_extension": "gcpimage.tar.gz", + + "mapping_type": "metadata_root", + "mapping_entry_json": "{\"project\": \"{gcp_project_name}\", \"image_name\": \"{gcp_image_name}\", \"availability\": \"All regions\"}" +} diff --git a/src/gardenlinux/github/release/notes/__init__.py b/src/gardenlinux/github/release/notes/__init__.py new file mode 100644 index 00000000..2b3eb8c4 --- /dev/null +++ b/src/gardenlinux/github/release/notes/__init__.py @@ -0,0 +1,3 @@ +from .markdown_generator import MarkdownGenerator + +__all__ = ["MarkdownGenerator"] diff --git a/src/gardenlinux/github/release/notes/markdown_generator.py b/src/gardenlinux/github/release/notes/markdown_generator.py new file mode 100644 index 00000000..ba6c404a --- /dev/null +++ b/src/gardenlinux/github/release/notes/markdown_generator.py @@ -0,0 +1,295 @@ +# -*- coding: utf-8 -*- + +import json +import re +from logging import Logger +from string import Template +from typing import Optional + +from ....apt import DebsrcFile, GardenLinuxRepo +from ....apt.package_repo_info import compare_repo +from ....constants import GL_CONTAINER_REGISTRY_BASE_URL +from ....distro_version import DistroVersion +from ....logger import LoggerSetup +from ..constants import ( + GL_RELEASE_CHARACTERS_LIMIT, + GL_RELEASE_CVE_PLACEHOLDER, + GL_RELEASE_MINOR_TEMPLATE, + HIGHLIGHT_PACKAGES, + IMAGE_VARIANTS, +) +from ..deployment_platform import DeploymentPlatform +from ..release import Release +from ..release_images_metadata import ReleaseImagesMetadata + + +class MarkdownGenerator(object): + """ + GitHub release notes generator + + :author: Garden Linux Maintainers + :copyright: Copyright 2024 SAP SE + :package: gardenlinux + :subpackage: github + :since: 1.0.0 + :license: https://www.apache.org/licenses/LICENSE-2.0 + Apache License, Version 2.0 + """ + + def __init__( + self, + release: Release, + releases_s3_bucket_name: str, + logger: Optional[Logger] = None, + ): + """ + Constructor __init__(MarkdownGenerator) + + :param release: GitHub release instance + :param releases_s3_bucket_name: S3 release bucket + :param logger: Logger instance + + :since: 1.0.0 + """ + + assert release.commitish is not None + + self._commitish = release.commitish + self._s3_bucket_name = releases_s3_bucket_name + self._version = release.tag + + if logger is None or not logger.hasHandlers(): + logger = LoggerSetup.get_logger("gardenlinux.github") + + self._logger = logger + + self._release_images_metadata = ReleaseImagesMetadata( + self._version, self._commitish, self._s3_bucket_name, self._logger + ) + + @property + def changes_and_cves_list(self) -> str: + """ + Get list of fixed CVEs, grouped by upgraded package. + + Note: This result is not perfect, feel free to edit the generated release notes and + file issues in glvd for improvement suggestions https://github.com/gardenlinux/glvd/issues + """ + + package_changes = self._release_images_metadata.changes_and_cves_list + + if len(package_changes) < 1: + return GL_RELEASE_CVE_PLACEHOLDER + + out_list = [ + "The following packages have been upgraded, to address the mentioned CVEs:" + ] + + for package, package_data in package_changes.items(): + upgrade_line = ( + f"- upgrade '{package}'" + f" from `{package_data['version']['old']}` to `{package_data['version']['new']}`" + ) + + out_list.append(upgrade_line) + + if len(package_data["fixed_cve_list"]) > 0: + for cve in package_data["fixed_cve_list"]: + out_list.append(f" - {cve}") + + return "\n".join(out_list) + + @property + def compared_package_versions_table(self) -> str: + version = DistroVersion(self._version) + + previous_repo = GardenLinuxRepo(version.previous_patch_release) + current_repo = GardenLinuxRepo(self._version) + pkg_diffs = sorted( + compare_repo(previous_repo, current_repo), key=lambda t: t[0] + ) + + out_list = [f"| Package | {version.previous_patch_release} | {self._version} |"] + out_list.append("|---------|--------------------|-------------------|") + + for pkg in pkg_diffs: + out_list.append( + f"|{pkg[0]} | `{pkg[1] if pkg[1] is not None else '-'}` | `{pkg[2] if pkg[2] is not None else '-'}` |" + ) + + return "\n".join(out_list) + + @property + def kmodbuild_registry_url(self) -> str: + return f"{GL_CONTAINER_REGISTRY_BASE_URL}/kmodbuild:{self._version}" + + @property + def package_list(self) -> DebsrcFile: + return self._release_images_metadata.package_list + + @property + def release_images_table(self) -> str: + """ + Generate the table format with collapsible region details + """ + + grouped_data = self._release_images_metadata.grouped_flavors_metadata + out_list = [] + + for variant in grouped_data.keys(): + for platform in sorted(grouped_data[variant].keys()): + for arch in sorted(grouped_data[variant][platform].keys()): + for metadata in grouped_data[variant][platform][arch]: + deployment_platform = DeploymentPlatform.new_instance(metadata) + data = deployment_platform.published_images_by_deployment + + if data is None: + continue + + details_content = self._generate_release_images_region_details( + deployment_platform + ) + summary_text = self._generate_release_images_region_summary( + deployment_platform + ) + + download_url = ( + deployment_platform.generate_s3_image_url_for_bucket( + self._s3_bucket_name + ) + ) + download_link = f"[{deployment_platform.artifact_base_name}.{deployment_platform.image_extension}]({download_url})" + + out_list.append( + f"| {IMAGE_VARIANTS[variant]} " + f"| {deployment_platform.full_name} " + f"| {arch} " + f"| `{deployment_platform.artifact_base_name}` " + f"|
{summary_text}
\n{details_content}
" + f"|
Download
\n{download_link}
" + "|" + ) + + return "\n".join(out_list) + + @property + def software_components(self) -> str: + packages_re = "" + + for package in HIGHLIGHT_PACKAGES: + if len(packages_re) > 0: + packages_re += "|" + + packages_re += f"^{re.escape(package)}$" + + packages_re_object = re.compile(packages_re) + out_list = [] + + for entry in self.package_list.values(): + if packages_re_object.match(entry.deb_source): + out_list.append(repr(entry)) + + return "\n".join(out_list) + + def __str__(self) -> str: + """ + Returns final markdown for the configured reproducibility check + + :return: (str) Markdown + :since: 1.0.0 + """ + + version = DistroVersion(self._version) + + if version.is_patch_release: + template = Template(GL_RELEASE_MINOR_TEMPLATE) + + out = template.safe_substitute( + changes=self.changes_and_cves_list, + compared_package_versions_table=self.compared_package_versions_table, + components_versions=self.software_components, + kmodbuild_registry_url=self.kmodbuild_registry_url, + previous_release_version=version.previous_patch_release, + published_images_table=self.release_images_table, + ) + else: + pass + + if len(out) > GL_RELEASE_CHARACTERS_LIMIT: + self._logger.error( + f"Generated release notes following below exceeded the maximum allowed characters of {GL_RELEASE_CHARACTERS_LIMIT}. Truncating:\n{out}" + ) + + out = out[: GL_RELEASE_CHARACTERS_LIMIT - 12] + " [truncated]" + + return out + + def _generate_release_images_region_details( + self, deployment_platform: DeploymentPlatform + ) -> str: + """ + Generate the detailed region information for the collapsible section + """ + + out_list = [] + + published_images = deployment_platform.published_images_by_deployment + + match deployment_platform.published_images_mapping_type: + case "regions_list": + for region_data in published_images["regions"]: + region_line = ( + f"**{region_data['region']}:** {region_data['image_id']}" + ) + if "image_name" in region_data: + region_line += f" ({region_data['image_name']})" + + out_list.append(region_line) + case "metadata_root": + for key, value in published_images["details"].items(): + out_list.append(f"**{key.replace('_', ' ').title()}:** {value}") + case "azure_gallery_and_marketplace_list": + if "gallery_images" in published_images: + out_list.append("**Gallery Images:**") + + for img in published_images["gallery_images"]: + out_list.append( + f"**{img['azure_cloud']} ({img['hyper_v_generation']}):** {img['image_id']}" + ) + + if "marketplace_images" in published_images: + out_list.append("**Marketplace Images:**") + + for img in published_images["marketplace_images"]: + out_list.append( + f"**{img['hyper_v_generation']}:** {img['urn']}" + ) + case _: + out_list.append("" + json.dumps(published_images) + "") + + return "
\n".join(out_list) + + def _generate_release_images_region_summary( + self, deployment_platform: DeploymentPlatform + ) -> str: + """ + Generate the summary text for the collapsible section + """ + + published_images = deployment_platform.published_images_by_deployment + + match deployment_platform.published_images_mapping_type: + case "regions_list": + count = len(published_images["regions"]) + return f"{count} regions" + case "metadata_root": + return "Global availability" + case "azure_gallery_and_marketplace_list": + gallery_count = len(published_images.get("gallery_images", [])) + marketplace_count = len(published_images.get("marketplace_images", [])) + + return ( + f"{gallery_count} gallery + {marketplace_count} marketplace images" + ) + case _: + return "Details available" diff --git a/src/gardenlinux/github/release/openstack.platform.json b/src/gardenlinux/github/release/openstack.platform.json new file mode 100644 index 00000000..e542d174 --- /dev/null +++ b/src/gardenlinux/github/release/openstack.platform.json @@ -0,0 +1,8 @@ +{ + "short_name": "openstack", + "full_name": "OpenStack", + "image_extension": "raw", + + "mapping_type": "regions_list", + "mapping_entry_json": "{\"region\": \"{region_name}\", \"image_id\": \"{image_id}\", \"image_name\": \"{image_name}\"}" +} diff --git a/src/gardenlinux/github/release/openstackbaremetal.platform.json b/src/gardenlinux/github/release/openstackbaremetal.platform.json new file mode 100644 index 00000000..e8369461 --- /dev/null +++ b/src/gardenlinux/github/release/openstackbaremetal.platform.json @@ -0,0 +1,8 @@ +{ + "short_name": "openstackbaremetal", + "full_name": "OpenStack Baremetal", + "image_extension": "raw", + + "mapping_type": "regions_list", + "mapping_entry_json": "{\"region\": \"{region_name}\", \"image_id\": \"{image_id}\", \"image_name\": \"{image_name}\"}" +} diff --git a/src/gardenlinux/github/release/release.py b/src/gardenlinux/github/release/release.py index d4c5099b..ff663adf 100644 --- a/src/gardenlinux/github/release/release.py +++ b/src/gardenlinux/github/release/release.py @@ -5,7 +5,13 @@ """ from logging import Logger -from typing import Optional +from os import PathLike +from pathlib import Path +from typing import Optional, Self + +from github import GithubException +from github.GitRelease import GitRelease +from github.GitReleaseAsset import GitReleaseAsset from ...logger import LoggerSetup from ..client import Client @@ -19,7 +25,7 @@ class Release(object): :copyright: Copyright 2024 SAP SE :package: gardenlinux :subpackage: github - :since: 1.0.0 + :since: 0.10.19 :license: https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 """ @@ -39,11 +45,12 @@ def __init__( :param token: GitHub access token :param logger: Logger instance - :since: 1.0.0 + :since: 0.10.19 """ self._owner = owner self._repo = repo + self._release_id: Optional[int] = None self._name: Optional[str] = None self._tag: Optional[str] = None self._commitish: Optional[str] = None @@ -61,10 +68,10 @@ def __init__( @property def body(self) -> str: """ - Returns the Git release body set. + Returns the GitHub release body set. - :return: (str) Git release body - :since: 1.0.0 + :return: (str) GitHub release body + :since: 0.10.19 """ if self._release_body is None: @@ -75,11 +82,11 @@ def body(self) -> str: @body.setter def body(self, value: str) -> None: """ - Sets the Git release body. + Sets the GitHub release body. - :param value: Git release body + :param value: GitHub release body - :since: 1.0.0 + :since: 0.10.19 """ self._release_body = value @@ -90,7 +97,7 @@ def commitish(self) -> Optional[str]: Returns the Git release related commit hash. :return: (str) Git release commit hash - :since: 1.0.0 + :since: 0.10.19 """ return self._commitish @@ -102,18 +109,32 @@ def commitish(self, value: str) -> None: :param value: Git release commit hash - :since: 1.0.0 + :since: 0.10.19 """ self._commitish = value + @property + def id(self) -> int: + """ + Returns the GitHub release ID set. + + :return: (int) GitHub release ID + :since: 0.10.19 + """ + + if self._release_id is None: + raise ValueError("GitHub release ID not set") + + return self._release_id + @property def is_latest(self) -> bool: """ Returns true if the Git release is marked as "latest". :return: (str) Git release latest status - :since: 1.0.0 + :since: 0.10.19 """ return self._latest @@ -125,7 +146,7 @@ def is_latest(self, value: bool) -> None: :param value: Git release latest status - :since: 1.0.0 + :since: 0.10.19 """ self._latest = bool(value) @@ -136,7 +157,7 @@ def is_pre_release(self) -> bool: Returns true if the Git release is marked as pre-release. :return: (str) Git release pre-release status - :since: 1.0.0 + :since: 0.10.19 """ return self._pre_release @@ -148,7 +169,7 @@ def is_pre_release(self, value: bool) -> None: :param value: Git release pre-release status - :since: 1.0.0 + :since: 0.10.19 """ self._pre_release = bool(value) @@ -159,7 +180,7 @@ def name(self) -> str: Returns the Git release name set. :return: (str) Git release name - :since: 1.0.0 + :since: 0.10.19 """ if self._name is None: @@ -174,7 +195,7 @@ def name(self, value: str) -> None: :param value: Git release name - :since: 1.0.0 + :since: 0.10.19 """ self._name = value @@ -185,7 +206,7 @@ def tag(self) -> str: Returns the Git release tag set. :return: (str) Git release tag - :since: 1.0.0 + :since: 0.10.19 """ if self._tag is None: @@ -200,17 +221,39 @@ def tag(self, value: str) -> None: :param value: Git release tag - :since: 1.0.0 + :since: 0.10.19 """ self._tag = value + def _copy_from_release_object(self, release_object: GitRelease | Self) -> None: + """ + Copy values from an given GitHub release. + + :return: (str) GitHub release ID created + :since: 0.10.19 + """ + + self._name = release_object.name + + if isinstance(release_object, GitRelease): + self._release_id = release_object.id + self._tag = release_object.tag_name + self._commitish = release_object.target_commitish + self._pre_release = release_object.prerelease + self._release_body = release_object.body_text + else: + self._tag = release_object.tag + self._commitish = release_object.commitish + self._pre_release = release_object.is_pre_release + self._release_body = release_object.body + def create(self) -> int: """ Creates an GitHub release. :return: (str) GitHub release ID created - :since: 1.0.0 + :since: 0.10.19 """ kwargs = { @@ -228,4 +271,92 @@ def create(self) -> int: f"{self._owner}/{self._repo}" ).create_git_release(self.tag, **kwargs) - return release.id # type: ignore[no-any-return] + self._release_id = release.id + + return self._release_id + + def get_asset_by_name(self, asset_name: str) -> GitReleaseAsset: + """ + Returns an GitHub release asset by the given name. + + :param asset_name: Asset name + + :return: (object) GitHub release asset + :since: 0.10.19 + """ + + github_release = self._client.get_repo( + f"{self._owner}/{self._repo}" + ).get_release(self.id) + + for asset in github_release.assets: + if asset_name == asset.name: + return asset + + raise RuntimeError(f"No asset found with name: {asset_name}") + + def upload_asset( + self, asset_file_path_name: PathLike[str] | str, overwrite: bool = False + ) -> None: + """ + Uploads an GitHub release asset. + + :param asset_file_path_name: File path and name to be uploaded + + :since: 0.10.19 + """ + + if not isinstance(asset_file_path_name, PathLike): + asset_file_path_name = Path(asset_file_path_name) + + if asset_file_path_name.stat().st_size < 1: # type: ignore[attr-defined] + self._logger.info(f"{asset_file_path_name} is empty and will be ignored") + return + + github_release = self._client.get_repo( + f"{self._owner}/{self._repo}" + ).get_release(self.id) + + asset_file_name = asset_file_path_name.name # type: ignore[attr-defined] + + try: + github_release.upload_asset(str(asset_file_path_name), name=asset_file_name) + except GithubException as exc: + is_asset_upload_retried = False + + if overwrite and exc.status == 422: + asset = self.get_asset_by_name(asset_file_name) + + asset.delete_asset() + self.upload_asset(asset_file_path_name) + + is_asset_upload_retried = True + + if not is_asset_upload_retried: + raise + + self._logger.info(f"Uploaded file '{asset_file_name}'") + + @staticmethod + def get( + release_id: int, + repo: str, + owner: str = "gardenlinux", + token: Optional[str] = None, + logger: Optional[Logger] = None, + ) -> "Release": + """ + Creates an GitHub release. + + :return: (str) GitHub release ID created + :since: 0.10.19 + """ + + github_release = ( + Client(token, logger).get_repo(f"{owner}/{repo}").get_release(release_id) + ) + + release = Release(repo, owner, token, logger) + release._copy_from_release_object(github_release) + + return release diff --git a/src/gardenlinux/github/release/release_images_metadata.py b/src/gardenlinux/github/release/release_images_metadata.py new file mode 100644 index 00000000..e1446374 --- /dev/null +++ b/src/gardenlinux/github/release/release_images_metadata.py @@ -0,0 +1,275 @@ +# -*- coding: utf-8 -*- + +""" +GitHub release notes generator +""" + +import gzip +from collections import OrderedDict +from collections.abc import Mapping, MutableSequence +from io import BytesIO +from logging import Logger +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Any, Dict, Optional + +import requests +import yaml + +from ...apt import DebsrcFile +from ...constants import GL_DEB_REPO_BASE_URL, GLVD_BASE_URL, REQUESTS_TIMEOUTS +from ...features import CName +from ...flavors import Parser +from ...git import Repository +from ...logger import LoggerSetup +from ...s3 import S3Artifacts + + +class ReleaseImagesMetadata(object): + """ + GitHub release instance to provide methods for interaction. + + :author: Garden Linux Maintainers + :copyright: Copyright 2024 SAP SE + :package: gardenlinux + :subpackage: github + :since: 1.0.0 + :license: https://www.apache.org/licenses/LICENSE-2.0 + Apache License, Version 2.0 + """ + + def __init__( + self, + version: str, + commitish: str, + s3_bucket_name: str, + logger: Optional[Logger] = None, + ): + """ + Constructor __init__(Generator) + + :param repo: GitHub repository containing releases + :param owner: GitHub owner for release data + :param token: GitHub access token + :param logger: Logger instance + + :since: 1.0.0 + """ + + self._commitish = commitish + self._flavors_parser: Optional[Parser] = None + self._glvd_data: Optional[OrderedDict[str, Any]] = None + self._s3_bucket_name = s3_bucket_name + self._version = version + + if logger is None or not logger.hasHandlers(): + logger = LoggerSetup.get_logger("gardenlinux.github") + + self._logger = logger + + @property + def changes_and_cves_list(self) -> Dict[str, Any]: + """ + Get list of fixed CVEs, grouped by upgraded package. + + Note: This result is not perfect, feel free to edit the generated release notes and + file issues in glvd for improvement suggestions https://github.com/gardenlinux/glvd/issues + """ + + if self._glvd_data is None: + try: + response = self._raw_request( + "GET", f"{GLVD_BASE_URL}/releaseNotes/{self._version}" + ) + data = response.json() + except Exception as exn: + self._logger.error(f"Failed to process GLVD API output: {exn}") + data = {} + + if not isinstance(data, Mapping) or len(data.get("packageList", [])) < 1: + return {} + + self._glvd_data = OrderedDict() + + for package in data["packageList"]: + package_data = { + "version": { + "old": package["oldVersion"], + "new": package["newVersion"], + }, + "fixed_cve_list": [], + } + + if isinstance(package.get("fixedCves"), MutableSequence): + package_data["fixed_cve_list"] = package["fixedCves"] + + self._glvd_data[package["sourcePackageName"]] = package_data + + return self._glvd_data + + @property + def flavors_parser(self) -> Parser: + if self._flavors_parser is None: + flavors_parser = None + + with TemporaryDirectory() as tmpdir: + repo = Repository.checkout_repo_sparse( + tmpdir, ["flavors.yaml"], commit=self._commitish + ) + flavors_file = Path(repo.root, "flavors.yaml") + + if not flavors_file.exists(): + raise RuntimeError(f"Error: {flavors_file} does not exist.") + + # Load and validate the flavors.yaml + with flavors_file.open("r") as fp: + flavors_parser = Parser(fp.read()) + + if flavors_parser is None: + raise RuntimeError( + f"Failed to find flavors for commitish: {self._commitish}" + ) + + self._flavors_parser = flavors_parser + + return self._flavors_parser + + @property + def grouped_flavors_metadata( + self, + ) -> dict[str, dict[str, dict[str, list[dict[str, Any]]]]]: + flavors = self.flavors_parser.filter(only_publish=True) + + # Group metadata by variant, platform, and architecture + grouped_data: dict[str, dict[str, dict[str, list[dict[str, Any]]]]] = ( + OrderedDict() + ) + + s3_artifacts = S3Artifacts(self._s3_bucket_name) + + with TemporaryDirectory() as tmpdir: + for flavor in flavors: + self._logger.debug( + f"{flavor=} version={self._version} commitish={self._commitish}" + ) + + cname = CName( + flavor[1], + arch=flavor[0], + commit_hash=self._commitish, + version=self._version, + ) + + try: + release_object = list( + s3_artifacts.bucket.objects.filter( + Prefix=f"meta/singles/{cname.cname}" + ) + )[0] + + s3_artifacts.bucket.download_file( + release_object.key, + str(Path(tmpdir, f"{cname.cname}.s3_metadata.yaml")), + ) + except IndexError: + self._logger.warning( + f"No artifacts found for flavor {cname.cname}, skipping..." + ) + continue + + with Path(tmpdir, f"{cname.cname}.s3_metadata.yaml").open("r") as file: + s3_data = ReleaseImagesMetadata.parse_s3_metadata( + yaml.load(file, Loader=yaml.SafeLoader) + ) + + # Skip if no publishing metadata found + if len(s3_data.get("published_image_metadata", [])) < 1: + continue + + if s3_data["variant_type"] not in grouped_data: + grouped_data[s3_data["variant_type"]] = {} + if s3_data["platform"] not in grouped_data[s3_data["variant_type"]]: + grouped_data[s3_data["variant_type"]][s3_data["platform"]] = {} + if ( + s3_data["architecture"] + not in grouped_data[s3_data["variant_type"]][s3_data["platform"]] + ): + grouped_data[s3_data["variant_type"]][s3_data["platform"]][ + s3_data["architecture"] + ] = [] + + grouped_data[s3_data["variant_type"]][s3_data["platform"]][ + s3_data["architecture"] + ].append(s3_data) + + return grouped_data + + @property + def package_list(self) -> DebsrcFile: + try: + response = self._raw_request( + "GET", + f"{GL_DEB_REPO_BASE_URL}/dists/{self._version}/main/binary-amd64/Packages.gz", + ) + except Exception as exn: + self._logger.error(f"Failed to process Debian repository request: {exn}") + return DebsrcFile() + + debsrc = DebsrcFile() + + with BytesIO(response.content) as buffer: + with gzip.open(buffer, "rt") as file: + debsrc.read(file) + + return debsrc + + def _raw_request( + self, + method: str, + url: str, + **kwargs: Any, + ) -> requests.Response: + """ + Returns the requests response for the request given. + + :param method: Python requests method to call + :param url: Python requests URL + + :return: (Response) Python requests response + :since: 1.0.0 + """ + + method_callable = getattr(requests, method.lower()) + + response = method_callable(url, timeout=REQUESTS_TIMEOUTS, **kwargs) + response.raise_for_status() + + return response # type: ignore[no-any-return] + + @staticmethod + def get_variant_from_metadata(metadata: Dict[str, Any]) -> str: + feature_set = metadata.get("modifiers", []) + + if "_tpm2" in feature_set and "_trustedboot" in feature_set: + return "trustedboot" + elif "_usi" in feature_set: + return "usi" + else: + return "legacy" + + @staticmethod + def parse_s3_metadata(metadata: Dict[str, Any]) -> Dict[str, Any]: + published_metadata = metadata.get("published_image_metadata", {}) + + if not isinstance(published_metadata, Mapping): + published_metadata = {} + + extended_metadata = metadata.copy() + + extended_metadata["variant_type"] = ( + ReleaseImagesMetadata.get_variant_from_metadata(metadata) + ) + + extended_metadata["published_image_metadata"] = published_metadata + + return extended_metadata diff --git a/src/gardenlinux/github/release_notes/__init__.py b/src/gardenlinux/github/release_notes/__init__.py deleted file mode 100644 index 6e0f7603..00000000 --- a/src/gardenlinux/github/release_notes/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -from .helpers import download_all_metadata_files, get_package_list -from .sections import ( - release_notes_changes_section, - release_notes_compare_package_versions_section, - release_notes_image_ids_section, - release_notes_software_components_section, -) - - -def create_github_release_notes( - gardenlinux_version: str, commitish: str, releases_s3_bucket_name: str -) -> str: - package_list = get_package_list(gardenlinux_version) - - output = "" - - output += release_notes_changes_section(gardenlinux_version) - - output += release_notes_software_components_section(package_list) - - output += release_notes_compare_package_versions_section( - gardenlinux_version, package_list - ) - - metadata_files = download_all_metadata_files( - gardenlinux_version, commitish, releases_s3_bucket_name - ) - - output += release_notes_image_ids_section(metadata_files) - - output += "\n" - output += "## Kernel Module Build Container (kmodbuild)" - output += "\n" - output += "```" - output += "\n" - output += f"ghcr.io/gardenlinux/gardenlinux/kmodbuild:{gardenlinux_version}" - output += "\n" - output += "```" - output += "\n" - return output diff --git a/src/gardenlinux/github/release_notes/deployment_platform/__init__.py b/src/gardenlinux/github/release_notes/deployment_platform/__init__.py deleted file mode 100644 index 4b5c9f33..00000000 --- a/src/gardenlinux/github/release_notes/deployment_platform/__init__.py +++ /dev/null @@ -1,114 +0,0 @@ -from typing import Any, Dict - -from gardenlinux.constants import GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME - - -class DeploymentPlatform: - artifacts_bucket_name = GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME - - def short_name(self) -> str: - return "generic" - - def full_name(self) -> str: - return "Generic Deployment Platform" - - def published_images_by_regions( - self, image_metadata: Dict[str, Any] - ) -> Dict[str, Any]: - published_image_metadata = image_metadata.get("published_image_metadata", {}) - flavor_name = image_metadata["s3_key"].split("/")[-1] - - regions = [] - for pset in published_image_metadata: - for p in published_image_metadata[pset]: - regions.append({"region": p["region_id"], "image_id": p["image_id"]}) - - return {"flavor": flavor_name, "regions": regions} - - def image_extension(self) -> str: - return "raw" - - def artifact_for_flavor(self, flavor: str, markdown_format: bool = True) -> str: - base_url = ( - f"https://{self.__class__.artifacts_bucket_name}.s3.amazonaws.com/objects" - ) - filename = f"{flavor}.{self.image_extension()}" - download_url = f"{base_url}/{flavor}/{filename}" - if markdown_format: - return f"[{filename}]({download_url})" - else: - return download_url - - def region_details(self, image_metadata: Dict[str, Any]) -> str: - """ - Generate the detailed region information for the collapsible section - """ - details: str = "" - - match self.published_images_by_regions(image_metadata): - case {"regions": regions}: - for region in regions: - match region: - case { - "region": region_name, - "image_id": image_id, - "image_name": image_name, - }: - details += ( - f"**{region_name}:** {image_id} ({image_name})
" - ) - case {"region": region_name, "image_id": image_id}: - details += f"**{region_name}:** {image_id}
" - case {"details": details_dict}: - for key, value in details_dict.items(): - details += f"**{key.replace('_', ' ').title()}:** {value}
" - case { - "gallery_images": gallery_images, - "marketplace_images": marketplace_images, - }: - if gallery_images: - details += "**Gallery Images:**
" - for img in gallery_images: - details += f"• {img['hyper_v_generation']} ({img['azure_cloud']}): {img['image_id']}
" - if marketplace_images: - details += "**Marketplace Images:**
" - for img in marketplace_images: - details += f"• {img['hyper_v_generation']}: {img['urn']}
" - case {"gallery_images": gallery_images}: - details += "**Gallery Images:**
" - for img in gallery_images: - details += f"• {img['hyper_v_generation']} ({img['azure_cloud']}): {img['image_id']}
" - case {"marketplace_images": marketplace_images}: - details += "**Marketplace Images:**
" - for img in marketplace_images: - details += f"• {img['hyper_v_generation']}: {img['urn']}
" - - return details - - def summary_text(self, image_metadata: Dict[str, Any]) -> str: - """ - Generate the summary text for the collapsible section - """ - match self.published_images_by_regions(image_metadata): - case {"regions": regions}: - count = len(regions) - return f"{count} regions" - case {"details": _}: - return "Global availability" - case { - "gallery_images": gallery_images, - "marketplace_images": marketplace_images, - }: - gallery_count = len(gallery_images) - marketplace_count = len(marketplace_images) - return ( - f"{gallery_count} gallery + {marketplace_count} marketplace images" - ) - case {"gallery_images": gallery_images}: - gallery_count = len(gallery_images) - return f"{gallery_count} gallery images" - case {"marketplace_images": marketplace_images}: - marketplace_count = len(marketplace_images) - return f"{marketplace_count} marketplace images" - case _: - return "Details available" diff --git a/src/gardenlinux/github/release_notes/deployment_platform/ali_cloud.py b/src/gardenlinux/github/release_notes/deployment_platform/ali_cloud.py deleted file mode 100644 index 451a4e35..00000000 --- a/src/gardenlinux/github/release_notes/deployment_platform/ali_cloud.py +++ /dev/null @@ -1,13 +0,0 @@ -# pyright: reportIncompatibleMethodOverride=false -from . import DeploymentPlatform - - -class AliCloud(DeploymentPlatform): - def short_name(self) -> str: - return "ali" - - def full_name(self) -> str: - return "Alibaba Cloud" - - def image_extension(self) -> str: - return "qcow2" diff --git a/src/gardenlinux/github/release_notes/deployment_platform/amazon_web_services.py b/src/gardenlinux/github/release_notes/deployment_platform/amazon_web_services.py deleted file mode 100644 index 5e7e9476..00000000 --- a/src/gardenlinux/github/release_notes/deployment_platform/amazon_web_services.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Any, Dict - -from . import DeploymentPlatform - - -class AmazonWebServices(DeploymentPlatform): - def short_name(self) -> str: - return "aws" - - def full_name(self) -> str: - return "Amazon Web Services" - - def published_images_by_regions( - self, image_metadata: Dict[str, Any] - ) -> Dict[str, Any]: - published_image_metadata = image_metadata.get("published_image_metadata", {}) - flavor_name = image_metadata["s3_key"].split("/")[-1] - - regions = [] - for pset in published_image_metadata: - for p in published_image_metadata[pset]: - regions.append({"region": p["aws_region_id"], "image_id": p["ami_id"]}) - - return {"flavor": flavor_name, "regions": regions} diff --git a/src/gardenlinux/github/release_notes/deployment_platform/azure.py b/src/gardenlinux/github/release_notes/deployment_platform/azure.py deleted file mode 100644 index a0a00a4b..00000000 --- a/src/gardenlinux/github/release_notes/deployment_platform/azure.py +++ /dev/null @@ -1,49 +0,0 @@ -from typing import Any, Dict - -from . import DeploymentPlatform - - -class Azure(DeploymentPlatform): - def short_name(self) -> str: - return "azure" - - def full_name(self) -> str: - return "Microsoft Azure" - - def image_extension(self) -> str: - return "vhd" - - def published_images_by_regions( - self, image_metadata: Dict[str, Any] - ) -> Dict[str, Any]: - published_image_metadata = image_metadata.get("published_image_metadata", {}) - flavor_name = image_metadata["s3_key"].split("/")[-1] - - gallery_images = [] - marketplace_images = [] - - for pset in published_image_metadata: - if pset == "published_gallery_images": - for gallery_image in published_image_metadata[pset]: - gallery_images.append( - { - "hyper_v_generation": gallery_image["hyper_v_generation"], - "azure_cloud": gallery_image["azure_cloud"], - "image_id": gallery_image["community_gallery_image_id"], - } - ) - - if pset == "published_marketplace_images": - for market_image in published_image_metadata[pset]: - marketplace_images.append( - { - "hyper_v_generation": market_image["hyper_v_generation"], - "urn": market_image["urn"], - } - ) - - return { - "flavor": flavor_name, - "gallery_images": gallery_images, - "marketplace_images": marketplace_images, - } diff --git a/src/gardenlinux/github/release_notes/deployment_platform/google_cloud.py b/src/gardenlinux/github/release_notes/deployment_platform/google_cloud.py deleted file mode 100644 index ff9b6383..00000000 --- a/src/gardenlinux/github/release_notes/deployment_platform/google_cloud.py +++ /dev/null @@ -1,30 +0,0 @@ -# pyright: reportIncompatibleMethodOverride=false -from typing import Any, Dict - -from . import DeploymentPlatform - - -class GoogleCloud(DeploymentPlatform): - def short_name(self) -> str: - return "gcp" - - def full_name(self) -> str: - return "Google Cloud Platform" - - def image_extension(self) -> str: - return "gcpimage.tar.gz" - - def published_images_by_regions( - self, image_metadata: Dict[str, Any] - ) -> Dict[str, Any]: - published_image_metadata = image_metadata.get("published_image_metadata", {}) - flavor_name = image_metadata["s3_key"].split("/")[-1] - - details = {} - if "gcp_image_name" in published_image_metadata: - details["image_name"] = published_image_metadata["gcp_image_name"] - if "gcp_project_name" in published_image_metadata: - details["project"] = published_image_metadata["gcp_project_name"] - details["availability"] = "Global (all regions)" - - return {"flavor": flavor_name, "details": details} diff --git a/src/gardenlinux/github/release_notes/deployment_platform/openstack.py b/src/gardenlinux/github/release_notes/deployment_platform/openstack.py deleted file mode 100644 index 61eafe76..00000000 --- a/src/gardenlinux/github/release_notes/deployment_platform/openstack.py +++ /dev/null @@ -1,31 +0,0 @@ -# pyright: reportIncompatibleMethodOverride=false -from typing import Any, Dict - -from . import DeploymentPlatform - - -class OpenStack(DeploymentPlatform): - def short_name(self) -> str: - return "openstack" - - def full_name(self) -> str: - return "OpenStack" - - def published_images_by_regions( - self, image_metadata: Dict[str, Any] - ) -> Dict[str, Any]: - published_image_metadata = image_metadata.get("published_image_metadata", {}) - flavor_name = image_metadata["s3_key"].split("/")[-1] - - regions = [] - if "published_openstack_images" in published_image_metadata: - for image in published_image_metadata["published_openstack_images"]: - regions.append( - { - "region": image["region_name"], - "image_id": image["image_id"], - "image_name": image["image_name"], - } - ) - - return {"flavor": flavor_name, "regions": regions} diff --git a/src/gardenlinux/github/release_notes/deployment_platform/openstack_baremetal.py b/src/gardenlinux/github/release_notes/deployment_platform/openstack_baremetal.py deleted file mode 100644 index a253182a..00000000 --- a/src/gardenlinux/github/release_notes/deployment_platform/openstack_baremetal.py +++ /dev/null @@ -1,10 +0,0 @@ -# pyright: reportIncompatibleMethodOverride=false -from .openstack import OpenStack - - -class OpenStackBareMetal(OpenStack): - def short_name(self) -> str: - return "openstackbaremetal" - - def full_name(self) -> str: - return "OpenStack Baremetal" diff --git a/src/gardenlinux/github/release_notes/helpers.py b/src/gardenlinux/github/release_notes/helpers.py deleted file mode 100644 index a6dddd4d..00000000 --- a/src/gardenlinux/github/release_notes/helpers.py +++ /dev/null @@ -1,141 +0,0 @@ -import gzip -import io -import logging -import shutil -from pathlib import Path -from typing import Any - -import requests -from git import Repo - -from gardenlinux.apt import DebsrcFile, GardenLinuxRepo -from gardenlinux.apt.package_repo_info import compare_repo -from gardenlinux.constants import ( - GL_DEB_REPO_BASE_URL, - IMAGE_VARIANTS, - REQUESTS_TIMEOUTS, -) -from gardenlinux.features import CName -from gardenlinux.flavors import Parser as FlavorsParser -from gardenlinux.logger import LoggerSetup -from gardenlinux.s3 import S3Artifacts - -LOGGER = LoggerSetup.get_logger( - "gardenlinux.github.release_notes.helpers", logging.INFO -) - - -def get_package_list(gardenlinux_version: str) -> DebsrcFile: - url = f"{GL_DEB_REPO_BASE_URL}/dists/{gardenlinux_version}/main/binary-amd64/Packages.gz" - response = requests.get(url, timeout=REQUESTS_TIMEOUTS) - response.raise_for_status() - - d = DebsrcFile() - - with io.BytesIO(response.content) as buf: - with gzip.open(buf, "rt") as f: - d.read(f) - - return d - - -def compare_apt_repo_versions(previous_version: Any, current_version: Any) -> str: - previous_repo = GardenLinuxRepo(previous_version) - current_repo = GardenLinuxRepo(current_version) - pkg_diffs = sorted(compare_repo(previous_repo, current_repo), key=lambda t: t[0]) - - output = f"| Package | {previous_version} | {current_version} |\n" - output += "|---------|--------------------|-------------------|\n" - - for pkg in pkg_diffs: - output += f"|{pkg[0]} | `{pkg[1] if pkg[1] is not None else '-'}` | `{pkg[2] if pkg[2] is not None else '-'}` |\n" - return output - - -def download_all_metadata_files( - version: Any, commitish: str, s3_bucket_name: str -) -> list[str]: - repo = Repo(".") - commit = repo.commit(commitish) - flavors_data = commit.tree["flavors.yaml"].data_stream.read().decode("utf-8") - flavors = FlavorsParser(flavors_data).filter(only_publish=True) - - local_dest_path = Path("s3_downloads") - if local_dest_path.exists(): - shutil.rmtree(local_dest_path) - local_dest_path.mkdir(mode=0o755, exist_ok=False) - - s3_artifacts = S3Artifacts(s3_bucket_name) - - for flavor in flavors: - LOGGER.debug(f"{flavor=} {version=} {commitish=}") - cname = CName(flavor[1], flavor[0], commitish) - # Filter by image variants - only download if the flavor matches one of the variants - flavor_matches_variant = False - for variant_suffix in IMAGE_VARIANTS: - if variant_suffix == "": - last_part = cname.cname.split("-")[-1] - if "_" not in last_part: - flavor_matches_variant = True - break - elif variant_suffix in cname.cname: - # Specific variant (any non-empty string in IMAGE_VARIANTS) - flavor_matches_variant = True - break - - if not flavor_matches_variant: - LOGGER.info( - f"Skipping flavor {cname.cname} - not matching image variants filter" - ) - continue - - try: - commit_short = commitish[:8] - download_metadata_file( - s3_artifacts, cname, version, commit_short, local_dest_path - ) - except IndexError: - LOGGER.warning(f"No artifacts found for flavor {cname.cname}, skipping...") - continue - - return [str(artifact) for artifact in local_dest_path.iterdir()] - - -def download_metadata_file( - s3_artifacts: S3Artifacts, - cname: CName, - version: Any, - commit_short: str, - artifacts_dir: Path, -) -> None: - """ - Download metadata file (s3_metadata.yaml) - """ - LOGGER.debug( - f"{s3_artifacts=} | {cname.cname=} | {version=} | {cname.commit_id=} | {commit_short=} | {artifacts_dir=}" - ) - maybe_release_objects = s3_artifacts.bucket.objects.filter( - Prefix=f"meta/singles/{cname.cname}-{version}-{commit_short}" - ) - - release_object = list(maybe_release_objects)[0] - LOGGER.debug(f"{release_object.bucket_name=} | {release_object.key=}") - - s3_artifacts.bucket.download_file( - release_object.key, - str(artifacts_dir.joinpath(f"{cname.cname}.s3_metadata.yaml")), - ) - - -def get_variant_from_flavor(flavor_name: str) -> str: - """ - Determine the variant from a flavor name by checking for variant suffixes. - Returns the variant key (e.g., 'legacy', 'usi', 'tpm2_trustedboot'). - """ - match flavor_name: - case name if "_usi" in name: - return "usi" - case name if "_tpm2_trustedboot" in name: - return "tpm2_trustedboot" - case _: - return "legacy" diff --git a/src/gardenlinux/github/release_notes/sections.py b/src/gardenlinux/github/release_notes/sections.py deleted file mode 100644 index 0da24803..00000000 --- a/src/gardenlinux/github/release_notes/sections.py +++ /dev/null @@ -1,295 +0,0 @@ -import logging -import re -import textwrap -from collections.abc import Mapping -from typing import Any, Dict - -import requests -import yaml -from yaml import SafeLoader - -from gardenlinux.constants import GLVD_BASE_URL, REQUESTS_TIMEOUTS -from gardenlinux.distro_version import DistroVersion -from gardenlinux.logger import LoggerSetup - -from .deployment_platform.ali_cloud import AliCloud -from .deployment_platform.amazon_web_services import AmazonWebServices -from .deployment_platform.azure import Azure -from .deployment_platform.google_cloud import GoogleCloud -from .deployment_platform.openstack import OpenStack -from .deployment_platform.openstack_baremetal import OpenStackBareMetal -from .helpers import compare_apt_repo_versions, get_variant_from_flavor - -LOGGER = LoggerSetup.get_logger("gardenlinux.github.release_notes", logging.INFO) - -IMAGE_IDS_VARIANT_ORDER = ["legacy", "usi", "tpm2_trustedboot"] -IMAGE_IDS_VARIANT_TABLE_NAMES = { - "legacy": "Default", - "usi": "USI", - "tpm2_trustedboot": "TPM2", -} -IMAGE_IDS_VARIANT_NAMES = { - "legacy": "Default", - "usi": "USI (Unified System Image)", - "tpm2_trustedboot": "TPM2 Trusted Boot", -} -PLATFORMS = { - "ali": AliCloud(), - "aws": AmazonWebServices(), - "gcp": GoogleCloud(), - "azure": Azure(), - "openstack": OpenStack(), - "openstackbaremetal": OpenStackBareMetal(), -} - - -def release_notes_changes_section(gardenlinux_version: str) -> str: - """ - Get list of fixed CVEs, grouped by upgraded package. - Note: This result is not perfect, feel free to edit the generated release notes and - file issues in glvd for improvement suggestions https://github.com/gardenlinux/glvd/issues - """ - try: - url = f"{GLVD_BASE_URL}/releaseNotes/{gardenlinux_version}" - response = requests.get(url, timeout=REQUESTS_TIMEOUTS) - response.raise_for_status() - data = response.json() - - if len(data["packageList"]) == 0: - return "" - - output = [ - "## Changes", - "The following packages have been upgraded, to address the mentioned CVEs:", - ] - for package in data["packageList"]: - upgrade_line = ( - f"- upgrade '{package['sourcePackageName']}' from `{package['oldVersion']}` " - f"to `{package['newVersion']}`" - ) - output.append(upgrade_line) - - if package["fixedCves"]: - for fixedCve in package["fixedCves"]: - output.append(f" - {fixedCve}") - - return "\n".join(output) + "\n\n" - except Exception as exn: - # There are expected error cases, - # for example with versions not supported by glvd (1443.x) - # or when the api is not available - # Fail gracefully by adding the placeholder we previously used, - # so that the release note generation does not fail. - LOGGER.error(f"Failed to process GLVD API output: {exn}") - return textwrap.dedent( - """ - ## Changes - The following packages have been upgraded, to address the mentioned CVEs: - **todo release facilitator: fill this in** - """ - ) - - -def release_notes_software_components_section(package_list: Dict[str, Any]) -> str: - output = "## Software Component Versions\n" - output += "```" - output += "\n" - packages_regex = re.compile( - r"^linux-image-amd64$|^systemd$|^containerd$|^runc$|^curl$|^openssl$|^openssh-server$|^libc-bin$" - ) - for entry in package_list.values(): - if packages_regex.match(entry.deb_source): - output += f"{entry!r}\n" - output += "```" - output += "\n\n" - return output - - -def release_notes_compare_package_versions_section( - gardenlinux_version: str, package_list: dict[str, Any] -) -> str: - version = DistroVersion(gardenlinux_version) - output = "" - - if version.is_patch_release(): - previous_version = f"{version.previous_patch_release()}" - - output += f"## Changes in Package Versions Compared to {previous_version}\n" - output += compare_apt_repo_versions(previous_version, gardenlinux_version) - else: - # creating the full list of all packages exceeds the limit for github release pages - # => ignore this part in the release page for now until we have a better strategy - pass - - return output - - -def generate_table_format( - grouped_data: dict[str, dict[str, dict[str, list[dict[str, Any]]]]], -) -> str: - """ - Generate the table format with collapsible region details - """ - output = "| Variant | Platform | Architecture | Flavor | Regions & Image IDs | Download Links |\n" - output += "|---------|----------|--------------|--------|---------------------|----------------|\n" - - for variant in IMAGE_IDS_VARIANT_ORDER: - if variant not in grouped_data: - continue - - for platform in sorted(grouped_data[variant].keys()): - for arch in sorted(grouped_data[variant][platform].keys()): - for metadata in grouped_data[variant][platform][arch]: - data = PLATFORMS[platform].published_images_by_regions(metadata) - if data is None: - continue - - details_content = PLATFORMS[platform].region_details(metadata) - summary_text = PLATFORMS[platform].summary_text(metadata) - - download_link = PLATFORMS[platform].artifact_for_flavor( - data["flavor"] - ) - - variant_display = IMAGE_IDS_VARIANT_TABLE_NAMES[variant] - output += ( - f"| {variant_display} " - f"| {PLATFORMS[platform].full_name()} " - f"| {arch} " - f"| `{data['flavor']}` " - f"|
{summary_text}
{details_content}
" - f"|
Download
{download_link}
" - "|\n" - ) - - return output - - -def generate_detailed_format( - grouped_data: dict[str, dict[str, dict[str, list[dict[str, Any]]]]], -) -> str: - """ - Generate the old detailed format with YAML - """ - output = "" - - for variant in IMAGE_IDS_VARIANT_ORDER: - if variant not in grouped_data: - continue - - output += f"
\nVariant - {IMAGE_IDS_VARIANT_NAMES[variant]}\n\n" - output += f"### Variant - {IMAGE_IDS_VARIANT_NAMES[variant]}\n\n" - - for platform in sorted(grouped_data[variant].keys()): - platform_long_name = PLATFORMS[platform].full_name() - platform_short_name = PLATFORMS[platform].short_name().upper() - output += f"
\n{platform_short_name} - {platform_long_name}\n\n" - output += f"#### {platform_short_name} - {platform_long_name}\n\n" - - for arch in sorted(grouped_data[variant][platform].keys()): - output += f"
\n{arch}\n\n" - output += f"##### {arch}\n\n" - output += "```\n" - - # Process all metadata for this variant/platform/architecture - for metadata in grouped_data[variant][platform][arch]: - data = PLATFORMS[platform].published_images_by_regions(metadata) - if data is None: - continue - - output += f"- flavor: {data['flavor']}\n" - - download_url = PLATFORMS[platform].artifact_for_flavor( - data["flavor"], markdown_format=False - ) - output += f" download_url: {download_url}\n" - - if "regions" in data: - output += " regions:\n" - for region in data["regions"]: - if "image_name" in region: - output += f" - region: {region['region']}\n" - output += f" image_id: {region['image_id']}\n" - output += f" image_name: {region['image_name']}\n" - else: - output += f" - region: {region['region']}\n" - output += f" image_id: {region['image_id']}\n" - elif "details" in data and platform != "gcp": - output += " details:\n" - for key, value in data["details"].items(): - output += f" {key}: {value}\n" - elif platform == "gcp" and "details" in data: - # For GCP, move details up to same level as flavor - for key, value in data["details"].items(): - output += f" {key}: {value}\n" - elif "gallery_images" in data or "marketplace_images" in data: - if data.get("gallery_images"): - output += " gallery_images:\n" - for img in data["gallery_images"]: - output += f" - hyper_v_generation: {img['hyper_v_generation']}\n" - output += f" azure_cloud: {img['azure_cloud']}\n" - output += f" image_id: {img['image_id']}\n" - if data.get("marketplace_images"): - output += " marketplace_images:\n" - for img in data["marketplace_images"]: - output += f" - hyper_v_generation: {img['hyper_v_generation']}\n" - output += f" urn: {img['urn']}\n" - - output += "```\n\n" - output += "
\n\n" - - output += "
\n\n" - - output += "
\n\n" - - return output - - -def release_notes_image_ids_section(metadata_files: list[str]) -> str: - """ - Groups metadata files by image variant, then platform, then architecture - """ - # Group metadata by variant, platform, and architecture - grouped_data: dict[str, dict[str, dict[str, list[dict[str, Any]]]]] = {} - - for metadata_file_path in metadata_files: - with open(metadata_file_path) as f: - metadata = yaml.load(f, Loader=SafeLoader) - - published_image_metadata = metadata.get("published_image_metadata", {}) - - # Skip if no publishing metadata found - if ( - not isinstance(published_image_metadata, Mapping) - or len(published_image_metadata) < 1 - ): - continue - - platform = metadata["platform"] - arch = metadata["architecture"] - - # Determine variant from flavor name - flavor_name = metadata["s3_key"].split("/")[-1] - variant = get_variant_from_flavor(flavor_name) - - if variant not in grouped_data: - grouped_data[variant] = {} - if platform not in grouped_data[variant]: - grouped_data[variant][platform] = {} - if arch not in grouped_data[variant][platform]: - grouped_data[variant][platform][arch] = [] - - grouped_data[variant][platform][arch].append(metadata) - - output = "## Published Images\n\n" - - output += "
\n📊 Table View\n\n" - output += generate_table_format(grouped_data) - output += "\n
\n\n" - - # Old format - output += "
\n📝 Detailed View\n\n" - output += generate_detailed_format(grouped_data) - output += "\n
\n\n" - - return output diff --git a/test-data/release_notes/github_release_notes_1877.3.md b/test-data/release_notes/github_release_notes_1877.3.md index 83bd76d1..f011babf 100644 --- a/test-data/release_notes/github_release_notes_1877.3.md +++ b/test-data/release_notes/github_release_notes_1877.3.md @@ -1,4 +1,5 @@ -## Changes +# Changes + The following packages have been upgraded, to address the mentioned CVEs: - upgrade 'gnutls28' from `3.8.9-2` to `3.8.9-3gl0+bp1877` - CVE-2025-32988 @@ -160,6 +161,7 @@ The following packages have been upgraded, to address the mentioned CVEs: - CVE-2025-47268 ## Software Component Versions + ``` containerd 2.1.4-0gl1+bp1877 curl 8.14.1-2gl0+bp1877 @@ -171,7 +173,8 @@ runc 1.3.0-1gl0+bp1877 systemd 257.5-2gl0 ``` -## Changes in Package Versions Compared to 1877.2 +## Changes in Package Versions compared to 1877.2 + | Package | 1877.2 | 1877.3 | |---------|--------------------|-------------------| |bpftool | `7.5.0+6.12.40-2gl0` | `7.5.0+6.12.44-3gl0` | @@ -276,933 +279,291 @@ systemd 257.5-2gl0 |sqlite3-tools-dbgsym | `-` | `3.46.1-7gl0+bp1877` | |usbip | `2.0+6.12.40-2gl0` | `2.0+6.12.44-3gl0` | |usbip-dbgsym | `2.0+6.12.40-2gl0` | `2.0+6.12.44-3gl0` | -## Published Images - -
-📊 Table View - -| Variant | Platform | Architecture | Flavor | Regions & Image IDs | Download Links | -|---------|----------|--------------|--------|---------------------|----------------| -| Default | Alibaba Cloud | amd64 | `ali-gardener_prod-amd64-1877.3-75df9f40` |
28 regions
**cn-qingdao:** m-m5efm8l2bltkbloui235
**cn-beijing:** m-2zee5ebi20ltzy5et7in
**cn-zhangjiakou:** m-8vbddy2wfex9nb29afcy
**cn-huhehaote:** m-hp3bx14og6cw9thujw1d
**cn-wulanchabu:** m-0jlh1iq2f3bryb5okjdk
**cn-hangzhou:** m-bp13aseh5a2wn0s5rdz6
**cn-shanghai:** m-uf61jbe9n8a9291h4u21
**cn-nanjing:** m-gc77bfbctuzphl2bpk0o
**cn-shenzhen:** m-wz9gio8m5ey0foj0g4xx
**cn-heyuan:** m-f8zdn54v0blnsafxb1t5
**cn-guangzhou:** m-7xv0q5feffsxxyttxdy9
**cn-fuzhou:** m-gw07bfbctuzphl2bpk0p
**cn-wuhan-lr:** m-n4a1u2avlb9pq0u5bdms
**cn-chengdu:** m-2vc5saul2saa2z57h216
**cn-hongkong:** m-j6c4zk6mwb2673iq5wrz
**ap-northeast-1:** m-6weibwo3vrt7ar7nelc9
**ap-northeast-2:** m-mj73oldn06th2vy0ymhv
**ap-southeast-1:** m-t4ngrf81d0fohwq493pw
**ap-southeast-3:** m-8psd64gzc1eru0qld7cc
**ap-southeast-6:** m-5tsdd6k3z1vvdyyio7zn
**ap-southeast-5:** m-k1aj4usnhqcssa2fpy0c
**ap-southeast-7:** m-0jo6uwekvn0gnwhwnq3s
**us-east-1:** m-0xi8netpfc2fdwfstz3c
**us-west-1:** m-rj9gwpx907qv6p6x8w45
**na-south-1:** m-4hfi34x77oaeznwuulq6
**eu-west-1:** m-d7o2ny5xc0m3kacxjbem
**me-east-1:** m-eb39mgohcec6gaynet9l
**eu-central-1:** m-gw86dlqmpaugljiykx91
|
Download
[ali-gardener_prod-amd64-1877.3-75df9f40.qcow2](https://gardenlinux-github-releases.s3.amazonaws.com/objects/ali-gardener_prod-amd64-1877.3-75df9f40/ali-gardener_prod-amd64-1877.3-75df9f40.qcow2)
| -| Default | Amazon Web Services | amd64 | `aws-gardener_prod-amd64-1877.3-75df9f40` |
21 regions
**ap-south-1:** ami-00c6adf1de4dd746a
**eu-north-1:** ami-07ad3940828172b90
**eu-west-3:** ami-071f4f48679d86638
**eu-south-1:** ami-0b10af1a19df9f038
**eu-west-2:** ami-0e2b7fe07573b71cd
**eu-west-1:** ami-01c547eb85d61da61
**ap-northeast-3:** ami-0dad917ede94cd3c7
**ap-northeast-2:** ami-0ecbeaf40d4643016
**ap-northeast-1:** ami-0b7225242babad11c
**me-central-1:** ami-0d298e552bf051bc7
**ca-central-1:** ami-0af8422162c8f056e
**sa-east-1:** ami-05d885175e942fc80
**ap-southeast-1:** ami-0a9802680adf7e430
**ap-southeast-2:** ami-07ed6f1e62fbd6d66
**us-east-1:** ami-055a0ce37433fcdee
**us-east-2:** ami-07e9069631850755a
**us-west-1:** ami-08c18abab76066f71
**us-west-2:** ami-00eca0475f90a1f8c
**eu-central-1:** ami-0198822fa7d539f8c
**cn-north-1:** ami-093c993faaca89b4d
**cn-northwest-1:** ami-05e1cc73d997d67b7
|
Download
[aws-gardener_prod-amd64-1877.3-75df9f40.raw](https://gardenlinux-github-releases.s3.amazonaws.com/objects/aws-gardener_prod-amd64-1877.3-75df9f40/aws-gardener_prod-amd64-1877.3-75df9f40.raw)
| -| Default | Amazon Web Services | arm64 | `aws-gardener_prod-arm64-1877.3-75df9f40` |
21 regions
**ap-south-1:** ami-00cd00c30d19609a2
**eu-north-1:** ami-0a969a1a1c4726831
**eu-west-3:** ami-0b579f6c70b7c4fe6
**eu-south-1:** ami-06c38608e2e7223d3
**eu-west-2:** ami-005c7058c3923b2eb
**eu-west-1:** ami-0395c3cd38a0a5cd6
**ap-northeast-3:** ami-0db3697cea87a5104
**ap-northeast-2:** ami-084444f62c7c580fb
**ap-northeast-1:** ami-017237dd9abeae8dd
**me-central-1:** ami-08efdb3153d0cd184
**ca-central-1:** ami-05b535ae9418fee3d
**sa-east-1:** ami-036ded98bad763e3c
**ap-southeast-1:** ami-03fcefb2fd18519d0
**ap-southeast-2:** ami-040f9d0caa5d79e84
**us-east-1:** ami-04110d6a1970e748c
**us-east-2:** ami-0c8dc664a21d5ca08
**us-west-1:** ami-0ddc462d075935666
**us-west-2:** ami-0e67c2546e54fed06
**eu-central-1:** ami-06a2a1e7da947b192
**cn-north-1:** ami-0b3755339496a3158
**cn-northwest-1:** ami-06fc0f74b500d2d82
|
Download
[aws-gardener_prod-arm64-1877.3-75df9f40.raw](https://gardenlinux-github-releases.s3.amazonaws.com/objects/aws-gardener_prod-arm64-1877.3-75df9f40/aws-gardener_prod-arm64-1877.3-75df9f40.raw)
| -| Default | Microsoft Azure | amd64 | `azure-gardener_prod-amd64-1877.3-75df9f40` |
4 gallery + 0 marketplace images
**Gallery Images:**
• V1 (public): /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme/Versions/1877.3.0
• V2 (public): /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-gen2/Versions/1877.3.0
• V1 (china): /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme/Versions/1877.3.0
• V2 (china): /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-gen2/Versions/1877.3.0
|
Download
[azure-gardener_prod-amd64-1877.3-75df9f40.vhd](https://gardenlinux-github-releases.s3.amazonaws.com/objects/azure-gardener_prod-amd64-1877.3-75df9f40/azure-gardener_prod-amd64-1877.3-75df9f40.vhd)
| -| Default | Microsoft Azure | arm64 | `azure-gardener_prod-arm64-1877.3-75df9f40` |
2 gallery + 0 marketplace images
**Gallery Images:**
• V2 (public): /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-arm64-gen2/Versions/1877.3.0
• V2 (china): /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-arm64-gen2/Versions/1877.3.0
|
Download
[azure-gardener_prod-arm64-1877.3-75df9f40.vhd](https://gardenlinux-github-releases.s3.amazonaws.com/objects/azure-gardener_prod-arm64-1877.3-75df9f40/azure-gardener_prod-arm64-1877.3-75df9f40.vhd)
| -| Default | Google Cloud Platform | amd64 | `gcp-gardener_prod-amd64-1877.3-75df9f40` |
Global availability
**Image Name:** gardenlinux-gcp-ff804026cbe7b5f2d6f729e4-1877-3-75df9f40
**Project:** sap-se-gcp-gardenlinux
**Availability:** Global (all regions)
|
Download
[gcp-gardener_prod-amd64-1877.3-75df9f40.gcpimage.tar.gz](https://gardenlinux-github-releases.s3.amazonaws.com/objects/gcp-gardener_prod-amd64-1877.3-75df9f40/gcp-gardener_prod-amd64-1877.3-75df9f40.gcpimage.tar.gz)
| -| Default | Google Cloud Platform | arm64 | `gcp-gardener_prod-arm64-1877.3-75df9f40` |
Global availability
**Image Name:** gardenlinux-gcp-c8504d3c3e67cf2fc7c3408c-1877-3-75df9f40
**Project:** sap-se-gcp-gardenlinux
**Availability:** Global (all regions)
|
Download
[gcp-gardener_prod-arm64-1877.3-75df9f40.gcpimage.tar.gz](https://gardenlinux-github-releases.s3.amazonaws.com/objects/gcp-gardener_prod-arm64-1877.3-75df9f40/gcp-gardener_prod-arm64-1877.3-75df9f40.gcpimage.tar.gz)
| -| Default | OpenStack | amd64 | `openstack-gardener_prod-amd64-1877.3-75df9f40` |
15 regions
**eu-de-1:** ed3b4c3d-941f-456a-a551-bd52b8397443 (gardenlinux-1877.3)
**eu-de-2:** 5ea6fb4f-20fc-43b8-8ffe-af8da6d61d6a (gardenlinux-1877.3)
**eu-nl-1:** ac9b5d43-ff53-494d-8adf-2249c324a9db (gardenlinux-1877.3)
**la-br-1:** 404f22a3-9822-4696-a60f-8566eedb93e3 (gardenlinux-1877.3)
**na-ca-1:** b69b72f3-574a-4f76-b4eb-ac9185ea2681 (gardenlinux-1877.3)
**na-us-1:** 40e99366-f13b-402a-a264-e7e4773ab8ba (gardenlinux-1877.3)
**na-us-2:** c50200c6-95fd-4a97-bef2-90b2d6afa3d3 (gardenlinux-1877.3)
**na-us-3:** d5b1d8c0-3420-4a82-931d-0506a6b8f166 (gardenlinux-1877.3)
**ap-ae-1:** 81c26cb7-c515-4610-949a-92c275640325 (gardenlinux-1877.3)
**ap-au-1:** 2d6e3edd-5596-41e6-a640-4b1b8e7310e7 (gardenlinux-1877.3)
**ap-cn-1:** 3564b5ef-9b37-4926-bb23-5655cf90de69 (gardenlinux-1877.3)
**ap-jp-1:** 2ff61187-f004-4317-bd4c-a17d93b475bc (gardenlinux-1877.3)
**ap-jp-2:** 2bc58951-9bf7-445b-a6e4-f634c7522d9b (gardenlinux-1877.3)
**ap-sa-1:** e4a4aa92-335a-454b-83bb-643cb918cf6a (gardenlinux-1877.3)
**ap-sa-2:** d3ac5df8-ce38-4a23-b611-dfef6b7a0db9 (gardenlinux-1877.3)
|
Download
[openstack-gardener_prod-amd64-1877.3-75df9f40.raw](https://gardenlinux-github-releases.s3.amazonaws.com/objects/openstack-gardener_prod-amd64-1877.3-75df9f40/openstack-gardener_prod-amd64-1877.3-75df9f40.raw)
| -| Default | OpenStack Baremetal | amd64 | `openstackbaremetal-gardener_prod-amd64-1877.3-75df9f40` |
15 regions
**eu-de-1:** 01c3ab26-5b93-4655-a743-1fef60f64b53 (gardenlinux-1877.3-baremetal)
**eu-de-2:** 7488d07b-65f1-4b85-8df8-13244b895d71 (gardenlinux-1877.3-baremetal)
**eu-nl-1:** 1926a818-55d5-49e1-9af8-eab8450705eb (gardenlinux-1877.3-baremetal)
**la-br-1:** 6fda686d-d2f7-4018-ab4c-1250e898197a (gardenlinux-1877.3-baremetal)
**na-ca-1:** a032ecc1-3bee-4d65-9f68-3e3f99e2c291 (gardenlinux-1877.3-baremetal)
**na-us-1:** d663d5f1-1b44-41af-9039-e36cc64a5920 (gardenlinux-1877.3-baremetal)
**na-us-2:** 818bbfdd-4ee4-49ee-8294-dc3a3c66971f (gardenlinux-1877.3-baremetal)
**na-us-3:** b154b48b-050f-48d6-997f-b6c2756079a6 (gardenlinux-1877.3-baremetal)
**ap-ae-1:** 5992e19c-2ca2-47be-ae55-50e2fd26b662 (gardenlinux-1877.3-baremetal)
**ap-au-1:** 986403a6-e254-4689-8f81-e32dc33c9b64 (gardenlinux-1877.3-baremetal)
**ap-cn-1:** 0c794890-a690-4881-b0c2-39a939b020e2 (gardenlinux-1877.3-baremetal)
**ap-jp-1:** f5be2c30-8e8e-4713-9e34-eb0a18922af5 (gardenlinux-1877.3-baremetal)
**ap-jp-2:** 8edb20a7-f0f2-47f2-9112-faa2569c3893 (gardenlinux-1877.3-baremetal)
**ap-sa-1:** dc12514b-b0a8-40dd-b756-a4d27421029c (gardenlinux-1877.3-baremetal)
**ap-sa-2:** 617f5ae7-91fd-4149-b783-7a3701a5f420 (gardenlinux-1877.3-baremetal)
|
Download
[openstackbaremetal-gardener_prod-amd64-1877.3-75df9f40.raw](https://gardenlinux-github-releases.s3.amazonaws.com/objects/openstackbaremetal-gardener_prod-amd64-1877.3-75df9f40/openstackbaremetal-gardener_prod-amd64-1877.3-75df9f40.raw)
| -| USI | Amazon Web Services | amd64 | `aws-gardener_prod_usi-amd64-1877.3-75df9f40` |
21 regions
**ap-south-1:** ami-0e904b4c264dbe923
**eu-north-1:** ami-016f506d46abb5c06
**eu-west-3:** ami-052404d8a97e9ec57
**eu-south-1:** ami-002193c185fb2d939
**eu-west-2:** ami-0225bc62c7d291107
**eu-west-1:** ami-0f737c34ae9ccfe10
**ap-northeast-3:** ami-0ecf19a78a7259c02
**ap-northeast-2:** ami-029152bf0a15cf306
**ap-northeast-1:** ami-0bf003b58ed636124
**me-central-1:** ami-0546ca7d7c2e00077
**ca-central-1:** ami-0a0081cbd4b479d33
**sa-east-1:** ami-086d3b7282338bcd1
**ap-southeast-1:** ami-04973efd023e5883f
**ap-southeast-2:** ami-00389783d0b7ef01b
**us-east-1:** ami-0f5c28bbc45608e9b
**us-east-2:** ami-08c7494a2a00b74e5
**us-west-1:** ami-0e2290963849dba62
**us-west-2:** ami-0fb86d519a38da40f
**eu-central-1:** ami-0c6394e4fdbefe8c0
**cn-north-1:** ami-0b4c979b27a0a7714
**cn-northwest-1:** ami-0cab977e76e274599
|
Download
[aws-gardener_prod_usi-amd64-1877.3-75df9f40.raw](https://gardenlinux-github-releases.s3.amazonaws.com/objects/aws-gardener_prod_usi-amd64-1877.3-75df9f40/aws-gardener_prod_usi-amd64-1877.3-75df9f40.raw)
| -| USI | Amazon Web Services | arm64 | `aws-gardener_prod_usi-arm64-1877.3-75df9f40` |
21 regions
**ap-south-1:** ami-029f2b705d69f9d50
**eu-north-1:** ami-0b1a9e403ea563206
**eu-west-3:** ami-067465814788be84a
**eu-south-1:** ami-092d7cf152ef6df29
**eu-west-2:** ami-0441298c8ae55a62b
**eu-west-1:** ami-012e58abe02f904c1
**ap-northeast-3:** ami-08c18c5f1aa7e9fba
**ap-northeast-2:** ami-0277ca365657bd9c2
**ap-northeast-1:** ami-006a3f35202f6edd4
**me-central-1:** ami-0aa9e8af8c777e400
**ca-central-1:** ami-0f8225fd2d6009961
**sa-east-1:** ami-0e945c537aef91eff
**ap-southeast-1:** ami-0f30b29a4428f7cea
**ap-southeast-2:** ami-0129e3a207e3e6f9d
**us-east-1:** ami-0cc9f69e3a7594e7b
**us-east-2:** ami-046243dad95d56f2a
**us-west-1:** ami-03ae03953c81a43c1
**us-west-2:** ami-0cbe1dbfeda64dc9b
**eu-central-1:** ami-0dd2780bfcddbda6b
**cn-north-1:** ami-0d993477d25affb3c
**cn-northwest-1:** ami-0a7fe5959bb23fab8
|
Download
[aws-gardener_prod_usi-arm64-1877.3-75df9f40.raw](https://gardenlinux-github-releases.s3.amazonaws.com/objects/aws-gardener_prod_usi-arm64-1877.3-75df9f40/aws-gardener_prod_usi-arm64-1877.3-75df9f40.raw)
| -| USI | Microsoft Azure | amd64 | `azure-gardener_prod_usi-amd64-1877.3-75df9f40` |
2 gallery + 0 marketplace images
**Gallery Images:**
• V2 (public): /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-gen2-usi/Versions/1877.3.0
• V2 (china): /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-gen2-usi/Versions/1877.3.0
|
Download
[azure-gardener_prod_usi-amd64-1877.3-75df9f40.vhd](https://gardenlinux-github-releases.s3.amazonaws.com/objects/azure-gardener_prod_usi-amd64-1877.3-75df9f40/azure-gardener_prod_usi-amd64-1877.3-75df9f40.vhd)
| -| USI | Microsoft Azure | arm64 | `azure-gardener_prod_usi-arm64-1877.3-75df9f40` |
2 gallery + 0 marketplace images
**Gallery Images:**
• V2 (public): /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-arm64-gen2-usi/Versions/1877.3.0
• V2 (china): /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-arm64-gen2-usi/Versions/1877.3.0
|
Download
[azure-gardener_prod_usi-arm64-1877.3-75df9f40.vhd](https://gardenlinux-github-releases.s3.amazonaws.com/objects/azure-gardener_prod_usi-arm64-1877.3-75df9f40/azure-gardener_prod_usi-arm64-1877.3-75df9f40.vhd)
| -| USI | Google Cloud Platform | amd64 | `gcp-gardener_prod_usi-amd64-1877.3-75df9f40` |
Global availability
**Image Name:** gardenlinux-gcp-51db8a4be084c3b640095f4b-1877-3-75df9f40
**Project:** sap-se-gcp-gardenlinux
**Availability:** Global (all regions)
|
Download
[gcp-gardener_prod_usi-amd64-1877.3-75df9f40.gcpimage.tar.gz](https://gardenlinux-github-releases.s3.amazonaws.com/objects/gcp-gardener_prod_usi-amd64-1877.3-75df9f40/gcp-gardener_prod_usi-amd64-1877.3-75df9f40.gcpimage.tar.gz)
| -| USI | Google Cloud Platform | arm64 | `gcp-gardener_prod_usi-arm64-1877.3-75df9f40` |
Global availability
**Image Name:** gardenlinux-gcp-c00f1e20ffeed4d8b80a76b9-1877-3-75df9f40
**Project:** sap-se-gcp-gardenlinux
**Availability:** Global (all regions)
|
Download
[gcp-gardener_prod_usi-arm64-1877.3-75df9f40.gcpimage.tar.gz](https://gardenlinux-github-releases.s3.amazonaws.com/objects/gcp-gardener_prod_usi-arm64-1877.3-75df9f40/gcp-gardener_prod_usi-arm64-1877.3-75df9f40.gcpimage.tar.gz)
| -| USI | OpenStack | amd64 | `openstack-gardener_prod_usi-amd64-1877.3-75df9f40` |
15 regions
**eu-de-1:** 15fc38b3-1cee-4c0a-829a-ef1f7faa1920 (gardenlinux-1877.3)
**eu-de-2:** c4e8e8e5-8c92-4c73-b21b-333087e7b092 (gardenlinux-1877.3)
**eu-nl-1:** e6f9e054-0613-4204-98c7-84676680418a (gardenlinux-1877.3)
**la-br-1:** 04416634-2eaf-44a1-a653-b1ae36bf0e0e (gardenlinux-1877.3)
**na-ca-1:** b548d8fd-0e6b-4cb6-9cd1-68b258df00cc (gardenlinux-1877.3)
**na-us-1:** 0a97e9af-a1f3-4ae4-bf44-98c432aa436c (gardenlinux-1877.3)
**na-us-2:** b1705d73-3f67-427c-8ade-5e245a857338 (gardenlinux-1877.3)
**na-us-3:** da3234f1-307c-431e-80bb-9e51dd75673d (gardenlinux-1877.3)
**ap-ae-1:** 16f24b39-b9ba-4756-8dcd-82473182f1e4 (gardenlinux-1877.3)
**ap-au-1:** 49de0ff1-2c7e-439d-a065-07c837fe48a8 (gardenlinux-1877.3)
**ap-cn-1:** 23a94a40-1e9a-4f4b-b2b6-4c167493fbb0 (gardenlinux-1877.3)
**ap-jp-1:** 1558417d-14bb-413e-9194-88b2bc5f18aa (gardenlinux-1877.3)
**ap-jp-2:** 8d39ad55-2f09-490e-8fa7-0bdf5c854ed7 (gardenlinux-1877.3)
**ap-sa-1:** 62be0147-062a-4375-b142-278a811e9754 (gardenlinux-1877.3)
**ap-sa-2:** 510d1ff1-4fc6-49ec-ad2f-a0985217dd14 (gardenlinux-1877.3)
|
Download
[openstack-gardener_prod_usi-amd64-1877.3-75df9f40.raw](https://gardenlinux-github-releases.s3.amazonaws.com/objects/openstack-gardener_prod_usi-amd64-1877.3-75df9f40/openstack-gardener_prod_usi-amd64-1877.3-75df9f40.raw)
| -| TPM2 | Amazon Web Services | amd64 | `aws-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40` |
19 regions
**ap-south-1:** ami-0052561d7bccfe6b7
**eu-north-1:** ami-06623180935c63669
**eu-west-3:** ami-026632e35fe37f9f4
**eu-south-1:** ami-0b60116fac38c2556
**eu-west-2:** ami-0ecd844859adf35c5
**eu-west-1:** ami-0313333df0acd7eb0
**ap-northeast-3:** ami-04e53edbd6ce18fc6
**ap-northeast-2:** ami-0ae03e19777874cef
**ap-northeast-1:** ami-079e68ce96cc03e78
**me-central-1:** ami-01e368d192a479934
**ca-central-1:** ami-02cabce931cafcf1f
**sa-east-1:** ami-075d5fa3b98620e15
**ap-southeast-1:** ami-0a26b478c0a210190
**ap-southeast-2:** ami-0f226413240aec4aa
**us-east-1:** ami-07dea60f619226e1b
**us-east-2:** ami-0e8e852987ee840c3
**us-west-1:** ami-0d9314ee5a439ab29
**us-west-2:** ami-04dc4614abf1649ab
**eu-central-1:** ami-005f7dab618420a91
|
Download
[aws-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.raw](https://gardenlinux-github-releases.s3.amazonaws.com/objects/aws-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40/aws-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.raw)
| -| TPM2 | Amazon Web Services | arm64 | `aws-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40` |
19 regions
**ap-south-1:** ami-035b751a08f528e47
**eu-north-1:** ami-04c60d1feb092d00f
**eu-west-3:** ami-0443172a73fe4fb27
**eu-south-1:** ami-02f8a867d02227542
**eu-west-2:** ami-06a900dd59c84620d
**eu-west-1:** ami-056a9d8447a991bff
**ap-northeast-3:** ami-0769caf50f7b7fb6f
**ap-northeast-2:** ami-06ad8c60e1093b543
**ap-northeast-1:** ami-0b8313d62dfeec78b
**me-central-1:** ami-0fa388dcaca7b3baf
**ca-central-1:** ami-02e7a07f60a5e0411
**sa-east-1:** ami-000ca39b22f2a695c
**ap-southeast-1:** ami-04f521cff21b58f50
**ap-southeast-2:** ami-02f5afcce42276457
**us-east-1:** ami-0a25256d5aaf8fdd7
**us-east-2:** ami-07bcfed39a329b612
**us-west-1:** ami-0b2e93f36b5a8bff2
**us-west-2:** ami-063f4f34958917b5c
**eu-central-1:** ami-0b15b442dd5e90d50
|
Download
[aws-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.raw](https://gardenlinux-github-releases.s3.amazonaws.com/objects/aws-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40/aws-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.raw)
| -| TPM2 | Microsoft Azure | amd64 | `azure-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40` |
2 gallery + 0 marketplace images
**Gallery Images:**
• V2 (public): /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-gen2-usi-secureboot/Versions/1877.3.0
• V2 (china): /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-gen2-usi-secureboot/Versions/1877.3.0
|
Download
[azure-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.vhd](https://gardenlinux-github-releases.s3.amazonaws.com/objects/azure-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40/azure-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.vhd)
| -| TPM2 | Microsoft Azure | arm64 | `azure-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40` |
2 gallery + 0 marketplace images
**Gallery Images:**
• V2 (public): /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-arm64-gen2-usi-secureboot/Versions/1877.3.0
• V2 (china): /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-arm64-gen2-usi-secureboot/Versions/1877.3.0
|
Download
[azure-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.vhd](https://gardenlinux-github-releases.s3.amazonaws.com/objects/azure-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40/azure-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.vhd)
| -| TPM2 | Google Cloud Platform | amd64 | `gcp-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40` |
Global availability
**Image Name:** gardenlinux-gcp-b4636aa3660a8d166531aab9-1877-3-75df9f40
**Project:** sap-se-gcp-gardenlinux
**Availability:** Global (all regions)
|
Download
[gcp-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.gcpimage.tar.gz](https://gardenlinux-github-releases.s3.amazonaws.com/objects/gcp-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40/gcp-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.gcpimage.tar.gz)
| -| TPM2 | Google Cloud Platform | arm64 | `gcp-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40` |
Global availability
**Image Name:** gardenlinux-gcp-63fd9d7dd465420fd4e499ab-1877-3-75df9f40
**Project:** sap-se-gcp-gardenlinux
**Availability:** Global (all regions)
|
Download
[gcp-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.gcpimage.tar.gz](https://gardenlinux-github-releases.s3.amazonaws.com/objects/gcp-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40/gcp-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.gcpimage.tar.gz)
| - -
- -
-📝 Detailed View - -
-Variant - Default - -### Variant - Default - -
-ALI - Alibaba Cloud - -#### ALI - Alibaba Cloud - -
-amd64 - -##### amd64 - -``` -- flavor: ali-gardener_prod-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/ali-gardener_prod-amd64-1877.3-75df9f40/ali-gardener_prod-amd64-1877.3-75df9f40.qcow2 - regions: - - region: cn-qingdao - image_id: m-m5efm8l2bltkbloui235 - - region: cn-beijing - image_id: m-2zee5ebi20ltzy5et7in - - region: cn-zhangjiakou - image_id: m-8vbddy2wfex9nb29afcy - - region: cn-huhehaote - image_id: m-hp3bx14og6cw9thujw1d - - region: cn-wulanchabu - image_id: m-0jlh1iq2f3bryb5okjdk - - region: cn-hangzhou - image_id: m-bp13aseh5a2wn0s5rdz6 - - region: cn-shanghai - image_id: m-uf61jbe9n8a9291h4u21 - - region: cn-nanjing - image_id: m-gc77bfbctuzphl2bpk0o - - region: cn-shenzhen - image_id: m-wz9gio8m5ey0foj0g4xx - - region: cn-heyuan - image_id: m-f8zdn54v0blnsafxb1t5 - - region: cn-guangzhou - image_id: m-7xv0q5feffsxxyttxdy9 - - region: cn-fuzhou - image_id: m-gw07bfbctuzphl2bpk0p - - region: cn-wuhan-lr - image_id: m-n4a1u2avlb9pq0u5bdms - - region: cn-chengdu - image_id: m-2vc5saul2saa2z57h216 - - region: cn-hongkong - image_id: m-j6c4zk6mwb2673iq5wrz - - region: ap-northeast-1 - image_id: m-6weibwo3vrt7ar7nelc9 - - region: ap-northeast-2 - image_id: m-mj73oldn06th2vy0ymhv - - region: ap-southeast-1 - image_id: m-t4ngrf81d0fohwq493pw - - region: ap-southeast-3 - image_id: m-8psd64gzc1eru0qld7cc - - region: ap-southeast-6 - image_id: m-5tsdd6k3z1vvdyyio7zn - - region: ap-southeast-5 - image_id: m-k1aj4usnhqcssa2fpy0c - - region: ap-southeast-7 - image_id: m-0jo6uwekvn0gnwhwnq3s - - region: us-east-1 - image_id: m-0xi8netpfc2fdwfstz3c - - region: us-west-1 - image_id: m-rj9gwpx907qv6p6x8w45 - - region: na-south-1 - image_id: m-4hfi34x77oaeznwuulq6 - - region: eu-west-1 - image_id: m-d7o2ny5xc0m3kacxjbem - - region: me-east-1 - image_id: m-eb39mgohcec6gaynet9l - - region: eu-central-1 - image_id: m-gw86dlqmpaugljiykx91 -``` - -
- -
- -
-AWS - Amazon Web Services - -#### AWS - Amazon Web Services - -
-amd64 - -##### amd64 - -``` -- flavor: aws-gardener_prod-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/aws-gardener_prod-amd64-1877.3-75df9f40/aws-gardener_prod-amd64-1877.3-75df9f40.raw - regions: - - region: ap-south-1 - image_id: ami-00c6adf1de4dd746a - - region: eu-north-1 - image_id: ami-07ad3940828172b90 - - region: eu-west-3 - image_id: ami-071f4f48679d86638 - - region: eu-south-1 - image_id: ami-0b10af1a19df9f038 - - region: eu-west-2 - image_id: ami-0e2b7fe07573b71cd - - region: eu-west-1 - image_id: ami-01c547eb85d61da61 - - region: ap-northeast-3 - image_id: ami-0dad917ede94cd3c7 - - region: ap-northeast-2 - image_id: ami-0ecbeaf40d4643016 - - region: ap-northeast-1 - image_id: ami-0b7225242babad11c - - region: me-central-1 - image_id: ami-0d298e552bf051bc7 - - region: ca-central-1 - image_id: ami-0af8422162c8f056e - - region: sa-east-1 - image_id: ami-05d885175e942fc80 - - region: ap-southeast-1 - image_id: ami-0a9802680adf7e430 - - region: ap-southeast-2 - image_id: ami-07ed6f1e62fbd6d66 - - region: us-east-1 - image_id: ami-055a0ce37433fcdee - - region: us-east-2 - image_id: ami-07e9069631850755a - - region: us-west-1 - image_id: ami-08c18abab76066f71 - - region: us-west-2 - image_id: ami-00eca0475f90a1f8c - - region: eu-central-1 - image_id: ami-0198822fa7d539f8c - - region: cn-north-1 - image_id: ami-093c993faaca89b4d - - region: cn-northwest-1 - image_id: ami-05e1cc73d997d67b7 -``` - -
- -
-arm64 - -##### arm64 - -``` -- flavor: aws-gardener_prod-arm64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/aws-gardener_prod-arm64-1877.3-75df9f40/aws-gardener_prod-arm64-1877.3-75df9f40.raw - regions: - - region: ap-south-1 - image_id: ami-00cd00c30d19609a2 - - region: eu-north-1 - image_id: ami-0a969a1a1c4726831 - - region: eu-west-3 - image_id: ami-0b579f6c70b7c4fe6 - - region: eu-south-1 - image_id: ami-06c38608e2e7223d3 - - region: eu-west-2 - image_id: ami-005c7058c3923b2eb - - region: eu-west-1 - image_id: ami-0395c3cd38a0a5cd6 - - region: ap-northeast-3 - image_id: ami-0db3697cea87a5104 - - region: ap-northeast-2 - image_id: ami-084444f62c7c580fb - - region: ap-northeast-1 - image_id: ami-017237dd9abeae8dd - - region: me-central-1 - image_id: ami-08efdb3153d0cd184 - - region: ca-central-1 - image_id: ami-05b535ae9418fee3d - - region: sa-east-1 - image_id: ami-036ded98bad763e3c - - region: ap-southeast-1 - image_id: ami-03fcefb2fd18519d0 - - region: ap-southeast-2 - image_id: ami-040f9d0caa5d79e84 - - region: us-east-1 - image_id: ami-04110d6a1970e748c - - region: us-east-2 - image_id: ami-0c8dc664a21d5ca08 - - region: us-west-1 - image_id: ami-0ddc462d075935666 - - region: us-west-2 - image_id: ami-0e67c2546e54fed06 - - region: eu-central-1 - image_id: ami-06a2a1e7da947b192 - - region: cn-north-1 - image_id: ami-0b3755339496a3158 - - region: cn-northwest-1 - image_id: ami-06fc0f74b500d2d82 -``` - -
- -
- -
-AZURE - Microsoft Azure - -#### AZURE - Microsoft Azure - -
-amd64 - -##### amd64 - -``` -- flavor: azure-gardener_prod-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/azure-gardener_prod-amd64-1877.3-75df9f40/azure-gardener_prod-amd64-1877.3-75df9f40.vhd - gallery_images: - - hyper_v_generation: V1 - azure_cloud: public - image_id: /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme/Versions/1877.3.0 - - hyper_v_generation: V2 - azure_cloud: public - image_id: /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-gen2/Versions/1877.3.0 - - hyper_v_generation: V1 - azure_cloud: china - image_id: /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme/Versions/1877.3.0 - - hyper_v_generation: V2 - azure_cloud: china - image_id: /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-gen2/Versions/1877.3.0 -``` - -
- -
-arm64 - -##### arm64 - -``` -- flavor: azure-gardener_prod-arm64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/azure-gardener_prod-arm64-1877.3-75df9f40/azure-gardener_prod-arm64-1877.3-75df9f40.vhd - gallery_images: - - hyper_v_generation: V2 - azure_cloud: public - image_id: /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-arm64-gen2/Versions/1877.3.0 - - hyper_v_generation: V2 - azure_cloud: china - image_id: /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-arm64-gen2/Versions/1877.3.0 -``` - -
- -
- -
-GCP - Google Cloud Platform - -#### GCP - Google Cloud Platform - -
-amd64 - -##### amd64 - -``` -- flavor: gcp-gardener_prod-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/gcp-gardener_prod-amd64-1877.3-75df9f40/gcp-gardener_prod-amd64-1877.3-75df9f40.gcpimage.tar.gz - image_name: gardenlinux-gcp-ff804026cbe7b5f2d6f729e4-1877-3-75df9f40 - project: sap-se-gcp-gardenlinux - availability: Global (all regions) -``` - -
- -
-arm64 - -##### arm64 - -``` -- flavor: gcp-gardener_prod-arm64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/gcp-gardener_prod-arm64-1877.3-75df9f40/gcp-gardener_prod-arm64-1877.3-75df9f40.gcpimage.tar.gz - image_name: gardenlinux-gcp-c8504d3c3e67cf2fc7c3408c-1877-3-75df9f40 - project: sap-se-gcp-gardenlinux - availability: Global (all regions) -``` - -
- -
- -
-OPENSTACK - OpenStack - -#### OPENSTACK - OpenStack - -
-amd64 - -##### amd64 - -``` -- flavor: openstack-gardener_prod-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/openstack-gardener_prod-amd64-1877.3-75df9f40/openstack-gardener_prod-amd64-1877.3-75df9f40.raw - regions: - - region: eu-de-1 - image_id: ed3b4c3d-941f-456a-a551-bd52b8397443 - image_name: gardenlinux-1877.3 - - region: eu-de-2 - image_id: 5ea6fb4f-20fc-43b8-8ffe-af8da6d61d6a - image_name: gardenlinux-1877.3 - - region: eu-nl-1 - image_id: ac9b5d43-ff53-494d-8adf-2249c324a9db - image_name: gardenlinux-1877.3 - - region: la-br-1 - image_id: 404f22a3-9822-4696-a60f-8566eedb93e3 - image_name: gardenlinux-1877.3 - - region: na-ca-1 - image_id: b69b72f3-574a-4f76-b4eb-ac9185ea2681 - image_name: gardenlinux-1877.3 - - region: na-us-1 - image_id: 40e99366-f13b-402a-a264-e7e4773ab8ba - image_name: gardenlinux-1877.3 - - region: na-us-2 - image_id: c50200c6-95fd-4a97-bef2-90b2d6afa3d3 - image_name: gardenlinux-1877.3 - - region: na-us-3 - image_id: d5b1d8c0-3420-4a82-931d-0506a6b8f166 - image_name: gardenlinux-1877.3 - - region: ap-ae-1 - image_id: 81c26cb7-c515-4610-949a-92c275640325 - image_name: gardenlinux-1877.3 - - region: ap-au-1 - image_id: 2d6e3edd-5596-41e6-a640-4b1b8e7310e7 - image_name: gardenlinux-1877.3 - - region: ap-cn-1 - image_id: 3564b5ef-9b37-4926-bb23-5655cf90de69 - image_name: gardenlinux-1877.3 - - region: ap-jp-1 - image_id: 2ff61187-f004-4317-bd4c-a17d93b475bc - image_name: gardenlinux-1877.3 - - region: ap-jp-2 - image_id: 2bc58951-9bf7-445b-a6e4-f634c7522d9b - image_name: gardenlinux-1877.3 - - region: ap-sa-1 - image_id: e4a4aa92-335a-454b-83bb-643cb918cf6a - image_name: gardenlinux-1877.3 - - region: ap-sa-2 - image_id: d3ac5df8-ce38-4a23-b611-dfef6b7a0db9 - image_name: gardenlinux-1877.3 -``` - -
- -
- -
-OPENSTACKBAREMETAL - OpenStack Baremetal - -#### OPENSTACKBAREMETAL - OpenStack Baremetal - -
-amd64 - -##### amd64 - -``` -- flavor: openstackbaremetal-gardener_prod-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/openstackbaremetal-gardener_prod-amd64-1877.3-75df9f40/openstackbaremetal-gardener_prod-amd64-1877.3-75df9f40.raw - regions: - - region: eu-de-1 - image_id: 01c3ab26-5b93-4655-a743-1fef60f64b53 - image_name: gardenlinux-1877.3-baremetal - - region: eu-de-2 - image_id: 7488d07b-65f1-4b85-8df8-13244b895d71 - image_name: gardenlinux-1877.3-baremetal - - region: eu-nl-1 - image_id: 1926a818-55d5-49e1-9af8-eab8450705eb - image_name: gardenlinux-1877.3-baremetal - - region: la-br-1 - image_id: 6fda686d-d2f7-4018-ab4c-1250e898197a - image_name: gardenlinux-1877.3-baremetal - - region: na-ca-1 - image_id: a032ecc1-3bee-4d65-9f68-3e3f99e2c291 - image_name: gardenlinux-1877.3-baremetal - - region: na-us-1 - image_id: d663d5f1-1b44-41af-9039-e36cc64a5920 - image_name: gardenlinux-1877.3-baremetal - - region: na-us-2 - image_id: 818bbfdd-4ee4-49ee-8294-dc3a3c66971f - image_name: gardenlinux-1877.3-baremetal - - region: na-us-3 - image_id: b154b48b-050f-48d6-997f-b6c2756079a6 - image_name: gardenlinux-1877.3-baremetal - - region: ap-ae-1 - image_id: 5992e19c-2ca2-47be-ae55-50e2fd26b662 - image_name: gardenlinux-1877.3-baremetal - - region: ap-au-1 - image_id: 986403a6-e254-4689-8f81-e32dc33c9b64 - image_name: gardenlinux-1877.3-baremetal - - region: ap-cn-1 - image_id: 0c794890-a690-4881-b0c2-39a939b020e2 - image_name: gardenlinux-1877.3-baremetal - - region: ap-jp-1 - image_id: f5be2c30-8e8e-4713-9e34-eb0a18922af5 - image_name: gardenlinux-1877.3-baremetal - - region: ap-jp-2 - image_id: 8edb20a7-f0f2-47f2-9112-faa2569c3893 - image_name: gardenlinux-1877.3-baremetal - - region: ap-sa-1 - image_id: dc12514b-b0a8-40dd-b756-a4d27421029c - image_name: gardenlinux-1877.3-baremetal - - region: ap-sa-2 - image_id: 617f5ae7-91fd-4149-b783-7a3701a5f420 - image_name: gardenlinux-1877.3-baremetal -``` - -
- -
- -
- -
-Variant - USI (Unified System Image) - -### Variant - USI (Unified System Image) - -
-AWS - Amazon Web Services - -#### AWS - Amazon Web Services - -
-amd64 - -##### amd64 - -``` -- flavor: aws-gardener_prod_usi-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/aws-gardener_prod_usi-amd64-1877.3-75df9f40/aws-gardener_prod_usi-amd64-1877.3-75df9f40.raw - regions: - - region: ap-south-1 - image_id: ami-0e904b4c264dbe923 - - region: eu-north-1 - image_id: ami-016f506d46abb5c06 - - region: eu-west-3 - image_id: ami-052404d8a97e9ec57 - - region: eu-south-1 - image_id: ami-002193c185fb2d939 - - region: eu-west-2 - image_id: ami-0225bc62c7d291107 - - region: eu-west-1 - image_id: ami-0f737c34ae9ccfe10 - - region: ap-northeast-3 - image_id: ami-0ecf19a78a7259c02 - - region: ap-northeast-2 - image_id: ami-029152bf0a15cf306 - - region: ap-northeast-1 - image_id: ami-0bf003b58ed636124 - - region: me-central-1 - image_id: ami-0546ca7d7c2e00077 - - region: ca-central-1 - image_id: ami-0a0081cbd4b479d33 - - region: sa-east-1 - image_id: ami-086d3b7282338bcd1 - - region: ap-southeast-1 - image_id: ami-04973efd023e5883f - - region: ap-southeast-2 - image_id: ami-00389783d0b7ef01b - - region: us-east-1 - image_id: ami-0f5c28bbc45608e9b - - region: us-east-2 - image_id: ami-08c7494a2a00b74e5 - - region: us-west-1 - image_id: ami-0e2290963849dba62 - - region: us-west-2 - image_id: ami-0fb86d519a38da40f - - region: eu-central-1 - image_id: ami-0c6394e4fdbefe8c0 - - region: cn-north-1 - image_id: ami-0b4c979b27a0a7714 - - region: cn-northwest-1 - image_id: ami-0cab977e76e274599 -``` - -
- -
-arm64 - -##### arm64 - -``` -- flavor: aws-gardener_prod_usi-arm64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/aws-gardener_prod_usi-arm64-1877.3-75df9f40/aws-gardener_prod_usi-arm64-1877.3-75df9f40.raw - regions: - - region: ap-south-1 - image_id: ami-029f2b705d69f9d50 - - region: eu-north-1 - image_id: ami-0b1a9e403ea563206 - - region: eu-west-3 - image_id: ami-067465814788be84a - - region: eu-south-1 - image_id: ami-092d7cf152ef6df29 - - region: eu-west-2 - image_id: ami-0441298c8ae55a62b - - region: eu-west-1 - image_id: ami-012e58abe02f904c1 - - region: ap-northeast-3 - image_id: ami-08c18c5f1aa7e9fba - - region: ap-northeast-2 - image_id: ami-0277ca365657bd9c2 - - region: ap-northeast-1 - image_id: ami-006a3f35202f6edd4 - - region: me-central-1 - image_id: ami-0aa9e8af8c777e400 - - region: ca-central-1 - image_id: ami-0f8225fd2d6009961 - - region: sa-east-1 - image_id: ami-0e945c537aef91eff - - region: ap-southeast-1 - image_id: ami-0f30b29a4428f7cea - - region: ap-southeast-2 - image_id: ami-0129e3a207e3e6f9d - - region: us-east-1 - image_id: ami-0cc9f69e3a7594e7b - - region: us-east-2 - image_id: ami-046243dad95d56f2a - - region: us-west-1 - image_id: ami-03ae03953c81a43c1 - - region: us-west-2 - image_id: ami-0cbe1dbfeda64dc9b - - region: eu-central-1 - image_id: ami-0dd2780bfcddbda6b - - region: cn-north-1 - image_id: ami-0d993477d25affb3c - - region: cn-northwest-1 - image_id: ami-0a7fe5959bb23fab8 -``` - -
- -
- -
-AZURE - Microsoft Azure - -#### AZURE - Microsoft Azure - -
-amd64 - -##### amd64 - -``` -- flavor: azure-gardener_prod_usi-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/azure-gardener_prod_usi-amd64-1877.3-75df9f40/azure-gardener_prod_usi-amd64-1877.3-75df9f40.vhd - gallery_images: - - hyper_v_generation: V2 - azure_cloud: public - image_id: /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-gen2-usi/Versions/1877.3.0 - - hyper_v_generation: V2 - azure_cloud: china - image_id: /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-gen2-usi/Versions/1877.3.0 -``` - -
- -
-arm64 - -##### arm64 - -``` -- flavor: azure-gardener_prod_usi-arm64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/azure-gardener_prod_usi-arm64-1877.3-75df9f40/azure-gardener_prod_usi-arm64-1877.3-75df9f40.vhd - gallery_images: - - hyper_v_generation: V2 - azure_cloud: public - image_id: /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-arm64-gen2-usi/Versions/1877.3.0 - - hyper_v_generation: V2 - azure_cloud: china - image_id: /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-arm64-gen2-usi/Versions/1877.3.0 -``` - -
- -
- -
-GCP - Google Cloud Platform - -#### GCP - Google Cloud Platform - -
-amd64 - -##### amd64 - -``` -- flavor: gcp-gardener_prod_usi-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/gcp-gardener_prod_usi-amd64-1877.3-75df9f40/gcp-gardener_prod_usi-amd64-1877.3-75df9f40.gcpimage.tar.gz - image_name: gardenlinux-gcp-51db8a4be084c3b640095f4b-1877-3-75df9f40 - project: sap-se-gcp-gardenlinux - availability: Global (all regions) -``` - -
- -
-arm64 - -##### arm64 - -``` -- flavor: gcp-gardener_prod_usi-arm64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/gcp-gardener_prod_usi-arm64-1877.3-75df9f40/gcp-gardener_prod_usi-arm64-1877.3-75df9f40.gcpimage.tar.gz - image_name: gardenlinux-gcp-c00f1e20ffeed4d8b80a76b9-1877-3-75df9f40 - project: sap-se-gcp-gardenlinux - availability: Global (all regions) -``` - -
- -
- -
-OPENSTACK - OpenStack - -#### OPENSTACK - OpenStack - -
-amd64 - -##### amd64 - -``` -- flavor: openstack-gardener_prod_usi-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/openstack-gardener_prod_usi-amd64-1877.3-75df9f40/openstack-gardener_prod_usi-amd64-1877.3-75df9f40.raw - regions: - - region: eu-de-1 - image_id: 15fc38b3-1cee-4c0a-829a-ef1f7faa1920 - image_name: gardenlinux-1877.3 - - region: eu-de-2 - image_id: c4e8e8e5-8c92-4c73-b21b-333087e7b092 - image_name: gardenlinux-1877.3 - - region: eu-nl-1 - image_id: e6f9e054-0613-4204-98c7-84676680418a - image_name: gardenlinux-1877.3 - - region: la-br-1 - image_id: 04416634-2eaf-44a1-a653-b1ae36bf0e0e - image_name: gardenlinux-1877.3 - - region: na-ca-1 - image_id: b548d8fd-0e6b-4cb6-9cd1-68b258df00cc - image_name: gardenlinux-1877.3 - - region: na-us-1 - image_id: 0a97e9af-a1f3-4ae4-bf44-98c432aa436c - image_name: gardenlinux-1877.3 - - region: na-us-2 - image_id: b1705d73-3f67-427c-8ade-5e245a857338 - image_name: gardenlinux-1877.3 - - region: na-us-3 - image_id: da3234f1-307c-431e-80bb-9e51dd75673d - image_name: gardenlinux-1877.3 - - region: ap-ae-1 - image_id: 16f24b39-b9ba-4756-8dcd-82473182f1e4 - image_name: gardenlinux-1877.3 - - region: ap-au-1 - image_id: 49de0ff1-2c7e-439d-a065-07c837fe48a8 - image_name: gardenlinux-1877.3 - - region: ap-cn-1 - image_id: 23a94a40-1e9a-4f4b-b2b6-4c167493fbb0 - image_name: gardenlinux-1877.3 - - region: ap-jp-1 - image_id: 1558417d-14bb-413e-9194-88b2bc5f18aa - image_name: gardenlinux-1877.3 - - region: ap-jp-2 - image_id: 8d39ad55-2f09-490e-8fa7-0bdf5c854ed7 - image_name: gardenlinux-1877.3 - - region: ap-sa-1 - image_id: 62be0147-062a-4375-b142-278a811e9754 - image_name: gardenlinux-1877.3 - - region: ap-sa-2 - image_id: 510d1ff1-4fc6-49ec-ad2f-a0985217dd14 - image_name: gardenlinux-1877.3 -``` - -
- -
- -
- -
-Variant - TPM2 Trusted Boot - -### Variant - TPM2 Trusted Boot - -
-AWS - Amazon Web Services - -#### AWS - Amazon Web Services - -
-amd64 - -##### amd64 - -``` -- flavor: aws-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/aws-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40/aws-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.raw - regions: - - region: ap-south-1 - image_id: ami-0052561d7bccfe6b7 - - region: eu-north-1 - image_id: ami-06623180935c63669 - - region: eu-west-3 - image_id: ami-026632e35fe37f9f4 - - region: eu-south-1 - image_id: ami-0b60116fac38c2556 - - region: eu-west-2 - image_id: ami-0ecd844859adf35c5 - - region: eu-west-1 - image_id: ami-0313333df0acd7eb0 - - region: ap-northeast-3 - image_id: ami-04e53edbd6ce18fc6 - - region: ap-northeast-2 - image_id: ami-0ae03e19777874cef - - region: ap-northeast-1 - image_id: ami-079e68ce96cc03e78 - - region: me-central-1 - image_id: ami-01e368d192a479934 - - region: ca-central-1 - image_id: ami-02cabce931cafcf1f - - region: sa-east-1 - image_id: ami-075d5fa3b98620e15 - - region: ap-southeast-1 - image_id: ami-0a26b478c0a210190 - - region: ap-southeast-2 - image_id: ami-0f226413240aec4aa - - region: us-east-1 - image_id: ami-07dea60f619226e1b - - region: us-east-2 - image_id: ami-0e8e852987ee840c3 - - region: us-west-1 - image_id: ami-0d9314ee5a439ab29 - - region: us-west-2 - image_id: ami-04dc4614abf1649ab - - region: eu-central-1 - image_id: ami-005f7dab618420a91 -``` - -
- -
-arm64 - -##### arm64 - -``` -- flavor: aws-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/aws-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40/aws-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.raw - regions: - - region: ap-south-1 - image_id: ami-035b751a08f528e47 - - region: eu-north-1 - image_id: ami-04c60d1feb092d00f - - region: eu-west-3 - image_id: ami-0443172a73fe4fb27 - - region: eu-south-1 - image_id: ami-02f8a867d02227542 - - region: eu-west-2 - image_id: ami-06a900dd59c84620d - - region: eu-west-1 - image_id: ami-056a9d8447a991bff - - region: ap-northeast-3 - image_id: ami-0769caf50f7b7fb6f - - region: ap-northeast-2 - image_id: ami-06ad8c60e1093b543 - - region: ap-northeast-1 - image_id: ami-0b8313d62dfeec78b - - region: me-central-1 - image_id: ami-0fa388dcaca7b3baf - - region: ca-central-1 - image_id: ami-02e7a07f60a5e0411 - - region: sa-east-1 - image_id: ami-000ca39b22f2a695c - - region: ap-southeast-1 - image_id: ami-04f521cff21b58f50 - - region: ap-southeast-2 - image_id: ami-02f5afcce42276457 - - region: us-east-1 - image_id: ami-0a25256d5aaf8fdd7 - - region: us-east-2 - image_id: ami-07bcfed39a329b612 - - region: us-west-1 - image_id: ami-0b2e93f36b5a8bff2 - - region: us-west-2 - image_id: ami-063f4f34958917b5c - - region: eu-central-1 - image_id: ami-0b15b442dd5e90d50 -``` - -
- -
- -
-AZURE - Microsoft Azure - -#### AZURE - Microsoft Azure - -
-amd64 - -##### amd64 - -``` -- flavor: azure-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/azure-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40/azure-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.vhd - gallery_images: - - hyper_v_generation: V2 - azure_cloud: public - image_id: /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-gen2-usi-secureboot/Versions/1877.3.0 - - hyper_v_generation: V2 - azure_cloud: china - image_id: /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-gen2-usi-secureboot/Versions/1877.3.0 -``` - -
- -
-arm64 - -##### arm64 - -``` -- flavor: azure-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/azure-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40/azure-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.vhd - gallery_images: - - hyper_v_generation: V2 - azure_cloud: public - image_id: /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-arm64-gen2-usi-secureboot/Versions/1877.3.0 - - hyper_v_generation: V2 - azure_cloud: china - image_id: /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-arm64-gen2-usi-secureboot/Versions/1877.3.0 -``` - -
- -
- -
-GCP - Google Cloud Platform - -#### GCP - Google Cloud Platform - -
-amd64 - -##### amd64 - -``` -- flavor: gcp-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/gcp-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40/gcp-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.gcpimage.tar.gz - image_name: gardenlinux-gcp-b4636aa3660a8d166531aab9-1877-3-75df9f40 - project: sap-se-gcp-gardenlinux - availability: Global (all regions) -``` - -
- -
-arm64 - -##### arm64 - -``` -- flavor: gcp-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40 - download_url: https://gardenlinux-github-releases.s3.amazonaws.com/objects/gcp-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40/gcp-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.gcpimage.tar.gz - image_name: gardenlinux-gcp-63fd9d7dd465420fd4e499ab-1877-3-75df9f40 - project: sap-se-gcp-gardenlinux - availability: Global (all regions) -``` - -
- -
- -
- - -
+# Published images + +| Variant | Platform | Architecture | Flavor | Regions | Download Links | +|---------|----------|--------------|--------|---------|----------------| +| Default | Alibaba Cloud | amd64 | `ali-gardener_prod-amd64-1877.3-75df9f40` |
28 regions
+**cn-qingdao:** m-m5efm8l2bltkbloui235
+**cn-beijing:** m-2zee5ebi20ltzy5et7in
+**cn-zhangjiakou:** m-8vbddy2wfex9nb29afcy
+**cn-huhehaote:** m-hp3bx14og6cw9thujw1d
+**cn-wulanchabu:** m-0jlh1iq2f3bryb5okjdk
+**cn-hangzhou:** m-bp13aseh5a2wn0s5rdz6
+**cn-shanghai:** m-uf61jbe9n8a9291h4u21
+**cn-nanjing:** m-gc77bfbctuzphl2bpk0o
+**cn-shenzhen:** m-wz9gio8m5ey0foj0g4xx
+**cn-heyuan:** m-f8zdn54v0blnsafxb1t5
+**cn-guangzhou:** m-7xv0q5feffsxxyttxdy9
+**cn-fuzhou:** m-gw07bfbctuzphl2bpk0p
+**cn-wuhan-lr:** m-n4a1u2avlb9pq0u5bdms
+**cn-chengdu:** m-2vc5saul2saa2z57h216
+**cn-hongkong:** m-j6c4zk6mwb2673iq5wrz
+**ap-northeast-1:** m-6weibwo3vrt7ar7nelc9
+**ap-northeast-2:** m-mj73oldn06th2vy0ymhv
+**ap-southeast-1:** m-t4ngrf81d0fohwq493pw
+**ap-southeast-3:** m-8psd64gzc1eru0qld7cc
+**ap-southeast-6:** m-5tsdd6k3z1vvdyyio7zn
+**ap-southeast-5:** m-k1aj4usnhqcssa2fpy0c
+**ap-southeast-7:** m-0jo6uwekvn0gnwhwnq3s
+**us-east-1:** m-0xi8netpfc2fdwfstz3c
+**us-west-1:** m-rj9gwpx907qv6p6x8w45
+**na-south-1:** m-4hfi34x77oaeznwuulq6
+**eu-west-1:** m-d7o2ny5xc0m3kacxjbem
+**me-east-1:** m-eb39mgohcec6gaynet9l
+**eu-central-1:** m-gw86dlqmpaugljiykx91
|
Download
+[ali-gardener_prod-amd64-1877.3-75df9f40.qcow2](https://test__gardenlinux__releases.s3.amazonaws.com/objects/ali-gardener_prod-amd64-1877.3-75df9f40/ali-gardener_prod-amd64-1877.3-75df9f40.qcow2)
| +| Default | Amazon Web Services | amd64 | `aws-gardener_prod-amd64-1877.3-75df9f40` |
21 regions
+**ap-south-1:** ami-00c6adf1de4dd746a
+**eu-north-1:** ami-07ad3940828172b90
+**eu-west-3:** ami-071f4f48679d86638
+**eu-south-1:** ami-0b10af1a19df9f038
+**eu-west-2:** ami-0e2b7fe07573b71cd
+**eu-west-1:** ami-01c547eb85d61da61
+**ap-northeast-3:** ami-0dad917ede94cd3c7
+**ap-northeast-2:** ami-0ecbeaf40d4643016
+**ap-northeast-1:** ami-0b7225242babad11c
+**me-central-1:** ami-0d298e552bf051bc7
+**ca-central-1:** ami-0af8422162c8f056e
+**sa-east-1:** ami-05d885175e942fc80
+**ap-southeast-1:** ami-0a9802680adf7e430
+**ap-southeast-2:** ami-07ed6f1e62fbd6d66
+**us-east-1:** ami-055a0ce37433fcdee
+**us-east-2:** ami-07e9069631850755a
+**us-west-1:** ami-08c18abab76066f71
+**us-west-2:** ami-00eca0475f90a1f8c
+**eu-central-1:** ami-0198822fa7d539f8c
+**cn-north-1:** ami-093c993faaca89b4d
+**cn-northwest-1:** ami-05e1cc73d997d67b7
|
Download
+[aws-gardener_prod-amd64-1877.3-75df9f40.raw](https://test__gardenlinux__releases.s3.amazonaws.com/objects/aws-gardener_prod-amd64-1877.3-75df9f40/aws-gardener_prod-amd64-1877.3-75df9f40.raw)
| +| Default | Amazon Web Services | arm64 | `aws-gardener_prod-arm64-1877.3-75df9f40` |
21 regions
+**ap-south-1:** ami-00cd00c30d19609a2
+**eu-north-1:** ami-0a969a1a1c4726831
+**eu-west-3:** ami-0b579f6c70b7c4fe6
+**eu-south-1:** ami-06c38608e2e7223d3
+**eu-west-2:** ami-005c7058c3923b2eb
+**eu-west-1:** ami-0395c3cd38a0a5cd6
+**ap-northeast-3:** ami-0db3697cea87a5104
+**ap-northeast-2:** ami-084444f62c7c580fb
+**ap-northeast-1:** ami-017237dd9abeae8dd
+**me-central-1:** ami-08efdb3153d0cd184
+**ca-central-1:** ami-05b535ae9418fee3d
+**sa-east-1:** ami-036ded98bad763e3c
+**ap-southeast-1:** ami-03fcefb2fd18519d0
+**ap-southeast-2:** ami-040f9d0caa5d79e84
+**us-east-1:** ami-04110d6a1970e748c
+**us-east-2:** ami-0c8dc664a21d5ca08
+**us-west-1:** ami-0ddc462d075935666
+**us-west-2:** ami-0e67c2546e54fed06
+**eu-central-1:** ami-06a2a1e7da947b192
+**cn-north-1:** ami-0b3755339496a3158
+**cn-northwest-1:** ami-06fc0f74b500d2d82
|
Download
+[aws-gardener_prod-arm64-1877.3-75df9f40.raw](https://test__gardenlinux__releases.s3.amazonaws.com/objects/aws-gardener_prod-arm64-1877.3-75df9f40/aws-gardener_prod-arm64-1877.3-75df9f40.raw)
| +| Default | Microsoft Azure | amd64 | `azure-gardener_prod-amd64-1877.3-75df9f40` |
4 gallery + 0 marketplace images
+**Gallery Images:**
+**public (V1):** /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme/Versions/1877.3.0
+**public (V2):** /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-gen2/Versions/1877.3.0
+**china (V1):** /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme/Versions/1877.3.0
+**china (V2):** /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-gen2/Versions/1877.3.0
|
Download
+[azure-gardener_prod-amd64-1877.3-75df9f40.vhd](https://test__gardenlinux__releases.s3.amazonaws.com/objects/azure-gardener_prod-amd64-1877.3-75df9f40/azure-gardener_prod-amd64-1877.3-75df9f40.vhd)
| +| Default | Microsoft Azure | arm64 | `azure-gardener_prod-arm64-1877.3-75df9f40` |
2 gallery + 0 marketplace images
+**Gallery Images:**
+**public (V2):** /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-arm64-gen2/Versions/1877.3.0
+**china (V2):** /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-arm64-gen2/Versions/1877.3.0
|
Download
+[azure-gardener_prod-arm64-1877.3-75df9f40.vhd](https://test__gardenlinux__releases.s3.amazonaws.com/objects/azure-gardener_prod-arm64-1877.3-75df9f40/azure-gardener_prod-arm64-1877.3-75df9f40.vhd)
| +| Default | Google Cloud Platform | amd64 | `gcp-gardener_prod-amd64-1877.3-75df9f40` |
Global availability
+**Project:** sap-se-gcp-gardenlinux
+**Image Name:** gardenlinux-gcp-ff804026cbe7b5f2d6f729e4-1877-3-75df9f40
+**Availability:** All regions
|
Download
+[gcp-gardener_prod-amd64-1877.3-75df9f40.gcpimage.tar.gz](https://test__gardenlinux__releases.s3.amazonaws.com/objects/gcp-gardener_prod-amd64-1877.3-75df9f40/gcp-gardener_prod-amd64-1877.3-75df9f40.gcpimage.tar.gz)
| +| Default | Google Cloud Platform | arm64 | `gcp-gardener_prod-arm64-1877.3-75df9f40` |
Global availability
+**Project:** sap-se-gcp-gardenlinux
+**Image Name:** gardenlinux-gcp-c8504d3c3e67cf2fc7c3408c-1877-3-75df9f40
+**Availability:** All regions
|
Download
+[gcp-gardener_prod-arm64-1877.3-75df9f40.gcpimage.tar.gz](https://test__gardenlinux__releases.s3.amazonaws.com/objects/gcp-gardener_prod-arm64-1877.3-75df9f40/gcp-gardener_prod-arm64-1877.3-75df9f40.gcpimage.tar.gz)
| +| Default | OpenStack | amd64 | `openstack-gardener_prod-amd64-1877.3-75df9f40` |
15 regions
+**eu-de-1:** ed3b4c3d-941f-456a-a551-bd52b8397443 (gardenlinux-1877.3)
+**eu-de-2:** 5ea6fb4f-20fc-43b8-8ffe-af8da6d61d6a (gardenlinux-1877.3)
+**eu-nl-1:** ac9b5d43-ff53-494d-8adf-2249c324a9db (gardenlinux-1877.3)
+**la-br-1:** 404f22a3-9822-4696-a60f-8566eedb93e3 (gardenlinux-1877.3)
+**na-ca-1:** b69b72f3-574a-4f76-b4eb-ac9185ea2681 (gardenlinux-1877.3)
+**na-us-1:** 40e99366-f13b-402a-a264-e7e4773ab8ba (gardenlinux-1877.3)
+**na-us-2:** c50200c6-95fd-4a97-bef2-90b2d6afa3d3 (gardenlinux-1877.3)
+**na-us-3:** d5b1d8c0-3420-4a82-931d-0506a6b8f166 (gardenlinux-1877.3)
+**ap-ae-1:** 81c26cb7-c515-4610-949a-92c275640325 (gardenlinux-1877.3)
+**ap-au-1:** 2d6e3edd-5596-41e6-a640-4b1b8e7310e7 (gardenlinux-1877.3)
+**ap-cn-1:** 3564b5ef-9b37-4926-bb23-5655cf90de69 (gardenlinux-1877.3)
+**ap-jp-1:** 2ff61187-f004-4317-bd4c-a17d93b475bc (gardenlinux-1877.3)
+**ap-jp-2:** 2bc58951-9bf7-445b-a6e4-f634c7522d9b (gardenlinux-1877.3)
+**ap-sa-1:** e4a4aa92-335a-454b-83bb-643cb918cf6a (gardenlinux-1877.3)
+**ap-sa-2:** d3ac5df8-ce38-4a23-b611-dfef6b7a0db9 (gardenlinux-1877.3)
|
Download
+[openstack-gardener_prod-amd64-1877.3-75df9f40.raw](https://test__gardenlinux__releases.s3.amazonaws.com/objects/openstack-gardener_prod-amd64-1877.3-75df9f40/openstack-gardener_prod-amd64-1877.3-75df9f40.raw)
| +| Default | OpenStack Baremetal | amd64 | `openstackbaremetal-gardener_prod-amd64-1877.3-75df9f40` |
15 regions
+**eu-de-1:** 01c3ab26-5b93-4655-a743-1fef60f64b53 (gardenlinux-1877.3-baremetal)
+**eu-de-2:** 7488d07b-65f1-4b85-8df8-13244b895d71 (gardenlinux-1877.3-baremetal)
+**eu-nl-1:** 1926a818-55d5-49e1-9af8-eab8450705eb (gardenlinux-1877.3-baremetal)
+**la-br-1:** 6fda686d-d2f7-4018-ab4c-1250e898197a (gardenlinux-1877.3-baremetal)
+**na-ca-1:** a032ecc1-3bee-4d65-9f68-3e3f99e2c291 (gardenlinux-1877.3-baremetal)
+**na-us-1:** d663d5f1-1b44-41af-9039-e36cc64a5920 (gardenlinux-1877.3-baremetal)
+**na-us-2:** 818bbfdd-4ee4-49ee-8294-dc3a3c66971f (gardenlinux-1877.3-baremetal)
+**na-us-3:** b154b48b-050f-48d6-997f-b6c2756079a6 (gardenlinux-1877.3-baremetal)
+**ap-ae-1:** 5992e19c-2ca2-47be-ae55-50e2fd26b662 (gardenlinux-1877.3-baremetal)
+**ap-au-1:** 986403a6-e254-4689-8f81-e32dc33c9b64 (gardenlinux-1877.3-baremetal)
+**ap-cn-1:** 0c794890-a690-4881-b0c2-39a939b020e2 (gardenlinux-1877.3-baremetal)
+**ap-jp-1:** f5be2c30-8e8e-4713-9e34-eb0a18922af5 (gardenlinux-1877.3-baremetal)
+**ap-jp-2:** 8edb20a7-f0f2-47f2-9112-faa2569c3893 (gardenlinux-1877.3-baremetal)
+**ap-sa-1:** dc12514b-b0a8-40dd-b756-a4d27421029c (gardenlinux-1877.3-baremetal)
+**ap-sa-2:** 617f5ae7-91fd-4149-b783-7a3701a5f420 (gardenlinux-1877.3-baremetal)
|
Download
+[openstackbaremetal-gardener_prod-amd64-1877.3-75df9f40.raw](https://test__gardenlinux__releases.s3.amazonaws.com/objects/openstackbaremetal-gardener_prod-amd64-1877.3-75df9f40/openstackbaremetal-gardener_prod-amd64-1877.3-75df9f40.raw)
| +| USI (Unified System Image) | Amazon Web Services | amd64 | `aws-gardener_prod_usi-amd64-1877.3-75df9f40` |
21 regions
+**ap-south-1:** ami-0e904b4c264dbe923
+**eu-north-1:** ami-016f506d46abb5c06
+**eu-west-3:** ami-052404d8a97e9ec57
+**eu-south-1:** ami-002193c185fb2d939
+**eu-west-2:** ami-0225bc62c7d291107
+**eu-west-1:** ami-0f737c34ae9ccfe10
+**ap-northeast-3:** ami-0ecf19a78a7259c02
+**ap-northeast-2:** ami-029152bf0a15cf306
+**ap-northeast-1:** ami-0bf003b58ed636124
+**me-central-1:** ami-0546ca7d7c2e00077
+**ca-central-1:** ami-0a0081cbd4b479d33
+**sa-east-1:** ami-086d3b7282338bcd1
+**ap-southeast-1:** ami-04973efd023e5883f
+**ap-southeast-2:** ami-00389783d0b7ef01b
+**us-east-1:** ami-0f5c28bbc45608e9b
+**us-east-2:** ami-08c7494a2a00b74e5
+**us-west-1:** ami-0e2290963849dba62
+**us-west-2:** ami-0fb86d519a38da40f
+**eu-central-1:** ami-0c6394e4fdbefe8c0
+**cn-north-1:** ami-0b4c979b27a0a7714
+**cn-northwest-1:** ami-0cab977e76e274599
|
Download
+[aws-gardener_prod_usi-amd64-1877.3-75df9f40.raw](https://test__gardenlinux__releases.s3.amazonaws.com/objects/aws-gardener_prod_usi-amd64-1877.3-75df9f40/aws-gardener_prod_usi-amd64-1877.3-75df9f40.raw)
| +| USI (Unified System Image) | Amazon Web Services | arm64 | `aws-gardener_prod_usi-arm64-1877.3-75df9f40` |
21 regions
+**ap-south-1:** ami-029f2b705d69f9d50
+**eu-north-1:** ami-0b1a9e403ea563206
+**eu-west-3:** ami-067465814788be84a
+**eu-south-1:** ami-092d7cf152ef6df29
+**eu-west-2:** ami-0441298c8ae55a62b
+**eu-west-1:** ami-012e58abe02f904c1
+**ap-northeast-3:** ami-08c18c5f1aa7e9fba
+**ap-northeast-2:** ami-0277ca365657bd9c2
+**ap-northeast-1:** ami-006a3f35202f6edd4
+**me-central-1:** ami-0aa9e8af8c777e400
+**ca-central-1:** ami-0f8225fd2d6009961
+**sa-east-1:** ami-0e945c537aef91eff
+**ap-southeast-1:** ami-0f30b29a4428f7cea
+**ap-southeast-2:** ami-0129e3a207e3e6f9d
+**us-east-1:** ami-0cc9f69e3a7594e7b
+**us-east-2:** ami-046243dad95d56f2a
+**us-west-1:** ami-03ae03953c81a43c1
+**us-west-2:** ami-0cbe1dbfeda64dc9b
+**eu-central-1:** ami-0dd2780bfcddbda6b
+**cn-north-1:** ami-0d993477d25affb3c
+**cn-northwest-1:** ami-0a7fe5959bb23fab8
|
Download
+[aws-gardener_prod_usi-arm64-1877.3-75df9f40.raw](https://test__gardenlinux__releases.s3.amazonaws.com/objects/aws-gardener_prod_usi-arm64-1877.3-75df9f40/aws-gardener_prod_usi-arm64-1877.3-75df9f40.raw)
| +| USI (Unified System Image) | Microsoft Azure | amd64 | `azure-gardener_prod_usi-amd64-1877.3-75df9f40` |
2 gallery + 0 marketplace images
+**Gallery Images:**
+**public (V2):** /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-gen2-usi/Versions/1877.3.0
+**china (V2):** /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-gen2-usi/Versions/1877.3.0
|
Download
+[azure-gardener_prod_usi-amd64-1877.3-75df9f40.vhd](https://test__gardenlinux__releases.s3.amazonaws.com/objects/azure-gardener_prod_usi-amd64-1877.3-75df9f40/azure-gardener_prod_usi-amd64-1877.3-75df9f40.vhd)
| +| USI (Unified System Image) | Microsoft Azure | arm64 | `azure-gardener_prod_usi-arm64-1877.3-75df9f40` |
2 gallery + 0 marketplace images
+**Gallery Images:**
+**public (V2):** /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-arm64-gen2-usi/Versions/1877.3.0
+**china (V2):** /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-arm64-gen2-usi/Versions/1877.3.0
|
Download
+[azure-gardener_prod_usi-arm64-1877.3-75df9f40.vhd](https://test__gardenlinux__releases.s3.amazonaws.com/objects/azure-gardener_prod_usi-arm64-1877.3-75df9f40/azure-gardener_prod_usi-arm64-1877.3-75df9f40.vhd)
| +| USI (Unified System Image) | Google Cloud Platform | amd64 | `gcp-gardener_prod_usi-amd64-1877.3-75df9f40` |
Global availability
+**Project:** sap-se-gcp-gardenlinux
+**Image Name:** gardenlinux-gcp-51db8a4be084c3b640095f4b-1877-3-75df9f40
+**Availability:** All regions
|
Download
+[gcp-gardener_prod_usi-amd64-1877.3-75df9f40.gcpimage.tar.gz](https://test__gardenlinux__releases.s3.amazonaws.com/objects/gcp-gardener_prod_usi-amd64-1877.3-75df9f40/gcp-gardener_prod_usi-amd64-1877.3-75df9f40.gcpimage.tar.gz)
| +| USI (Unified System Image) | Google Cloud Platform | arm64 | `gcp-gardener_prod_usi-arm64-1877.3-75df9f40` |
Global availability
+**Project:** sap-se-gcp-gardenlinux
+**Image Name:** gardenlinux-gcp-c00f1e20ffeed4d8b80a76b9-1877-3-75df9f40
+**Availability:** All regions
|
Download
+[gcp-gardener_prod_usi-arm64-1877.3-75df9f40.gcpimage.tar.gz](https://test__gardenlinux__releases.s3.amazonaws.com/objects/gcp-gardener_prod_usi-arm64-1877.3-75df9f40/gcp-gardener_prod_usi-arm64-1877.3-75df9f40.gcpimage.tar.gz)
| +| USI (Unified System Image) | OpenStack | amd64 | `openstack-gardener_prod_usi-amd64-1877.3-75df9f40` |
15 regions
+**eu-de-1:** 15fc38b3-1cee-4c0a-829a-ef1f7faa1920 (gardenlinux-1877.3)
+**eu-de-2:** c4e8e8e5-8c92-4c73-b21b-333087e7b092 (gardenlinux-1877.3)
+**eu-nl-1:** e6f9e054-0613-4204-98c7-84676680418a (gardenlinux-1877.3)
+**la-br-1:** 04416634-2eaf-44a1-a653-b1ae36bf0e0e (gardenlinux-1877.3)
+**na-ca-1:** b548d8fd-0e6b-4cb6-9cd1-68b258df00cc (gardenlinux-1877.3)
+**na-us-1:** 0a97e9af-a1f3-4ae4-bf44-98c432aa436c (gardenlinux-1877.3)
+**na-us-2:** b1705d73-3f67-427c-8ade-5e245a857338 (gardenlinux-1877.3)
+**na-us-3:** da3234f1-307c-431e-80bb-9e51dd75673d (gardenlinux-1877.3)
+**ap-ae-1:** 16f24b39-b9ba-4756-8dcd-82473182f1e4 (gardenlinux-1877.3)
+**ap-au-1:** 49de0ff1-2c7e-439d-a065-07c837fe48a8 (gardenlinux-1877.3)
+**ap-cn-1:** 23a94a40-1e9a-4f4b-b2b6-4c167493fbb0 (gardenlinux-1877.3)
+**ap-jp-1:** 1558417d-14bb-413e-9194-88b2bc5f18aa (gardenlinux-1877.3)
+**ap-jp-2:** 8d39ad55-2f09-490e-8fa7-0bdf5c854ed7 (gardenlinux-1877.3)
+**ap-sa-1:** 62be0147-062a-4375-b142-278a811e9754 (gardenlinux-1877.3)
+**ap-sa-2:** 510d1ff1-4fc6-49ec-ad2f-a0985217dd14 (gardenlinux-1877.3)
|
Download
+[openstack-gardener_prod_usi-amd64-1877.3-75df9f40.raw](https://test__gardenlinux__releases.s3.amazonaws.com/objects/openstack-gardener_prod_usi-amd64-1877.3-75df9f40/openstack-gardener_prod_usi-amd64-1877.3-75df9f40.raw)
| +| TPM2 Trusted Boot | Amazon Web Services | amd64 | `aws-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40` |
19 regions
+**ap-south-1:** ami-0052561d7bccfe6b7
+**eu-north-1:** ami-06623180935c63669
+**eu-west-3:** ami-026632e35fe37f9f4
+**eu-south-1:** ami-0b60116fac38c2556
+**eu-west-2:** ami-0ecd844859adf35c5
+**eu-west-1:** ami-0313333df0acd7eb0
+**ap-northeast-3:** ami-04e53edbd6ce18fc6
+**ap-northeast-2:** ami-0ae03e19777874cef
+**ap-northeast-1:** ami-079e68ce96cc03e78
+**me-central-1:** ami-01e368d192a479934
+**ca-central-1:** ami-02cabce931cafcf1f
+**sa-east-1:** ami-075d5fa3b98620e15
+**ap-southeast-1:** ami-0a26b478c0a210190
+**ap-southeast-2:** ami-0f226413240aec4aa
+**us-east-1:** ami-07dea60f619226e1b
+**us-east-2:** ami-0e8e852987ee840c3
+**us-west-1:** ami-0d9314ee5a439ab29
+**us-west-2:** ami-04dc4614abf1649ab
+**eu-central-1:** ami-005f7dab618420a91
|
Download
+[aws-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.raw](https://test__gardenlinux__releases.s3.amazonaws.com/objects/aws-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40/aws-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.raw)
| +| TPM2 Trusted Boot | Amazon Web Services | arm64 | `aws-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40` |
19 regions
+**ap-south-1:** ami-035b751a08f528e47
+**eu-north-1:** ami-04c60d1feb092d00f
+**eu-west-3:** ami-0443172a73fe4fb27
+**eu-south-1:** ami-02f8a867d02227542
+**eu-west-2:** ami-06a900dd59c84620d
+**eu-west-1:** ami-056a9d8447a991bff
+**ap-northeast-3:** ami-0769caf50f7b7fb6f
+**ap-northeast-2:** ami-06ad8c60e1093b543
+**ap-northeast-1:** ami-0b8313d62dfeec78b
+**me-central-1:** ami-0fa388dcaca7b3baf
+**ca-central-1:** ami-02e7a07f60a5e0411
+**sa-east-1:** ami-000ca39b22f2a695c
+**ap-southeast-1:** ami-04f521cff21b58f50
+**ap-southeast-2:** ami-02f5afcce42276457
+**us-east-1:** ami-0a25256d5aaf8fdd7
+**us-east-2:** ami-07bcfed39a329b612
+**us-west-1:** ami-0b2e93f36b5a8bff2
+**us-west-2:** ami-063f4f34958917b5c
+**eu-central-1:** ami-0b15b442dd5e90d50
|
Download
+[aws-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.raw](https://test__gardenlinux__releases.s3.amazonaws.com/objects/aws-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40/aws-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.raw)
| +| TPM2 Trusted Boot | Microsoft Azure | amd64 | `azure-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40` |
2 gallery + 0 marketplace images
+**Gallery Images:**
+**public (V2):** /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-gen2-usi-secureboot/Versions/1877.3.0
+**china (V2):** /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-gen2-usi-secureboot/Versions/1877.3.0
|
Download
+[azure-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.vhd](https://test__gardenlinux__releases.s3.amazonaws.com/objects/azure-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40/azure-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.vhd)
| +| TPM2 Trusted Boot | Microsoft Azure | arm64 | `azure-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40` |
2 gallery + 0 marketplace images
+**Gallery Images:**
+**public (V2):** /CommunityGalleries/gardenlinux-13e998fe-534d-4b0a-8a27-f16a73aef620/Images/gardenlinux-nvme-arm64-gen2-usi-secureboot/Versions/1877.3.0
+**china (V2):** /CommunityGalleries/gardenlinux-8e6518fb-9ae0-4f66-abfd-9a06997e2492/Images/gardenlinux-nvme-arm64-gen2-usi-secureboot/Versions/1877.3.0
|
Download
+[azure-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.vhd](https://test__gardenlinux__releases.s3.amazonaws.com/objects/azure-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40/azure-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.vhd)
| +| TPM2 Trusted Boot | Google Cloud Platform | amd64 | `gcp-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40` |
Global availability
+**Project:** sap-se-gcp-gardenlinux
+**Image Name:** gardenlinux-gcp-b4636aa3660a8d166531aab9-1877-3-75df9f40
+**Availability:** All regions
|
Download
+[gcp-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.gcpimage.tar.gz](https://test__gardenlinux__releases.s3.amazonaws.com/objects/gcp-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40/gcp-gardener_prod_tpm2_trustedboot-amd64-1877.3-75df9f40.gcpimage.tar.gz)
| +| TPM2 Trusted Boot | Google Cloud Platform | arm64 | `gcp-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40` |
Global availability
+**Project:** sap-se-gcp-gardenlinux
+**Image Name:** gardenlinux-gcp-63fd9d7dd465420fd4e499ab-1877-3-75df9f40
+**Availability:** All regions
|
Download
+[gcp-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.gcpimage.tar.gz](https://test__gardenlinux__releases.s3.amazonaws.com/objects/gcp-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40/gcp-gardener_prod_tpm2_trustedboot-arm64-1877.3-75df9f40.gcpimage.tar.gz)
| ## Kernel Module Build Container (kmodbuild) + ``` ghcr.io/gardenlinux/gardenlinux/kmodbuild:1877.3 ``` diff --git a/tests/distro_version/test_distro_version.py b/tests/distro_version/test_distro_version.py index 8daee9c8..d96d26a8 100644 --- a/tests/distro_version/test_distro_version.py +++ b/tests/distro_version/test_distro_version.py @@ -2,9 +2,7 @@ from gardenlinux.distro_version import ( DistroVersion, - LegacyDistroVersion, NotAPatchRelease, - SemverDistroVersion, UnsupportedDistroVersion, ) @@ -27,24 +25,24 @@ def test_distro_version_unrecognizable_too_short_version() -> None: def test_distro_version_legacy_version_is_parsable() -> None: - assert isinstance(DistroVersion("1.2"), LegacyDistroVersion) + assert isinstance(DistroVersion("1.2"), DistroVersion) def test_distro_version_semver_version_is_parsable() -> None: - assert isinstance(DistroVersion("1.2.3"), SemverDistroVersion) + assert isinstance(DistroVersion("1.2.3"), DistroVersion) def test_distro_version_patch_release_is_recognized() -> None: - assert DistroVersion("1.1").is_patch_release() - assert DistroVersion("1.1.100").is_patch_release() - assert not DistroVersion("1.0").is_patch_release() - assert not DistroVersion("1.0.0").is_patch_release() + assert DistroVersion("1.1").is_patch_release + assert DistroVersion("1.1.100").is_patch_release + assert not DistroVersion("1.0").is_patch_release + assert not DistroVersion("1.0.0").is_patch_release def test_distro_version_previous_patch_release_is_recognized() -> None: - assert DistroVersion("1.1").previous_patch_release().__str__() == "1.0" - assert DistroVersion("1.1.100").previous_patch_release().__str__() == "1.1.99" + assert DistroVersion("1.1").previous_patch_release.__str__() == "1.0" + assert DistroVersion("1.1.100").previous_patch_release.__str__() == "1.1.99" with pytest.raises(NotAPatchRelease): - DistroVersion("1.0").previous_patch_release() + DistroVersion("1.0").previous_patch_release with pytest.raises(NotAPatchRelease): - DistroVersion("1.100.0").previous_patch_release() + DistroVersion("1.100.0").previous_patch_release diff --git a/tests/github/conftest.py b/tests/github/conftest.py index 114adcfc..ac61fee5 100644 --- a/tests/github/conftest.py +++ b/tests/github/conftest.py @@ -7,7 +7,7 @@ import pytest from moto import mock_aws -from gardenlinux.constants import RELEASE_ID_FILE, S3_DOWNLOADS_DIR +from gardenlinux.constants import S3_DOWNLOADS_DIR from ..constants import TEST_GARDENLINUX_RELEASE_BUCKET_NAME @@ -29,18 +29,11 @@ def github_token() -> Generator[None, None, None]: @pytest.fixture def artifact_for_upload(downloads_dir: None) -> Generator[Path, None, None]: artifact = S3_DOWNLOADS_DIR / "artifact.log" - artifact.touch() + artifact.write_text("Everything is fine so far") yield artifact artifact.unlink() -@pytest.fixture -def release_id_file() -> Generator[Path, None, None]: - f = Path(RELEASE_ID_FILE) - yield f - f.unlink() - - @pytest.fixture def release_s3_bucket() -> Generator[Any, None, None]: with mock_aws(): diff --git a/tests/github/constants.py b/tests/github/constants.py index 91ea3419..a562bf8f 100644 --- a/tests/github/constants.py +++ b/tests/github/constants.py @@ -1,3 +1,28 @@ +from ..constants import TEST_COMMIT, TEST_GARDENLINUX_RELEASE + +RELEASE_JSON = { + "url": "https://api.github.com/repos/gardenlinux/gardenlinux/releases/1", + "html_url": f"https://github.com/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE}", + "assets_url": "https://api.github.com/repos/gardenlinux/gardenlinux/releases/1/assets", + "upload_url": "https://uploads.github.com/repos/gardenlinux/gardenlinux/releases/1/assets{?name,label}", + "tarball_url": "https://api.github.com/repos/gardenlinux/gardenlinux/tarball/{TEST_GARDENLINUX_RELEASE}", + "zipball_url": "https://api.github.com/repos/gardenlinux/gardenlinux/zipball/{TEST_GARDENLINUX_RELEASE}", + "discussion_url": "https://github.com/gardenlinux/gardenlinux/discussions/1", + "id": 1, + "node_id": "MDc6UmVsZWFzZTE=", + "tag_name": TEST_GARDENLINUX_RELEASE, + "target_commitish": TEST_COMMIT, + "name": TEST_GARDENLINUX_RELEASE, + "body": "Happily copied from REST API endpoints for releases @ github.com", + "draft": False, + "prerelease": False, + "immutable": False, + "created_at": "2013-02-27T19:35:32Z", + "published_at": "2013-02-27T19:35:32Z", + "author": {}, + "assets": [], +} + REPO_JSON = { "id": 1, "node_id": "test", diff --git a/tests/github/test_create_github_release_notes.py b/tests/github/test_create_github_release_notes.py deleted file mode 100644 index 7c341270..00000000 --- a/tests/github/test_create_github_release_notes.py +++ /dev/null @@ -1,222 +0,0 @@ -import pytest -import requests_mock -from git import Repo -from moto import mock_aws - -from gardenlinux.constants import GLVD_BASE_URL -from gardenlinux.distro_version import UnsupportedDistroVersion -from gardenlinux.github.release_notes import release_notes_changes_section # type: ignore[attr-defined] -from gardenlinux.github.release_notes import ( # type: ignore[attr-defined] - release_notes_compare_package_versions_section, -) -from gardenlinux.github.release_notes.deployment_platform import DeploymentPlatform -from gardenlinux.github.release_notes.helpers import get_variant_from_flavor -from gardenlinux.s3.bucket import Bucket - -from ..constants import ( - RELEASE_ARTIFACTS_METADATA_FILES, - RELEASE_NOTES_S3_ARTIFACTS_DIR, - RELEASE_NOTES_TEST_DATA_DIR, - TEST_GARDENLINUX_COMMIT, - TEST_GARDENLINUX_RELEASE, -) - -TEST_FLAVORS = [ - ("foo_bar_baz", "legacy"), - ("aws-gardener_prod_trustedboot_tpm2-amd64", "legacy"), - ("openstack-gardener_prod_tpm2_trustedboot-arm64", "tpm2_trustedboot"), - ("azure-gardener_prod_usi-amd64", "usi"), - ("", "legacy"), -] - - -def test_release_notes_changes_section_empty_packagelist() -> None: - with requests_mock.Mocker() as m: - m.get( - f"{GLVD_BASE_URL}/releaseNotes/{TEST_GARDENLINUX_RELEASE}", - text='{"packageList": []}', - status_code=200, - ) - assert release_notes_changes_section(TEST_GARDENLINUX_RELEASE) == "", ( - "Expected an empty result if GLVD returns an empty package list" - ) - - -def test_release_notes_changes_section_broken_glvd_response() -> None: - with requests_mock.Mocker() as m: - m.get( - f"{GLVD_BASE_URL}/releaseNotes/{TEST_GARDENLINUX_RELEASE}", - text="

Personal Home Page

", - status_code=200, - ) - assert "fill this in" in release_notes_changes_section( - TEST_GARDENLINUX_RELEASE - ), ( - "Expected a placeholder message to be generated if GVLD response is not valid" - ) - - -def test_release_notes_compare_package_versions_section_legacy_versioning_is_recognized() -> ( - None -): - with requests_mock.Mocker() as m: - m.get( - f"{GLVD_BASE_URL}/releaseNotes/1.0", - json={ - "packageList": [ - { - "sourcePackageName": "gardenlinux-release-example", - "oldVersion": "0.9pre1", - "newVersion": "1.0", - "fixedCves": [], - } - ] - }, - status_code=200, - ) - - assert "upgrade 'gardenlinux-release-example'" in release_notes_changes_section( - "1.0" - ), "Legacy versioning is supported" - - -def test_release_notes_compare_package_versions_section_legacy_versioning_patch_release_is_recognized( - monkeypatch: pytest.MonkeyPatch, -) -> None: - def mock_compare_apt_repo_versions( - previous_version: str, current_version: str - ) -> str: - output = f"| Package | {previous_version} | {current_version} |\n" - output += "|---------|--------------------|-------------------|\n" - output += "|containerd|1.0|1.1|\n" - return output - - monkeypatch.setattr( - "gardenlinux.github.release_notes.sections.compare_apt_repo_versions", - mock_compare_apt_repo_versions, - ) - - assert "|containerd|1.0|1.1|" in release_notes_compare_package_versions_section( - "1.1", {} - ), "Legacy versioning patch releases are supported" - - -def test_release_notes_compare_package_versions_section_semver_is_recognized() -> None: - with requests_mock.Mocker() as m: - m.get( - f"{GLVD_BASE_URL}/releaseNotes/1.20.0", - json={ - "packageList": [ - { - "sourcePackageName": "gardenlinux-release-example", - "oldVersion": "1.19.0", - "newVersion": "1.20.0", - "fixedCves": [], - } - ] - }, - status_code=200, - ) - - assert "upgrade 'gardenlinux-release-example'" in release_notes_changes_section( - "1.20.0" - ), "Semver is supported" - - -def test_release_notes_compare_package_versions_section_semver_patch_release_is_recognized( - monkeypatch: pytest.MonkeyPatch, -) -> None: - def mock_compare_apt_repo_versions( - previous_version: str, current_version: str - ) -> str: - output = f"| Package | {previous_version} | {current_version} |\n" - output += "|---------|--------------------|-------------------|\n" - output += "|containerd|1.0|1.1|\n" - return output - - monkeypatch.setattr( - "gardenlinux.github.release_notes.sections.compare_apt_repo_versions", - mock_compare_apt_repo_versions, - ) - - assert "|containerd|1.0|1.1|" in release_notes_compare_package_versions_section( - "1.20.1", {} - ), "Semver patch releases are supported" - - -def test_release_notes_compare_package_versions_section_unrecognizable_version() -> ( - None -): - with pytest.raises(UnsupportedDistroVersion): - release_notes_compare_package_versions_section("garden.linux", {}) - - -@pytest.mark.parametrize("flavor", TEST_FLAVORS) -def test_get_variant_from_flavor(flavor: str) -> None: - assert get_variant_from_flavor(flavor[0]) == flavor[1] - - -def test_default_get_file_extension_for_deployment_platform() -> None: - assert DeploymentPlatform().image_extension() == "raw" - - -@mock_aws -def test_github_release_page( - monkeypatch: pytest.MonkeyPatch, downloads_dir: None, release_s3_bucket: Bucket -) -> None: - class SubmoduleAsRepo(Repo): # type: ignore[misc] - """This will fake a git submodule as a git repository object.""" - - def __new__(cls, *args, **kwargs): # type: ignore[no-untyped-def] - r = super().__new__(Repo) # pyright: ignore[reportArgumentType] - r.__init__(*args, **kwargs) - - maybe_gl_submodule = [ - submodule - for submodule in r.submodules - if submodule.name.endswith("/gardenlinux") - ] - if not maybe_gl_submodule: - return r - else: - gl = maybe_gl_submodule[0] - - sr = gl.module() - sr.remotes.origin.pull("main") - return sr - - monkeypatch.setattr( - "gardenlinux.github.release_notes.helpers.Repo", # pyright: ignore[reportAttributeAccessIssue] - SubmoduleAsRepo, - ) - import gardenlinux.github - - release_fixture_path = ( - RELEASE_NOTES_TEST_DATA_DIR - / f"github_release_notes_{TEST_GARDENLINUX_RELEASE}.md" - ) - glvd_response_fixture_path = ( - RELEASE_NOTES_TEST_DATA_DIR / f"glvd_{TEST_GARDENLINUX_RELEASE}.json" - ) - - with requests_mock.Mocker(real_http=True) as m: - for yaml_file in RELEASE_ARTIFACTS_METADATA_FILES: - filepath = f"{RELEASE_NOTES_S3_ARTIFACTS_DIR}/{yaml_file}" - base = yaml_file[: -len(".s3_metadata.yaml")] - key = f"meta/singles/{base}-{TEST_GARDENLINUX_RELEASE}-{TEST_GARDENLINUX_COMMIT}" - release_s3_bucket.upload_file(filepath, key) - - m.get( - f"{GLVD_BASE_URL}/releaseNotes/{TEST_GARDENLINUX_RELEASE}", - text=glvd_response_fixture_path.read_text(), - status_code=200, - ) - generated_release_notes = ( - gardenlinux.github.release_notes.create_github_release_notes( # pyright: ignore[reportAttributeAccessIssue] - TEST_GARDENLINUX_RELEASE, - TEST_GARDENLINUX_COMMIT, - release_s3_bucket.name, - ) - ) - - assert generated_release_notes == release_fixture_path.read_text() diff --git a/tests/github/test_deployment_platform.py b/tests/github/test_deployment_platform.py new file mode 100644 index 00000000..2de9ecc5 --- /dev/null +++ b/tests/github/test_deployment_platform.py @@ -0,0 +1,8 @@ +from gardenlinux.github.release import DeploymentPlatform + + +def test_default_get_file_extension_for_deployment_platform() -> None: + assert ( + DeploymentPlatform.new_instance({"platform": "generic"}).image_extension + == "raw" + ) diff --git a/tests/github/test_download_metadata_files.py b/tests/github/test_download_metadata_files.py deleted file mode 100644 index 8138929e..00000000 --- a/tests/github/test_download_metadata_files.py +++ /dev/null @@ -1,125 +0,0 @@ -import pytest - -from gardenlinux.constants import S3_DOWNLOADS_DIR -from gardenlinux.features import CName -from gardenlinux.github.release_notes.helpers import download_metadata_file -from gardenlinux.s3 import Bucket, S3Artifacts - -from ..constants import ( - RELEASE_NOTES_S3_ARTIFACTS_DIR, - TEST_GARDENLINUX_COMMIT_SHORT, - TEST_GARDENLINUX_RELEASE, - TEST_GARDENLINUX_RELEASE_BUCKET_NAME, -) - - -def test_download_metadata_file(downloads_dir: None, release_s3_bucket: Bucket) -> None: - release_s3_bucket.upload_file( - str( - RELEASE_NOTES_S3_ARTIFACTS_DIR / "aws-gardener_prod-amd64.s3_metadata.yaml" - ), - f"meta/singles/test-aws-gardener_prod-amd64-{TEST_GARDENLINUX_RELEASE}-{TEST_GARDENLINUX_COMMIT_SHORT}", - ) - - s3_artifacts = S3Artifacts(TEST_GARDENLINUX_RELEASE_BUCKET_NAME) - s3_artifacts._bucket = release_s3_bucket - - cname = CName("test-aws-gardener_prod", "amd64", TEST_GARDENLINUX_COMMIT_SHORT) - download_metadata_file( - s3_artifacts, - cname, - TEST_GARDENLINUX_RELEASE, - TEST_GARDENLINUX_COMMIT_SHORT, - S3_DOWNLOADS_DIR, - ) - assert (S3_DOWNLOADS_DIR / "test-aws-gardener_prod-amd64.s3_metadata.yaml").exists() - - -def test_download_metadata_file_no_such_release( - downloads_dir: None, release_s3_bucket: Bucket -) -> None: - release_s3_bucket.upload_file( - str( - RELEASE_NOTES_S3_ARTIFACTS_DIR / "aws-gardener_prod-amd64.s3_metadata.yaml" - ), - f"meta/singles/test-aws-gardener_prod-amd64-{TEST_GARDENLINUX_RELEASE}-{TEST_GARDENLINUX_COMMIT_SHORT}", - ) - s3_artifacts = S3Artifacts(TEST_GARDENLINUX_RELEASE_BUCKET_NAME) - s3_artifacts._bucket = release_s3_bucket - - release = "0000.0" - commit = TEST_GARDENLINUX_COMMIT_SHORT - cname = CName("aws-gardener_prod", "amd64", commit) - - with pytest.raises(IndexError): - download_metadata_file( - s3_artifacts, - cname, - release, - TEST_GARDENLINUX_COMMIT_SHORT, - S3_DOWNLOADS_DIR, - ) - assert not ( - S3_DOWNLOADS_DIR / "test-aws-gardener_prod-amd64.s3_metadata.yaml" - ).exists() - - -def test_download_metadata_file_no_such_commit( - downloads_dir: None, release_s3_bucket: Bucket -) -> None: - release_s3_bucket.upload_file( - str( - RELEASE_NOTES_S3_ARTIFACTS_DIR / "aws-gardener_prod-amd64.s3_metadata.yaml" - ), - f"meta/singles/test-aws-gardener_prod-amd64-{TEST_GARDENLINUX_RELEASE}-{TEST_GARDENLINUX_COMMIT_SHORT}", - ) - - s3_artifacts = S3Artifacts(TEST_GARDENLINUX_RELEASE_BUCKET_NAME) - s3_artifacts._bucket = release_s3_bucket - - release = TEST_GARDENLINUX_RELEASE - commit = "deadbeef" - cname = CName("test-aws-gardener_prod", "amd64", commit) - - with pytest.raises(IndexError): - download_metadata_file( - s3_artifacts, - cname, - release, - commit, - S3_DOWNLOADS_DIR, - ) - assert not ( - S3_DOWNLOADS_DIR / "test-aws-gardener_prod-amd64.s3_metadata.yaml" - ).exists() - - -def test_download_metadata_file_no_such_release_and_commit( - downloads_dir: None, release_s3_bucket: Bucket -) -> None: - release_s3_bucket.upload_file( - str( - RELEASE_NOTES_S3_ARTIFACTS_DIR / "aws-gardener_prod-amd64.s3_metadata.yaml" - ), - f"meta/singles/test-aws-gardener_prod-amd64-{TEST_GARDENLINUX_RELEASE}-{TEST_GARDENLINUX_COMMIT_SHORT}", - ) - - s3_artifacts = S3Artifacts(TEST_GARDENLINUX_RELEASE_BUCKET_NAME) - s3_artifacts._bucket = release_s3_bucket - - release = "0000.0" - commit = "deadbeef" - cname = CName("test-aws-gardener_prod", "amd64", commit) - print(f"{cname.cname=}") - - with pytest.raises(IndexError): - download_metadata_file( - s3_artifacts, - cname, - release, - TEST_GARDENLINUX_COMMIT_SHORT, - S3_DOWNLOADS_DIR, - ) - assert not ( - S3_DOWNLOADS_DIR / "test-aws-gardener_prod-amd64.s3_metadata.yaml" - ).exists() diff --git a/tests/github/test_github_script.py b/tests/github/test_github_script.py deleted file mode 100644 index c3ca2ede..00000000 --- a/tests/github/test_github_script.py +++ /dev/null @@ -1,177 +0,0 @@ -import sys - -import pytest -import requests_mock - -import gardenlinux.github.release.__main__ as gh -from gardenlinux.constants import GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME - -from ..constants import TEST_GARDENLINUX_COMMIT, TEST_GARDENLINUX_RELEASE -from .constants import REPO_JSON - - -def test_script_parse_args_wrong_command( - monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str] -) -> None: - monkeypatch.setattr(sys, "argv", ["gh", "rejoice"]) - - with pytest.raises(SystemExit): - gh.main() - captured = capfd.readouterr() - - assert "argument command: invalid choice: 'rejoice'" in captured.err, ( - "Expected help message printed" - ) - - -def test_script_parse_args_create_command_required_args( - monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str] -) -> None: - monkeypatch.setattr( - sys, - "argv", - [ - "gh", - "create-with-gl-release-notes", - "--owner", - "gardenlinux", - "--repo", - "gardenlinux", - ], - ) - - with pytest.raises(SystemExit): - gh.main() - captured = capfd.readouterr() - - assert "the following arguments are required: --tag, --commit" in captured.err, ( - "Expected help message on missing arguments for 'create' command" - ) - - -def test_script_parse_args_upload_command_required_args( - monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str] -) -> None: - monkeypatch.setattr( - sys, "argv", ["gh", "upload", "--owner", "gardenlinux", "--repo", "gardenlinux"] - ) - - with pytest.raises(SystemExit): - gh.main() - captured = capfd.readouterr() - - assert ( - "the following arguments are required: --release_id, --file_path" - in captured.err - ), "Expected help message on missing arguments for 'upload' command" - - -def test_script_create_dry_run( - monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str] -) -> None: - monkeypatch.setattr( - sys, - "argv", - [ - "gh", - "create-with-gl-release-notes", - "--owner", - "gardenlinux", - "--repo", - "gardenlinux", - "--tag", - TEST_GARDENLINUX_RELEASE, - "--commit", - TEST_GARDENLINUX_COMMIT, - "--dry-run", - ], - ) - monkeypatch.setattr( - "gardenlinux.github.release.__main__.create_github_release_notes", - lambda tag, commit, bucket: f"{tag} {commit} {bucket}", - ) - - gh.main() - captured = capfd.readouterr() - - assert ( - captured.out - == f"Dry Run ...\nThis release would be created:\n{TEST_GARDENLINUX_RELEASE} {TEST_GARDENLINUX_COMMIT} {GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME}\n" - ), "Expected dry-run create to return generated release notes text" - - -def test_script_create( - monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture -) -> None: - with requests_mock.Mocker() as m: - monkeypatch.setattr( - sys, - "argv", - [ - "gh", - "create-with-gl-release-notes", - "--owner", - "gardenlinux", - "--repo", - "gardenlinux", - "--tag", - TEST_GARDENLINUX_RELEASE, - "--commit", - TEST_GARDENLINUX_COMMIT, - ], - ) - monkeypatch.setattr( - "gardenlinux.github.release.__main__.create_github_release_notes", - lambda tag, commit, bucket: f"{tag} {commit} {bucket}", - ) - monkeypatch.setenv("GITHUB_TOKEN", "invalid") - - m.get( - "//api.github.com:443/repos/gardenlinux/gardenlinux", - json=REPO_JSON, - status_code=200, - ) - - m.post( - "//api.github.com:443/repos/gardenlinux/gardenlinux/releases", - json={"id": 101}, - status_code=201, - ) - - gh.main() - - assert any( - "Release created with ID: 101" in record.message - for record in caplog.records - ), "Expected a release creation confirmation log entry" - - -def test_script_upload_dry_run( - monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str] -) -> None: - monkeypatch.setattr( - sys, - "argv", - [ - "gh", - "upload", - "--owner", - "gardenlinux", - "--repo", - "gardenlinux", - "--release_id", - TEST_GARDENLINUX_RELEASE, - "--file_path", - "foo", - "--dry-run", - ], - ) - monkeypatch.setattr( - "gardenlinux.github.release.__main__.upload_to_github_release_page", - lambda a1, a2, a3, a4, dry_run: print(f"dry-run: {dry_run}"), - ) - - gh.main() - captured = capfd.readouterr() - - assert captured.out == "dry-run: True\n" diff --git a/tests/github/test_main.py b/tests/github/test_main.py new file mode 100644 index 00000000..43360a8b --- /dev/null +++ b/tests/github/test_main.py @@ -0,0 +1,333 @@ +import sys +from pathlib import Path + +import pytest +import requests_mock + +import gardenlinux.github.release.__main__ as gh +from gardenlinux.constants import GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME + +from ..constants import TEST_GARDENLINUX_COMMIT, TEST_GARDENLINUX_RELEASE +from .constants import RELEASE_JSON, REPO_JSON + + +def test_script_parse_args_wrong_command( + monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str] +) -> None: + monkeypatch.setattr(sys, "argv", ["gh", "rejoice"]) + + with pytest.raises(SystemExit): + gh.main() + captured = capfd.readouterr() + + assert "argument command: invalid choice: 'rejoice'" in captured.err, ( + "Expected help message printed" + ) + + +def test_script_parse_args_create_command_required_args( + monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str] +) -> None: + monkeypatch.setattr( + sys, + "argv", + [ + "gh", + "create-with-gl-release-notes", + "--owner", + "gardenlinux", + "--repo", + "gardenlinux", + ], + ) + + with pytest.raises(SystemExit): + gh.main() + captured = capfd.readouterr() + + assert "the following arguments are required: --tag, --commit" in captured.err, ( + "Expected help message on missing arguments for 'create' command" + ) + + +def test_script_create_dry_run( + monkeypatch: pytest.MonkeyPatch, + capfd: pytest.CaptureFixture[str], + github_token: str, +) -> None: + monkeypatch.setattr( + sys, + "argv", + [ + "gh", + "create-with-gl-release-notes", + "--owner", + "gardenlinux", + "--repo", + "gardenlinux", + "--tag", + TEST_GARDENLINUX_RELEASE, + "--commit", + TEST_GARDENLINUX_COMMIT, + "--dry-run", + ], + ) + + monkeypatch.setattr( + "gardenlinux.github.release.notes.markdown_generator.MarkdownGenerator.__str__", + lambda self: f"{self._version} {self._commitish} {self._s3_bucket_name}", + ) + + gh.main() + captured = capfd.readouterr() + + assert ( + captured.out + == f"Dry Run ...\nThis release would be created:\n{TEST_GARDENLINUX_RELEASE} {TEST_GARDENLINUX_COMMIT} {GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME}\n" + ), "Expected dry-run create to return generated release notes text" + + +def test_script_create( + monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture, github_token: str +) -> None: + with requests_mock.Mocker() as m: + monkeypatch.setattr( + sys, + "argv", + [ + "gh", + "create-with-gl-release-notes", + "--owner", + "gardenlinux", + "--repo", + "gardenlinux", + "--tag", + TEST_GARDENLINUX_RELEASE, + "--commit", + TEST_GARDENLINUX_COMMIT, + ], + ) + + monkeypatch.setattr( + "gardenlinux.github.release.notes.markdown_generator.MarkdownGenerator.__str__", + lambda self: f"{self._version} {self._commitish} {self._s3_bucket_name}", + ) + + m.get( + "//api.github.com:443/repos/gardenlinux/gardenlinux", + json=REPO_JSON, + status_code=200, + ) + + m.post( + "//api.github.com:443/repos/gardenlinux/gardenlinux/releases", + json={"id": 101}, + status_code=201, + ) + + gh.main() + + assert any( + "Release created with ID: 101" in record.message + for record in caplog.records + ), "Expected a release creation confirmation log entry" + + +def test_script_upload_needs_github_token( + monkeypatch: pytest.MonkeyPatch, artifact_for_upload: Path +) -> None: + with pytest.raises(ValueError) as exn: + monkeypatch.setattr( + sys, + "argv", + [ + "gh", + "upload", + "--owner", + "gardenlinux", + "--repo", + "gardenlinux", + "--release_id", + TEST_GARDENLINUX_RELEASE, + "--file_path", + str(artifact_for_upload), + "--dry-run", + ], + ) + + gh.main() + + assert str(exn.value) == "GITHUB_TOKEN environment variable not set", ( + "Expected an exception to be raised on missing GITHUB_TOKEN environment variable" + ) + + +def test_script_parse_args_upload_command_required_args( + monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str] +) -> None: + monkeypatch.setattr( + sys, "argv", ["gh", "upload", "--owner", "gardenlinux", "--repo", "gardenlinux"] + ) + + with pytest.raises(SystemExit): + gh.main() + captured = capfd.readouterr() + + assert ( + "the following arguments are required: --release_id, --file_path" + in captured.err + ), "Expected help message on missing arguments for 'upload' command" + + +def test_script_upload_dry_run( + monkeypatch: pytest.MonkeyPatch, + capfd: pytest.CaptureFixture[str], + github_token: str, + artifact_for_upload: Path, +) -> None: + with requests_mock.Mocker() as m: + m.get( + "//api.github.com:443/repos/gardenlinux/gardenlinux", + json=REPO_JSON, + status_code=200, + ) + + m.get( + f"//api.github.com:443/repos/gardenlinux/gardenlinux/releases/tags/{TEST_GARDENLINUX_RELEASE}", + json=RELEASE_JSON, + status_code=200, + ) + + monkeypatch.setattr( + sys, + "argv", + [ + "gh", + "upload", + "--owner", + "gardenlinux", + "--repo", + "gardenlinux", + "--release_id", + TEST_GARDENLINUX_RELEASE, + "--file_path", + str(artifact_for_upload), + "--dry-run", + ], + ) + + gh.main() + + captured = capfd.readouterr() + assert "would be uploaded for release" in captured.out, ( + "Expected a dry‑run log entry" + ) + + +def test_script_upload_inaccessible_file( + monkeypatch: pytest.MonkeyPatch, + capfd: pytest.CaptureFixture[str], + github_token: str, + artifact_for_upload: Path, +) -> None: + artifact_for_upload.chmod(0) + + with requests_mock.Mocker() as m: + m.get( + "//api.github.com:443/repos/gardenlinux/gardenlinux", + json=REPO_JSON, + status_code=200, + ) + + m.get( + "//api.github.com:443/repos/gardenlinux/gardenlinux/releases/1", + json=RELEASE_JSON, + status_code=200, + ) + + m.get( + f"//api.github.com:443/repos/gardenlinux/gardenlinux/releases/tags/{TEST_GARDENLINUX_RELEASE}", + json=RELEASE_JSON, + status_code=200, + ) + + m.post( + f"//uploads.github.com:443/repos/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE}/assets?name=artifact.log", + text="{}", + status_code=201, + ) + + monkeypatch.setattr( + sys, + "argv", + [ + "gh", + "upload", + "--owner", + "gardenlinux", + "--repo", + "gardenlinux", + "--release_id", + TEST_GARDENLINUX_RELEASE, + "--file_path", + str(artifact_for_upload), + ], + ) + + with pytest.raises(PermissionError): + gh.main() + + +def test_script_upload( + monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture, + github_token: str, + artifact_for_upload: Path, +) -> None: + with requests_mock.Mocker() as m: + m.get( + "//api.github.com:443/repos/gardenlinux/gardenlinux", + json=REPO_JSON, + status_code=200, + ) + + m.get( + "//api.github.com:443/repos/gardenlinux/gardenlinux/releases/1", + json=RELEASE_JSON, + status_code=200, + ) + + m.get( + f"//api.github.com:443/repos/gardenlinux/gardenlinux/releases/tags/{TEST_GARDENLINUX_RELEASE}", + json=RELEASE_JSON, + status_code=200, + ) + + m.post( + "//uploads.github.com:443/repos/gardenlinux/gardenlinux/releases/1/assets?label=&name=artifact.log", + json={}, + status_code=201, + ) + + monkeypatch.setattr( + sys, + "argv", + [ + "gh", + "upload", + "--owner", + "gardenlinux", + "--repo", + "gardenlinux", + "--release_id", + TEST_GARDENLINUX_RELEASE, + "--file_path", + str(artifact_for_upload), + ], + ) + + gh.main() + + assert any("Uploaded file" in record.message for record in caplog.records), ( + "Expected a upload file log entry" + ) diff --git a/tests/github/test_markdown_generator.py b/tests/github/test_markdown_generator.py new file mode 100644 index 00000000..7b0bc7a6 --- /dev/null +++ b/tests/github/test_markdown_generator.py @@ -0,0 +1,178 @@ +import pytest +import requests_mock +from moto import mock_aws + +from gardenlinux.constants import GLVD_BASE_URL +from gardenlinux.github.release import Release +from gardenlinux.github.release.notes import MarkdownGenerator +from gardenlinux.s3.bucket import Bucket + +from ..constants import ( + RELEASE_ARTIFACTS_METADATA_FILES, + RELEASE_NOTES_S3_ARTIFACTS_DIR, + RELEASE_NOTES_TEST_DATA_DIR, + REPO_NAME, + TEST_GARDENLINUX_COMMIT, + TEST_GARDENLINUX_RELEASE, + TEST_GARDENLINUX_RELEASE_BUCKET_NAME, +) + +TEST_FLAVORS = [ + ("foo_bar_baz", "legacy"), + ("aws-gardener_prod_trustedboot_tpm2-amd64", "legacy"), + ("openstack-gardener_prod_tpm2_trustedboot-arm64", "tpm2_trustedboot"), + ("azure-gardener_prod_usi-amd64", "usi"), + ("", "legacy"), +] + + +def test_release_notes_changes_section_empty_packagelist(github_token: str) -> None: + with requests_mock.Mocker() as m: + m.get( + f"{GLVD_BASE_URL}/releaseNotes/{TEST_GARDENLINUX_RELEASE}", + json={"packageList": []}, + status_code=200, + ) + + release = Release(REPO_NAME) + release.tag = TEST_GARDENLINUX_RELEASE + release.commitish = TEST_GARDENLINUX_COMMIT + + generator = MarkdownGenerator( + release, + TEST_GARDENLINUX_RELEASE_BUCKET_NAME, + ) + + assert ( + "todo release facilitator: fill this in" in generator.changes_and_cves_list + ), "Expected an placeholder result if GLVD returns an empty package list" + + +def test_release_notes_changes_section_broken_glvd_response(github_token: str) -> None: + with requests_mock.Mocker() as m: + m.get( + f"{GLVD_BASE_URL}/releaseNotes/{TEST_GARDENLINUX_RELEASE}", + text="

Personal Home Page

", + status_code=200, + ) + + release = Release(REPO_NAME) + release.tag = TEST_GARDENLINUX_RELEASE + release.commitish = TEST_GARDENLINUX_COMMIT + + generator = MarkdownGenerator( + release, + TEST_GARDENLINUX_RELEASE_BUCKET_NAME, + ) + + assert ( + "todo release facilitator: fill this in" in generator.changes_and_cves_list + ), ( + "Expected a placeholder message to be generated if GVLD response is not valid" + ) + + +def test_release_notes_compare_package_versions_section_legacy_versioning_is_recognized( + github_token: str, +) -> None: + with requests_mock.Mocker() as m: + m.get( + f"{GLVD_BASE_URL}/releaseNotes/1.0", + json={ + "packageList": [ + { + "sourcePackageName": "gardenlinux-release-example", + "oldVersion": "0.9pre1", + "newVersion": "1.0", + "fixedCves": [], + } + ] + }, + status_code=200, + ) + + release = Release(REPO_NAME) + release.tag = "1.0" + release.commitish = TEST_GARDENLINUX_COMMIT + + generator = MarkdownGenerator( + release, + TEST_GARDENLINUX_RELEASE_BUCKET_NAME, + ) + + assert ( + "upgrade 'gardenlinux-release-example'" in generator.changes_and_cves_list + ), "Legacy versioning is supported" + + +def test_release_notes_compare_package_versions_section_semver_is_recognized( + github_token: str, +) -> None: + with requests_mock.Mocker() as m: + m.get( + f"{GLVD_BASE_URL}/releaseNotes/1.20.0", + json={ + "packageList": [ + { + "sourcePackageName": "gardenlinux-release-example", + "oldVersion": "1.19.0", + "newVersion": "1.20.0", + "fixedCves": [], + } + ] + }, + status_code=200, + ) + + release = Release(REPO_NAME) + release.tag = "1.20.0" + release.commitish = TEST_GARDENLINUX_COMMIT + + generator = MarkdownGenerator( + release, + TEST_GARDENLINUX_RELEASE_BUCKET_NAME, + ) + + assert ( + "upgrade 'gardenlinux-release-example'" in generator.changes_and_cves_list + ), "Semver is supported" + + +@mock_aws +def test_github_release_page( + monkeypatch: pytest.MonkeyPatch, + github_token: str, + downloads_dir: None, + release_s3_bucket: Bucket, +) -> None: + release_fixture_path = ( + RELEASE_NOTES_TEST_DATA_DIR + / f"github_release_notes_{TEST_GARDENLINUX_RELEASE}.md" + ) + glvd_response_fixture_path = ( + RELEASE_NOTES_TEST_DATA_DIR / f"glvd_{TEST_GARDENLINUX_RELEASE}.json" + ) + + with requests_mock.Mocker(real_http=True) as m: + for yaml_file in RELEASE_ARTIFACTS_METADATA_FILES: + filepath = f"{RELEASE_NOTES_S3_ARTIFACTS_DIR}/{yaml_file}" + base = yaml_file[: -len(".s3_metadata.yaml")] + key = f"meta/singles/{base}-{TEST_GARDENLINUX_RELEASE}-{TEST_GARDENLINUX_COMMIT}" + release_s3_bucket.upload_file(filepath, key) + + m.get( + f"{GLVD_BASE_URL}/releaseNotes/{TEST_GARDENLINUX_RELEASE}", + text=glvd_response_fixture_path.read_text(), + status_code=200, + ) + + release = Release(REPO_NAME) + release.tag = TEST_GARDENLINUX_RELEASE + release.commitish = TEST_GARDENLINUX_COMMIT + + generator = MarkdownGenerator( + release, + TEST_GARDENLINUX_RELEASE_BUCKET_NAME, + ) + + assert str(generator) == release_fixture_path.read_text() diff --git a/tests/github/test_release.py b/tests/github/test_release.py index dc74685b..f17783a3 100644 --- a/tests/github/test_release.py +++ b/tests/github/test_release.py @@ -1,10 +1,8 @@ -from pathlib import Path - import pytest import requests_mock from github import GithubException -from gardenlinux.github.release import Release, write_to_release_id_file +from gardenlinux.github.release import Release from ..constants import ( TEST_GARDENLINUX_COMMIT, @@ -70,20 +68,3 @@ def test_Release(caplog: pytest.LogCaptureFixture, github_token: str) -> None: ) assert release.create() == 101 - - -def test_write_to_release_id_file(release_id_file: Path) -> None: - write_to_release_id_file(TEST_GARDENLINUX_RELEASE) - assert release_id_file.read_text() == TEST_GARDENLINUX_RELEASE - - -def test_write_to_release_id_file_broken_file_permissions( - release_id_file: Path, caplog: pytest.LogCaptureFixture -) -> None: - release_id_file.touch(0) # this will make the file unwritable - - with pytest.raises(SystemExit): - write_to_release_id_file(TEST_GARDENLINUX_RELEASE) - assert any("Could not create" in record.message for record in caplog.records), ( - "Expected a failure log record" - ) diff --git a/tests/github/test_upload_to_github_release_page.py b/tests/github/test_upload_to_github_release_page.py deleted file mode 100644 index 11d3b976..00000000 --- a/tests/github/test_upload_to_github_release_page.py +++ /dev/null @@ -1,181 +0,0 @@ -import sys -from pathlib import Path - -import pytest -import requests -import requests_mock - -import gardenlinux.github.release.__main__ as gh -from gardenlinux.github.release import upload_to_github_release_page - -from ..constants import TEST_GARDENLINUX_RELEASE - - -def test_upload_to_github_release_page_dryrun( - caplog: pytest.LogCaptureFixture, artifact_for_upload: Path -) -> None: - with requests_mock.Mocker(): - assert ( - upload_to_github_release_page( # type: ignore[func-returns-value] - "gardenlinux", - "gardenlinux", - TEST_GARDENLINUX_RELEASE, - str(artifact_for_upload), - dry_run=True, - ) - is None - ) - assert any( - "Dry run: would upload" in record.message for record in caplog.records - ), "Expected a dry‑run log entry" - - -def test_upload_to_github_release_page_needs_github_token( - downloads_dir: None, artifact_for_upload: Path -) -> None: - with requests_mock.Mocker(): - with pytest.raises(ValueError) as exn: - upload_to_github_release_page( - "gardenlinux", - "gardenlinux", - TEST_GARDENLINUX_RELEASE, - str(artifact_for_upload), - dry_run=False, - ) - assert str(exn.value) == "GITHUB_TOKEN environment variable not set", ( - "Expected an exception to be raised on missing GITHUB_TOKEN environment variable" - ) - - -def test_upload_to_github_release_page( - downloads_dir: None, - caplog: pytest.LogCaptureFixture, - github_token: None, - artifact_for_upload: Path, -) -> None: - with requests_mock.Mocker(real_http=True) as m: - m.post( - f"https://uploads.github.com/repos/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE}/assets?name=artifact.log", - text="{}", - status_code=201, - ) - - upload_to_github_release_page( - "gardenlinux", - "gardenlinux", - TEST_GARDENLINUX_RELEASE, - str(artifact_for_upload), - dry_run=False, - ) - assert any( - "Upload successful" in record.message for record in caplog.records - ), "Expected an upload confirmation log entry" - - -def test_upload_to_github_release_page_unreadable_artifact( - downloads_dir: None, - caplog: pytest.LogCaptureFixture, - github_token: None, - artifact_for_upload: Path, -) -> None: - artifact_for_upload.chmod(0) - - upload_to_github_release_page( - "gardenlinux", - "gardenlinux", - TEST_GARDENLINUX_RELEASE, - str(artifact_for_upload), - dry_run=False, - ) - assert any("Error reading file" in record.message for record in caplog.records), ( - "Expected an error message log entry" - ) - - -def test_upload_to_github_release_page_failed( - downloads_dir: None, - caplog: pytest.LogCaptureFixture, - github_token: None, - artifact_for_upload: Path, -) -> None: - with requests_mock.Mocker(real_http=True) as m: - m.post( - f"https://uploads.github.com/repos/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE}/assets?name=artifact.log", - text="{}", - status_code=503, - ) - - with pytest.raises(requests.exceptions.HTTPError): - upload_to_github_release_page( - "gardenlinux", - "gardenlinux", - TEST_GARDENLINUX_RELEASE, - str(artifact_for_upload), - dry_run=False, - ) - assert any( - "Upload failed with status code 503:" in record.message - for record in caplog.records - ), "Expected an error HTTP status code to be logged" - - -def test_script_parse_args_wrong_command( - monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str] -) -> None: - monkeypatch.setattr(sys, "argv", ["gh", "rejoice"]) - - with pytest.raises(SystemExit): - gh.main() - captured = capfd.readouterr() - - assert "argument command: invalid choice: 'rejoice'" in captured.err, ( - "Expected help message printed" - ) - - -def test_script_parse_args_upload_command_required_args( - monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str] -) -> None: - monkeypatch.setattr( - sys, "argv", ["gh", "upload", "--owner", "gardenlinux", "--repo", "gardenlinux"] - ) - - with pytest.raises(SystemExit): - gh.main() - captured = capfd.readouterr() - - assert ( - "the following arguments are required: --release_id, --file_path" - in captured.err - ), "Expected help message on missing arguments for 'upload' command" - - -def test_script_upload_dry_run( - monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str] -) -> None: - monkeypatch.setattr( - sys, - "argv", - [ - "gh", - "upload", - "--owner", - "gardenlinux", - "--repo", - "gardenlinux", - "--release_id", - TEST_GARDENLINUX_RELEASE, - "--file_path", - "foo", - "--dry-run", - ], - ) - monkeypatch.setattr( - "gardenlinux.github.release.__main__.upload_to_github_release_page", - lambda a1, a2, a3, a4, dry_run: print(f"dry-run: {dry_run}"), - ) - - gh.main() - captured = capfd.readouterr() - - assert captured.out == "dry-run: True\n"