diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 61f5764f..47bec682 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.20" python_version: description: Python version to setup default: "3.13" diff --git a/pyproject.toml b/pyproject.toml index efa07c38..e78c9a34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "gardenlinux" -version = "0.10.16" +version = "0.10.20" 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" diff --git a/src/gardenlinux/constants.py b/src/gardenlinux/constants.py index fb242093..491494fd 100644 --- a/src/gardenlinux/constants.py +++ b/src/gardenlinux/constants.py @@ -159,7 +159,7 @@ 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" diff --git a/src/gardenlinux/github/release/__init__.py b/src/gardenlinux/github/release/__init__.py index ac7d6fff..26d36b65 100644 --- a/src/gardenlinux/github/release/__init__.py +++ b/src/gardenlinux/github/release/__init__.py @@ -1,58 +1,13 @@ -import json import logging -import os import sys -import requests - -from ...constants import RELEASE_ID_FILE, REQUESTS_TIMEOUTS +from ...constants import RELEASE_ID_FILE from ...logger import LoggerSetup from .release import Release 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: @@ -63,47 +18,4 @@ def write_to_release_id_file(release_id: str | int) -> None: 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__ = ["Release", "write_to_release_id_file"] diff --git a/src/gardenlinux/github/release/__main__.py b/src/gardenlinux/github/release/__main__.py index 013108b9..be76b43d 100644 --- a/src/gardenlinux/github/release/__main__.py +++ b/src/gardenlinux/github/release/__main__.py @@ -6,7 +6,6 @@ from ..release_notes import create_github_release_notes from . import ( - upload_to_github_release_page, write_to_release_id_file, ) from .release import Release @@ -132,6 +131,7 @@ def get_parser() -> argparse.ArgumentParser: upload_parser.add_argument( "--release_id", + type=int, required=True, help="GitHub release ID to upload the file to (required).", ) @@ -149,6 +149,13 @@ 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, + help="Overwrite assets with the same name.", + ) + return parser @@ -169,6 +176,7 @@ def main() -> None: body = create_github_release_notes( args.tag, args.commit, GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME ) + if args.dry_run: print("Dry Run ...") print("This release would be created:") @@ -184,9 +192,16 @@ def main() -> None: write_to_release_id_file(f"{release_id}") LOGGER.info(f"Release created with ID: {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/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/tests/github/conftest.py b/tests/github/conftest.py index 114adcfc..8361738c 100644 --- a/tests/github/conftest.py +++ b/tests/github/conftest.py @@ -29,7 +29,7 @@ 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() diff --git a/tests/github/constants.py b/tests/github/constants.py index 91ea3419..60301f2b 100644 --- a/tests/github/constants.py +++ b/tests/github/constants.py @@ -1,3 +1,30 @@ +from ..constants import TEST_COMMIT, TEST_GARDENLINUX_RELEASE + +TEST_GARDENLINUX_RELEASE_ID = 1 + +RELEASE_JSON = { + "url": f"https://api.github.com/repos/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE_ID}", + "html_url": f"https://github.com/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE}", + "assets_url": f"https://api.github.com/repos/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE_ID}/assets", + "upload_url": f"https://uploads.github.com/repos/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE_ID}/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_github_script.py b/tests/github/test_github_script.py index c3ca2ede..6c51025f 100644 --- a/tests/github/test_github_script.py +++ b/tests/github/test_github_script.py @@ -1,4 +1,5 @@ import sys +from pathlib import Path import pytest import requests_mock @@ -7,7 +8,7 @@ from gardenlinux.constants import GARDENLINUX_GITHUB_RELEASE_BUCKET_NAME from ..constants import TEST_GARDENLINUX_COMMIT, TEST_GARDENLINUX_RELEASE -from .constants import REPO_JSON +from .constants import RELEASE_JSON, REPO_JSON, TEST_GARDENLINUX_RELEASE_ID def test_script_parse_args_wrong_command( @@ -49,23 +50,6 @@ def test_script_parse_args_create_command_required_args( ) -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: @@ -146,32 +130,183 @@ def test_script_create( ), "Expected a release creation confirmation log entry" -def test_script_upload_dry_run( +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", + str(TEST_GARDENLINUX_RELEASE_ID), + "--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", - "--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}"), + sys, "argv", ["gh", "upload", "--owner", "gardenlinux", "--repo", "gardenlinux"] ) - gh.main() + with pytest.raises(SystemExit): + gh.main() captured = capfd.readouterr() - assert captured.out == "dry-run: True\n" + 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/{TEST_GARDENLINUX_RELEASE_ID}", + json=RELEASE_JSON, + status_code=200, + ) + + monkeypatch.setattr( + sys, + "argv", + [ + "gh", + "upload", + "--owner", + "gardenlinux", + "--repo", + "gardenlinux", + "--release_id", + str(TEST_GARDENLINUX_RELEASE_ID), + "--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( + f"//api.github.com:443/repos/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE_ID}", + json=RELEASE_JSON, + status_code=200, + ) + + monkeypatch.setattr( + sys, + "argv", + [ + "gh", + "upload", + "--owner", + "gardenlinux", + "--repo", + "gardenlinux", + "--release_id", + str(TEST_GARDENLINUX_RELEASE_ID), + "--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( + f"//api.github.com:443/repos/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE_ID}", + json=RELEASE_JSON, + status_code=200, + ) + + m.post( + f"//uploads.github.com:443/repos/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE_ID}/assets?label=&name=artifact.log", + json={}, + status_code=201, + ) + + monkeypatch.setattr( + sys, + "argv", + [ + "gh", + "upload", + "--owner", + "gardenlinux", + "--repo", + "gardenlinux", + "--release_id", + str(TEST_GARDENLINUX_RELEASE_ID), + "--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_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"