Skip to content

Commit af939ab

Browse files
Merge pull request #345
Restructure and clean up GitHub release notes generator
2 parents 548538e + 12b37df commit af939ab

44 files changed

Lines changed: 3224 additions & 3271 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

poetry.lock

Lines changed: 1053 additions & 1044 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ packages = [{ include = "gardenlinux", from = "src" }]
1010
[tool.poetry.dependencies]
1111
python = ">=3.13,!=3.14.1"
1212
apt-repo = "^0.5"
13-
boto3 = "^1.42.30"
14-
click = "^8.3.1"
13+
boto3 = "^1.43.10"
14+
click = "^8.4.0"
1515
cryptography = "^46.0.3"
1616
jsonschema = "^4.26.0"
1717
networkx = "^3.6"
@@ -21,22 +21,22 @@ pygit2 = "^1.19.1"
2121
pygments = "^2.19.2"
2222
PyGithub = "^2.8.1"
2323
PyYAML = "^6.0.2"
24-
gitpython = "^3.1.45"
24+
semver = "^3.0.4"
2525

2626
[tool.poetry.group.dev.dependencies]
27-
bandit = "^1.9.3"
27+
bandit = "^1.9.4"
2828
isort = "^8.0.1"
29-
moto = "^5.1.20"
30-
pre-commit = "^4.5.1"
31-
python-dotenv = "^1.2.1"
32-
pytest = "^9.0.2"
33-
pytest-cov = "^7.0.0"
29+
moto = "^5.2.1"
30+
pre-commit = "^4.6.0"
31+
python-dotenv = "^1.2.2"
32+
pytest = "^9.0.3"
33+
pytest-cov = "^7.1.0"
3434
requests-mock = "^1.12.1"
3535
mypy = "1.20.1"
3636
types-click = "^7.1.8"
37-
types-pyyaml = "^6.0.12.20250915"
38-
types-requests = "^2.32.4.20260107"
39-
boto3-stubs = { extras = ["s3"], version = "^1.42.30" }
37+
types-pyyaml = "^6.0.12.20260518"
38+
types-requests = "^2.33.0.20260518"
39+
boto3-stubs = { extras = ["s3"], version = "^1.42.41" }
4040

4141
[tool.poetry.group.docs.dependencies]
4242
sphinx-rtd-theme = "^3.0.2"

src/gardenlinux/constants.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145

146146
GL_BUG_REPORT_URL = "https://github.com/gardenlinux/gardenlinux/issues"
147147
GL_COMMIT_SPECIAL_VALUES = ("local",)
148+
GL_CONTAINER_REGISTRY_BASE_URL = "ghcr.io/gardenlinux/gardenlinux"
148149
GL_DEB_REPO_BASE_URL = "https://packages.gardenlinux.io/gardenlinux"
149150
GL_DISTRIBUTION_NAME = "Garden Linux"
150151
GL_HOME_URL = "https://gardenlinux.io"
@@ -157,8 +158,6 @@
157158
OCI_ANNOTATION_SIGNED_STRING_KEY = "io.gardenlinux.oci.signed-string"
158159
OCI_IMAGE_INDEX_MEDIA_TYPE = "application/vnd.oci.image.index.v1+json"
159160

160-
RELEASE_ID_FILE = ".github_release_id"
161-
162161
REQUESTS_TIMEOUTS = (5, 60) # connect, read
163162

164163
S3_DOWNLOADS_DIR = Path(os.path.dirname(__file__)) / ".." / "s3_downloads"
@@ -167,7 +166,3 @@
167166
GLVD_BASE_URL = "https://security.gardenlinux.org/v1"
168167

169168
PODMAN_CONNECTION_MAX_IDLE_SECONDS = 3
170-
171-
# https://github.com/gardenlinux/gardenlinux/issues/3044
172-
# Empty string is the 'legacy' variant with traditional root fs and still needed/supported
173-
IMAGE_VARIANTS = ["", "_usi", "_tpm2_trustedboot"]

