Skip to content

Commit 1995f73

Browse files
authored
Merge branch 'main' into dependabot/pip/pytest-cov-7.0.0
2 parents 7118296 + 8bd3e3c commit 1995f73

File tree

8 files changed

+317
-55
lines changed

8 files changed

+317
-55
lines changed

poetry.lock

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

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ python = "^3.13"
1212
apt-repo = "^0.5"
1313
boto3 = "^1.40.30"
1414
click = "^8.2.1"
15-
cryptography = "^45.0.6"
15+
cryptography = "^46.0.1"
1616
jsonschema = "^4.25.1"
1717
networkx = "^3.5"
1818
oras = "^0.2.38"
@@ -28,6 +28,7 @@ python-dotenv = "^1.1.1"
2828
pytest = "^8.4.1"
2929
pytest-cov = "^7.0.0"
3030
isort = "^6.0.1"
31+
requests-mock = "^1.12.1"
3132

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

src/gardenlinux/github/__main__.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import argparse
2+
3+
from .release import upload_to_github_release_page
4+
5+
6+
def main():
7+
parser = argparse.ArgumentParser(description="GitHub Release Script")
8+
subparsers = parser.add_subparsers(dest="command")
9+
10+
upload_parser = subparsers.add_parser("upload")
11+
upload_parser.add_argument("--owner", default="gardenlinux")
12+
upload_parser.add_argument("--repo", default="gardenlinux")
13+
upload_parser.add_argument("--release_id", required=True)
14+
upload_parser.add_argument("--file_path", required=True)
15+
upload_parser.add_argument("--dry-run", action="store_true", default=False)
16+
17+
args = parser.parse_args()
18+
19+
if args.command == "upload":
20+
upload_to_github_release_page(
21+
args.owner, args.repo, args.release_id, args.file_path, args.dry_run
22+
)
23+
else:
24+
parser.print_help()
25+
26+
27+
if __name__ == "__main__":
28+
main()
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import os
2+
3+
import requests
4+
5+
from gardenlinux.logger import LoggerSetup
6+
7+
LOGGER = LoggerSetup.get_logger("gardenlinux.github", "INFO")
8+
9+
REQUESTS_TIMEOUTS = (5, 30) # connect, read
10+
11+
12+
def upload_to_github_release_page(
13+
github_owner, github_repo, gardenlinux_release_id, file_to_upload, dry_run
14+
):
15+
if dry_run:
16+
LOGGER.info(
17+
f"Dry run: would upload {file_to_upload} to release {gardenlinux_release_id} in repo {github_owner}/{github_repo}"
18+
)
19+
return
20+
21+
token = os.environ.get("GITHUB_TOKEN")
22+
if not token:
23+
raise ValueError("GITHUB_TOKEN environment variable not set")
24+
25+
headers = {
26+
"Authorization": f"token {token}",
27+
"Content-Type": "application/octet-stream",
28+
}
29+
30+
upload_url = f"https://uploads.github.com/repos/{github_owner}/{github_repo}/releases/{gardenlinux_release_id}/assets?name={os.path.basename(file_to_upload)}"
31+
32+
try:
33+
with open(file_to_upload, "rb") as f:
34+
file_contents = f.read()
35+
except IOError as e:
36+
LOGGER.error(f"Error reading file {file_to_upload}: {e}")
37+
return
38+
39+
response = requests.post(upload_url, headers=headers, data=file_contents, timeout=REQUESTS_TIMEOUTS)
40+
if response.status_code == 201:
41+
LOGGER.info("Upload successful")
42+
else:
43+
LOGGER.error(
44+
f"Upload failed with status code {response.status_code}: {response.text}"
45+
)
46+
response.raise_for_status()

tests/constants.py

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

3+
import os
4+
from pathlib import Path
5+
36
from gardenlinux.git import Repository
47

58
TEST_DATA_DIR = "test-data"
@@ -20,3 +23,7 @@
2023
TEST_COMMIT = Repository(GL_ROOT_DIR).commit_id[:8]
2124
TEST_VERSION = "1000.0"
2225
TEST_VERSION_STABLE = "1000"
26+
27+
TEST_GARDENLINUX_RELEASE = "1877.3"
28+
29+
S3_DOWNLOADS_DIR = Path(os.path.dirname(__file__)) / ".." / "s3_downloads"

tests/github/__init__.py

Whitespace-only changes.

