Skip to content

Commit 658b417

Browse files
feat(RELEASE-2478): convert collect-gh-params to python
Convert the collect-gh-params managed task to a python script. Assisted-by: Claude Code Signed-off-by: Filip Nikolovski <fnikolov@redhat.com>
1 parent 86975a4 commit 658b417

2 files changed

Lines changed: 306 additions & 0 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python3
2+
"""Collect three parameters for the create-github-release task.
3+
4+
The githubSecret from the Data file, the repository from the
5+
snapshot file, and the release_version from the binaries of the
6+
extract-checksums-from-image task.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
from pathlib import Path
12+
13+
import tekton
14+
from file import load_json_dict
15+
16+
17+
def validate_input_files(
18+
data_file: Path,
19+
snapshot_file: Path,
20+
) -> None:
21+
"""Raise RuntimeError when any required input file is missing."""
22+
for label, path in [("data", data_file), ("snapshot", snapshot_file)]:
23+
if not path.is_file():
24+
raise RuntimeError(f"No valid {label} file was provided.")
25+
26+
27+
def collect_params(
28+
*,
29+
data_file: Path,
30+
snapshot_file: Path,
31+
binaries_path: Path,
32+
result_repository: Path,
33+
result_release_version: Path,
34+
result_github_secret: Path,
35+
) -> int:
36+
"""Collect github parameters and write to results."""
37+
validate_input_files(data_file, snapshot_file)
38+
39+
data = load_json_dict(data_file)
40+
snapshot = load_json_dict(snapshot_file)
41+
42+
github_secret = data["github"]["githubSecret"]
43+
if not github_secret:
44+
raise RuntimeError(
45+
"No valid secret was provided via 'github.githubSecret' key in data."
46+
)
47+
48+
sha_file = next(binaries_path.glob("*_SHA256SUMS"))
49+
stem = sha_file.name.removesuffix("_SHA256SUMS")
50+
parts = stem.rsplit("_", 1)
51+
if len(parts) < 2:
52+
raise RuntimeError(
53+
f"Malformed SHA256SUMS filename: '{sha_file.name}'."
54+
+ " Expected format: '<name>_<version>_SHA256SUMS'"
55+
)
56+
release_version = parts[-1]
57+
58+
repository = snapshot["components"][0]["source"]["git"]["url"]
59+
60+
result_release_version.write_text(release_version, encoding="utf-8")
61+
result_github_secret.write_text(github_secret, encoding="utf-8")
62+
result_repository.write_text(repository, encoding="utf-8")
63+
64+
return 0
65+
66+
67+
def main() -> int:
68+
"""Parse environment variables and collect github parameters."""
69+
data_dir = Path(tekton.require_env("DATA_DIR"))
70+
data_path = tekton.require_env("DATA_PATH")
71+
snapshot_path = tekton.require_env("SNAPSHOT_PATH")
72+
binaries_path = tekton.require_env("BINARIES_PATH")
73+
74+
(
75+
result_repository,
76+
result_release_version,
77+
result_github_secret,
78+
) = tekton.result_paths_from_env(
79+
"RESULT_REPOSITORY", "RESULT_RELEASE_VERSION", "RESULT_GITHUB_SECRET"
80+
)
81+
82+
return collect_params(
83+
data_file=data_dir / data_path,
84+
snapshot_file=data_dir / snapshot_path,
85+
binaries_path=data_dir / binaries_path,
86+
result_repository=result_repository,
87+
result_release_version=result_release_version,
88+
result_github_secret=result_github_secret,
89+
)
90+
91+
92+
if __name__ == "__main__": # pragma: no cover
93+
raise SystemExit(main())
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
"""Tests for collect_gh_params."""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
from pathlib import Path
7+
8+
import pytest
9+
10+
import collect_gh_params
11+
12+
13+
def _write_json(path: Path, data: dict) -> None:
14+
path.write_text(json.dumps(data), encoding="utf-8")
15+
16+
17+
def _make_dirs(tmp_path: Path) -> tuple[Path, Path, Path, Path]:
18+
data_dir = tmp_path / "data"
19+
data_dir.mkdir()
20+
binaries = data_dir / "binaries"
21+
binaries.mkdir()
22+
results = tmp_path / "results"
23+
results.mkdir()
24+
return data_dir, binaries, results, tmp_path
25+
26+
27+
def _default_data() -> dict:
28+
return {"github": {"githubSecret": "my-secret"}}
29+
30+
31+
def _default_snapshot() -> dict:
32+
return {"components": [{"source": {"git": {"url": "https://github.com/org/repo"}}}]}
33+
34+
35+
def _setup_happy_path(
36+
tmp_path: Path,
37+
*,
38+
data: dict | None = None,
39+
snapshot: dict | None = None,
40+
sha_filename: str = "project_v1.0.0_SHA256SUMS",
41+
) -> dict[str, Path]:
42+
data_dir, binaries, results, _ = _make_dirs(tmp_path)
43+
44+
data_file = data_dir / "data.json"
45+
_write_json(data_file, data or _default_data())
46+
47+
snapshot_file = data_dir / "snapshot.json"
48+
_write_json(snapshot_file, snapshot or _default_snapshot())
49+
50+
(binaries / sha_filename).touch()
51+
52+
return {
53+
"data_file": data_file,
54+
"snapshot_file": snapshot_file,
55+
"binaries_path": binaries,
56+
"result_repository": results / "repository",
57+
"result_release_version": results / "release_version",
58+
"result_github_secret": results / "github_secret",
59+
}
60+
61+
62+
# --- validate_input_files ---
63+
64+
65+
def test_validate_input_files_both_exist(tmp_path: Path) -> None:
66+
"""No error when both files exist."""
67+
data_file = tmp_path / "data.json"
68+
snapshot_file = tmp_path / "snapshot.json"
69+
data_file.touch()
70+
snapshot_file.touch()
71+
72+
collect_gh_params.validate_input_files(data_file, snapshot_file)
73+
74+
75+
def test_validate_input_files_missing_data(tmp_path: Path) -> None:
76+
"""RuntimeError when data file is missing."""
77+
snapshot_file = tmp_path / "snapshot.json"
78+
snapshot_file.touch()
79+
80+
with pytest.raises(RuntimeError, match="No valid data file"):
81+
collect_gh_params.validate_input_files(tmp_path / "missing.json", snapshot_file)
82+
83+
84+
def test_validate_input_files_missing_snapshot(tmp_path: Path) -> None:
85+
"""RuntimeError when snapshot file is missing."""
86+
data_file = tmp_path / "data.json"
87+
data_file.touch()
88+
89+
with pytest.raises(RuntimeError, match="No valid snapshot file"):
90+
collect_gh_params.validate_input_files(data_file, tmp_path / "missing.json")
91+
92+
93+
# --- collect_params happy path ---
94+
95+
96+
def test_collect_params_happy_path(tmp_path: Path) -> None:
97+
"""Writes correct values to all three result files."""
98+
paths = _setup_happy_path(tmp_path)
99+
rc = collect_gh_params.collect_params(**paths)
100+
101+
assert rc == 0
102+
assert paths["result_github_secret"].read_text() == "my-secret"
103+
assert paths["result_repository"].read_text() == "https://github.com/org/repo"
104+
assert paths["result_release_version"].read_text() == "v1.0.0"
105+
106+
107+
# --- collect_params error cases ---
108+
109+
110+
def test_collect_params_missing_github_key(tmp_path: Path) -> None:
111+
"""KeyError when 'github' key is absent from data."""
112+
paths = _setup_happy_path(tmp_path, data={"other": "value"})
113+
114+
with pytest.raises(KeyError, match="github"):
115+
collect_gh_params.collect_params(**paths)
116+
117+
118+
def test_collect_params_empty_github_secret(tmp_path: Path) -> None:
119+
"""RuntimeError when githubSecret is an empty string."""
120+
paths = _setup_happy_path(tmp_path, data={"github": {"githubSecret": ""}})
121+
122+
with pytest.raises(RuntimeError, match="No valid secret"):
123+
collect_gh_params.collect_params(**paths)
124+
125+
126+
def test_collect_params_missing_github_secret_key(tmp_path: Path) -> None:
127+
"""KeyError when 'githubSecret' key is absent."""
128+
paths = _setup_happy_path(tmp_path, data={"github": {"other": "val"}})
129+
130+
with pytest.raises(KeyError, match="githubSecret"):
131+
collect_gh_params.collect_params(**paths)
132+
133+
134+
def test_collect_params_no_sha256sums_file(tmp_path: Path) -> None:
135+
"""StopIteration when no *_SHA256SUMS file exists."""
136+
paths = _setup_happy_path(tmp_path)
137+
sha_file = next(paths["binaries_path"].glob("*_SHA256SUMS"))
138+
sha_file.unlink()
139+
140+
with pytest.raises(StopIteration):
141+
collect_gh_params.collect_params(**paths)
142+
143+
144+
def test_collect_params_empty_components(tmp_path: Path) -> None:
145+
"""IndexError when components list is empty."""
146+
paths = _setup_happy_path(tmp_path, snapshot={"components": []})
147+
148+
with pytest.raises(IndexError):
149+
collect_gh_params.collect_params(**paths)
150+
151+
152+
def test_collect_params_missing_components_key(tmp_path: Path) -> None:
153+
"""KeyError when 'components' key is absent from snapshot."""
154+
paths = _setup_happy_path(tmp_path, snapshot={"other": "value"})
155+
156+
with pytest.raises(KeyError, match="components"):
157+
collect_gh_params.collect_params(**paths)
158+
159+
160+
# --- release version extraction ---
161+
162+
163+
def test_release_version_standard_filename(tmp_path: Path) -> None:
164+
"""Extracts version from standard filename."""
165+
paths = _setup_happy_path(tmp_path, sha_filename="project_v1.2.3_SHA256SUMS")
166+
collect_gh_params.collect_params(**paths)
167+
168+
assert paths["result_release_version"].read_text() == "v1.2.3"
169+
170+
171+
def test_release_version_multiple_underscores(tmp_path: Path) -> None:
172+
"""Extracts version from filename with multiple underscores."""
173+
paths = _setup_happy_path(tmp_path, sha_filename="my_project_name_2.0.0_SHA256SUMS")
174+
collect_gh_params.collect_params(**paths)
175+
176+
assert paths["result_release_version"].read_text() == "2.0.0"
177+
178+
179+
def test_release_version_malformed_filename(tmp_path: Path) -> None:
180+
"""RuntimeError when SHA256SUMS filename has no version segment."""
181+
paths = _setup_happy_path(tmp_path, sha_filename="noversion_SHA256SUMS")
182+
183+
with pytest.raises(RuntimeError, match="Malformed SHA256SUMS filename"):
184+
collect_gh_params.collect_params(**paths)
185+
186+
187+
# --- main ---
188+
189+
190+
def test_main_happy_path(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
191+
"""Returns 0 when all env vars and files are valid."""
192+
paths = _setup_happy_path(tmp_path)
193+
194+
monkeypatch.setenv("DATA_DIR", str(paths["data_file"].parent))
195+
monkeypatch.setenv("DATA_PATH", paths["data_file"].name)
196+
monkeypatch.setenv("SNAPSHOT_PATH", paths["snapshot_file"].name)
197+
monkeypatch.setenv("BINARIES_PATH", "binaries")
198+
monkeypatch.setenv("RESULT_REPOSITORY", str(paths["result_repository"]))
199+
monkeypatch.setenv("RESULT_RELEASE_VERSION", str(paths["result_release_version"]))
200+
monkeypatch.setenv("RESULT_GITHUB_SECRET", str(paths["result_github_secret"]))
201+
202+
assert collect_gh_params.main() == 0
203+
assert paths["result_github_secret"].read_text() == "my-secret"
204+
assert paths["result_repository"].read_text() == "https://github.com/org/repo"
205+
assert paths["result_release_version"].read_text() == "v1.0.0"
206+
207+
208+
def test_main_missing_env_var(monkeypatch: pytest.MonkeyPatch) -> None:
209+
"""SystemExit when a required env var is missing."""
210+
monkeypatch.delenv("DATA_DIR", raising=False)
211+
212+
with pytest.raises(SystemExit):
213+
collect_gh_params.main()

0 commit comments

Comments
 (0)