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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,097 changes: 1,053 additions & 1,044 deletions poetry.lock

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ packages = [{ include = "gardenlinux", from = "src" }]
[tool.poetry.dependencies]
python = ">=3.13,!=3.14.1"
apt-repo = "^0.5"
boto3 = "^1.42.30"
click = "^8.3.1"
boto3 = "^1.43.10"
click = "^8.4.0"
cryptography = "^46.0.3"
jsonschema = "^4.26.0"
networkx = "^3.6"
Expand All @@ -21,22 +21,22 @@ 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"
bandit = "^1.9.4"
isort = "^8.0.1"
moto = "^5.1.20"
pre-commit = "^4.5.1"
python-dotenv = "^1.2.1"
pytest = "^9.0.2"
pytest-cov = "^7.0.0"
moto = "^5.2.1"
pre-commit = "^4.6.0"
python-dotenv = "^1.2.2"
pytest = "^9.0.3"
pytest-cov = "^7.1.0"
requests-mock = "^1.12.1"
mypy = "1.20.1"
types-click = "^7.1.8"
types-pyyaml = "^6.0.12.20250915"
types-requests = "^2.32.4.20260107"
boto3-stubs = { extras = ["s3"], version = "^1.42.30" }
types-pyyaml = "^6.0.12.20260518"
types-requests = "^2.33.0.20260518"
boto3-stubs = { extras = ["s3"], version = "^1.42.41" }

[tool.poetry.group.docs.dependencies]
sphinx-rtd-theme = "^3.0.2"
Expand Down
7 changes: 1 addition & 6 deletions src/gardenlinux/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -157,8 +158,6 @@
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, 60) # connect, read

S3_DOWNLOADS_DIR = Path(os.path.dirname(__file__)) / ".." / "s3_downloads"
Expand All @@ -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"]
63 changes: 63 additions & 0 deletions src/gardenlinux/distro_version.py
Original file line number Diff line number Diff line change
@@ -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,
)
)
73 changes: 0 additions & 73 deletions src/gardenlinux/distro_version/__init__.py

This file was deleted.

77 changes: 77 additions & 0 deletions src/gardenlinux/git/remote_callbacks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-

"""
Git credentials provider
"""

from typing import Any, Optional

from pygit2 import KeypairFromAgent
from pygit2 import RemoteCallbacks as _RemoteCallbacks
from pygit2 import UserPass
from pygit2.enums import CredentialType


class RemoteCallbacks(_RemoteCallbacks): # type: ignore[misc]
"""
pygit2.org: Base class for pygit2 remote callbacks.

:author: Garden Linux Maintainers
:copyright: Copyright 2024 SAP SE
:package: gardenlinux
:subpackage: git
:since: 1.0.0
:license: https://www.apache.org/licenses/LICENSE-2.0
Apache License, Version 2.0
"""

def __init__(
self,
*args: Any,
username: Optional[str] = None,
password: Optional[str] = None,
**kwargs: Any,
):
"""
Constructor __init__(RemoteCallbacks)

:param token: GitHub/Git access token for HTTPS authentication.
Falls back to the GITHUB_TOKEN environment variable if not provided.

:since: 1.0.0
"""

self._username = ""
self._password = ""

if username and password:
self._username = username
self._password = password

def credentials(
self,
url: str,
username_from_url: Optional[str],
allowed_types: CredentialType,
) -> Optional[UserPass | KeypairFromAgent]:
"""
pygit2.org: Credentials callback. If the remote server requires
authentication, this function will be called and its return value
used for authentication.

:param url: The URL being accessed (after any insteadOf rewrites)
:param username_from_url: Username extracted from the URL, if any
:param allowed_types: Bitmask of credential types the server accepts

:return: A pygit2 credential object
:since: 1.0.0
"""

if allowed_types & CredentialType.USERPASS_PLAINTEXT:
if self._password:
return UserPass(self._username, self._password)

if allowed_types & CredentialType.SSH_KEY:
return KeypairFromAgent(username_from_url or "git")

return _RemoteCallbacks.credentials(url, username_from_url, allowed_types)
9 changes: 7 additions & 2 deletions src/gardenlinux/git/repository.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-

from logging import Logger
from os import PathLike
from os import PathLike, environ
from pathlib import Path
from typing import Any, List, Optional

Expand All @@ -11,6 +11,7 @@

from ..constants import GL_REPOSITORY_URL
from ..logger import LoggerSetup
from .remote_callbacks import RemoteCallbacks


class Repository(_Repository): # type: ignore[misc]
Expand Down Expand Up @@ -135,7 +136,11 @@ def checkout_repo(
)

repo = init_repository(git_directory, origin_url=repo_url)
repo.remotes["origin"].fetch()
repo.remotes["origin"].fetch(
callbacks=RemoteCallbacks(
username=environ.get("GITHUB_TOKEN"), password="x-oauth-basic"
)
)

if commit is None:
refish = f"origin/{branch}"
Expand Down
22 changes: 3 additions & 19 deletions src/gardenlinux/github/release/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
import logging
import sys

from ...constants import RELEASE_ID_FILE
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 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)


__all__ = ["Release", "write_to_release_id_file"]
__all__ = ["DeploymentPlatform", "Release", "ReleaseImagesMetadata"]
23 changes: 11 additions & 12 deletions src/gardenlinux/github/release/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +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 write_to_release_id_file
from .notes import MarkdownGenerator
from .release import Release

LOGGER = LoggerSetup.get_logger("gardenlinux.github", logging.INFO)
Expand Down Expand Up @@ -167,24 +166,24 @@ 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":
release = Release.get(args.release_id, repo=args.repo, owner=args.owner)

Expand Down
8 changes: 8 additions & 0 deletions src/gardenlinux/github/release/ali_cloud.platform.json
Original file line number Diff line number Diff line change
@@ -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}\"}"
}
Loading
Loading