tests/github/conftest.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import os
2+
import shutil
3+
4+
import pytest
5+
6+
from ..constants import S3_DOWNLOADS_DIR
7+
8+
9+
@pytest.fixture
10+
def downloads_dir():
11+
os.makedirs(S3_DOWNLOADS_DIR, exist_ok=True)
12+
yield
13+
shutil.rmtree(S3_DOWNLOADS_DIR)
14+
15+
16+
@pytest.fixture
17+
def github_token():
18+
os.environ["GITHUB_TOKEN"] = "foobarbazquux"
19+
yield
20+
del os.environ["GITHUB_TOKEN"]
21+
22+
23+
@pytest.fixture
24+
def artifact_for_upload(downloads_dir):
25+
artifact = S3_DOWNLOADS_DIR / "artifact.log"
26+
artifact.touch()
27+
yield artifact
28+
artifact.unlink()
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import sys
2+
3+
import pytest
4+
import requests
5+
import requests_mock
6+
7+
import gardenlinux.github.__main__ as gh
8+
from gardenlinux.github.release import upload_to_github_release_page
9+
10+
from ..constants import TEST_GARDENLINUX_RELEASE
11+
12+
13+
def test_upload_to_github_release_page_dryrun(caplog, artifact_for_upload):
14+
with requests_mock.Mocker():
15+
assert upload_to_github_release_page(
16+
"gardenlinux",
17+
"gardenlinux",
18+
TEST_GARDENLINUX_RELEASE,
19+
artifact_for_upload,
20+
dry_run=True) is None
21+
assert any("Dry run: would upload" in record.message for record in caplog.records), "Expected a dry‑run log entry"
22+
23+
24+
def test_upload_to_github_release_page_needs_github_token(downloads_dir, artifact_for_upload):
25+
with requests_mock.Mocker():
26+
with pytest.raises(ValueError) as exn:
27+
upload_to_github_release_page(
28+
"gardenlinux",
29+
"gardenlinux",
30+
TEST_GARDENLINUX_RELEASE,
31+
artifact_for_upload,
32+
dry_run=False)
33+
assert str(exn.value) == "GITHUB_TOKEN environment variable not set", \
34+
"Expected an exception to be raised on missing GITHUB_TOKEN environment variable"
35+
36+
37+
def test_upload_to_github_release_page(downloads_dir, caplog, github_token, artifact_for_upload):
38+
with requests_mock.Mocker(real_http=True) as m:
39+
m.post(
40+
f"https://uploads.github.com/repos/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE}/assets?name=artifact.log",
41+
text="{}",
42+
status_code=201
43+
)
44+
45+
upload_to_github_release_page(
46+
"gardenlinux",
47+
"gardenlinux",
48+
TEST_GARDENLINUX_RELEASE,
49+
artifact_for_upload,
50+
dry_run=False)
51+
assert any("Upload successful" in record.message for record in caplog.records), \
52+
"Expected an upload confirmation log entry"
53+
54+
55+
def test_upload_to_github_release_page_unreadable_artifact(downloads_dir, caplog, github_token, artifact_for_upload):
56+
artifact_for_upload.chmod(0)
57+
58+
upload_to_github_release_page(
59+
"gardenlinux",
60+
"gardenlinux",
61+
TEST_GARDENLINUX_RELEASE,
62+
artifact_for_upload,
63+
dry_run=False)
64+
assert any("Error reading file" in record.message for record in caplog.records), \
65+
"Expected an error message log entry"
66+
67+
68+
def test_upload_to_github_release_page_failed(downloads_dir, caplog, github_token, artifact_for_upload):
69+
with requests_mock.Mocker(real_http=True) as m:
70+
m.post(
71+
f"https://uploads.github.com/repos/gardenlinux/gardenlinux/releases/{TEST_GARDENLINUX_RELEASE}/assets?name=artifact.log",
72+
text="{}",
73+
status_code=503
74+
)
75+
76+
with pytest.raises(requests.exceptions.HTTPError):
77+
upload_to_github_release_page(
78+
"gardenlinux",
79+
"gardenlinux",
80+
TEST_GARDENLINUX_RELEASE,
81+
artifact_for_upload,
82+
dry_run=False)
83+
assert any("Upload failed with status code 503:" in record.message for record in caplog.records), \
84+
"Expected an error HTTP status code to be logged"
85+
86+
87+
def test_script_parse_args_wrong_command(monkeypatch, capfd):
88+
monkeypatch.setattr(sys, "argv", ["gh", "rejoice"])
89+
90+
with pytest.raises(SystemExit):
91+
gh.main()
92+
captured = capfd.readouterr()
93+
94+
assert "argument command: invalid choice: 'rejoice'" in captured.err, "Expected help message printed"
95+
96+
97+
def test_script_parse_args_upload_command_required_args(monkeypatch, capfd):
98+
monkeypatch.setattr(sys, "argv", ["gh", "upload", "--owner", "gardenlinux", "--repo", "gardenlinux"])
99+
100+
with pytest.raises(SystemExit):
101+
gh.main()
102+
captured = capfd.readouterr()
103+
104+
assert "the following arguments are required: --release_id, --file_path" in captured.err, \
105+
"Expected help message on missing arguments for 'upload' command"
106+
107+
108+
def test_script_upload_dry_run(monkeypatch, capfd):
109+
monkeypatch.setattr(sys, "argv", ["gh", "upload", "--owner", "gardenlinux", "--repo",
110+
"gardenlinux", "--release_id", TEST_GARDENLINUX_RELEASE, "--file_path", "foo", "--dry-run"])
111+
monkeypatch.setattr("gardenlinux.github.__main__.upload_to_github_release_page",
112+
lambda a1, a2, a3, a4, dry_run: print(f"dry-run: {dry_run}"))
113+
114+
gh.main()
115+
captured = capfd.readouterr()
116+
117+
assert captured.out == "dry-run: True\n"

0 commit comments

Comments
 (0)