src/gardenlinux/distro_version.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from semver import Version
2+
3+
4+
class UnsupportedDistroVersion(Exception):
5+
pass
6+
7+
8+
class NotAPatchRelease(Exception):
9+
pass
10+
11+
12+
class DistroVersion(Version): # type: ignore[misc]
13+
def __init__(self, version: str | Version):
14+
self._version_format_without_patch_number = False
15+
16+
try:
17+
if isinstance(version, Version):
18+
version_parsed = version
19+
elif len(version.split(".")) == 2:
20+
# Support version strings without patch numbers
21+
version_parsed = Version.parse(f"{version}.0")
22+
self._version_format_without_patch_number = True
23+
else:
24+
version_parsed = Version.parse(version)
25+
except Exception as exc:
26+
raise UnsupportedDistroVersion(exc)
27+
28+
Version.__init__(
29+
self,
30+
version_parsed.major,
31+
version_parsed.minor,
32+
version_parsed.patch,
33+
version_parsed.prerelease,
34+
version_parsed.build,
35+
)
36+
37+
@property
38+
def is_patch_release(self) -> bool:
39+
if self._version_format_without_patch_number:
40+
return self.minor > 0 # type: ignore[no-any-return]
41+
42+
return self.patch > 0 # type: ignore[no-any-return]
43+
44+
@property
45+
def previous_patch_release(self) -> str:
46+
if not self.is_patch_release:
47+
raise NotAPatchRelease(f"{self} is not a patch release")
48+
49+
if self._version_format_without_patch_number:
50+
previous_version = DistroVersion(
51+
Version(self.major, self.minor - 1, self.patch)
52+
)
53+
return f"{previous_version.major}.{previous_version.minor}"
54+
55+
return str(
56+
Version(
57+
self.major,
58+
self.minor,
59+
self.patch - 1,
60+
prerelease=self.prerelease,
61+
build=self.build,
62+
)
63+
)

src/gardenlinux/distro_version/__init__.py

Lines changed: 0 additions & 73 deletions
This file was deleted.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
Git credentials provider
5+
"""
6+
7+
from typing import Any, Optional
8+
9+
from pygit2 import KeypairFromAgent
10+
from pygit2 import RemoteCallbacks as _RemoteCallbacks
11+
from pygit2 import UserPass
12+
from pygit2.enums import CredentialType
13+
14+
15+
class RemoteCallbacks(_RemoteCallbacks): # type: ignore[misc]
16+
"""
17+
pygit2.org: Base class for pygit2 remote callbacks.
18+
19+
:author: Garden Linux Maintainers
20+
:copyright: Copyright 2024 SAP SE
21+
:package: gardenlinux
22+
:subpackage: git
23+
:since: 1.0.0
24+
:license: https://www.apache.org/licenses/LICENSE-2.0
25+
Apache License, Version 2.0
26+
"""
27+
28+
def __init__(
29+
self,
30+
*args: Any,
31+
username: Optional[str] = None,
32+
password: Optional[str] = None,
33+
**kwargs: Any,
34+
):
35+
"""
36+
Constructor __init__(RemoteCallbacks)
37+
38+
:param token: GitHub/Git access token for HTTPS authentication.
39+
Falls back to the GITHUB_TOKEN environment variable if not provided.
40+
41+
:since: 1.0.0
42+
"""
43+
44+
self._username = ""
45+
self._password = ""
46+
47+
if username and password:
48+
self._username = username
49+
self._password = password
50+
51+
def credentials(
52+
self,
53+
url: str,
54+
username_from_url: Optional[str],
55+
allowed_types: CredentialType,
56+
) -> Optional[UserPass | KeypairFromAgent]:
57+
"""
58+
pygit2.org: Credentials callback. If the remote server requires
59+
authentication, this function will be called and its return value
60+
used for authentication.
61+
62+
:param url: The URL being accessed (after any insteadOf rewrites)
63+
:param username_from_url: Username extracted from the URL, if any
64+
:param allowed_types: Bitmask of credential types the server accepts
65+
66+
:return: A pygit2 credential object
67+
:since: 1.0.0
68+
"""
69+
70+
if allowed_types & CredentialType.USERPASS_PLAINTEXT:
71+
if self._password:
72+
return UserPass(self._username, self._password)
73+
74+
if allowed_types & CredentialType.SSH_KEY:
75+
return KeypairFromAgent(username_from_url or "git")
76+
77+
return _RemoteCallbacks.credentials(url, username_from_url, allowed_types)

src/gardenlinux/git/repository.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
from logging import Logger
4-
from os import PathLike
4+
from os import PathLike, environ
55
from pathlib import Path
66
from typing import Any, List, Optional
77

@@ -11,6 +11,7 @@
1111

1212
from ..constants import GL_REPOSITORY_URL
1313
from ..logger import LoggerSetup
14+
from .remote_callbacks import RemoteCallbacks
1415

1516

1617
class Repository(_Repository): # type: ignore[misc]
@@ -135,7 +136,11 @@ def checkout_repo(
135136
)
136137

137138
repo = init_repository(git_directory, origin_url=repo_url)
138-
repo.remotes["origin"].fetch()
139+
repo.remotes["origin"].fetch(
140+
callbacks=RemoteCallbacks(
141+
username=environ.get("GITHUB_TOKEN"), password="x-oauth-basic"
142+
)
143+
)
139144

140145
if commit is None:
141146
refish = f"origin/{branch}"
Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,5 @@
1-
import logging
2-
import sys
3-
4-
from ...constants import RELEASE_ID_FILE
5-
from ...logger import LoggerSetup
1+
from .deployment_platform import DeploymentPlatform
62
from .release import Release
3+
from .release_images_metadata import ReleaseImagesMetadata
74

8-
LOGGER = LoggerSetup.get_logger("gardenlinux.github.release", logging.INFO)
9-
10-
11-
def write_to_release_id_file(release_id: str | int) -> None:
12-
try:
13-
with open(RELEASE_ID_FILE, "w") as file:
14-
file.write(str(release_id))
15-
LOGGER.info(f"Created {RELEASE_ID_FILE} successfully.")
16-
except IOError as e:
17-
LOGGER.error(f"Could not create {RELEASE_ID_FILE} file: {e}")
18-
sys.exit(1)
19-
20-
21-
__all__ = ["Release", "write_to_release_id_file"]
5+
__all__ = ["DeploymentPlatform", "Release", "ReleaseImagesMetadata"]

src/gardenlinux/github/release/__main__.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
from gardenlinux.constants import GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME
55
from gardenlinux.logger import LoggerSetup
66

7-
from ..release_notes import create_github_release_notes
8-
from . import write_to_release_id_file
7+
from .notes import MarkdownGenerator
98
from .release import Release
109

1110
LOGGER = LoggerSetup.get_logger("gardenlinux.github", logging.INFO)
@@ -167,24 +166,24 @@ def main() -> None:
167166
release.is_latest = args.latest
168167
release.create()
169168
elif args.command == "create-with-gl-release-notes":
170-
body = create_github_release_notes(
171-
args.tag, args.commit, GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME
172-
)
169+
release = Release(args.repo, args.owner)
170+
release.tag = args.tag
171+
release.commitish = args.commit
172+
release.is_latest = args.latest
173+
174+
generator = MarkdownGenerator(release, GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME)
173175

174176
if args.dry_run:
175177
print("Dry Run ...")
176178
print("This release would be created:")
177-
print(body)
179+
print(str(generator))
178180
else:
179-
release = Release(args.repo, args.owner)
180-
release.tag = args.tag
181-
release.body = body
182-
release.commitish = args.commit
183-
release.is_latest = args.latest
181+
release.body = str(generator)
184182

185183
release_id = release.create()
186-
write_to_release_id_file(f"{release_id}")
187184
LOGGER.info(f"Release created with ID: {release_id}")
185+
186+
print(f"{release_id}")
188187
elif args.command == "upload":
189188
release = Release.get(args.release_id, repo=args.repo, owner=args.owner)
190189

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"short_name": "ali",
3+
"full_name": "Alibaba Cloud",
4+
"image_extension": "qcow2",
5+
6+
"mapping_type": "regions_list",
7+
"mapping_entry_json": "{\"region\": \"{region_id}\", \"image_id\": \"{image_id}\"}"
8+
}

0 commit comments

Comments
 (0)