Skip to content

Commit 8a51130

Browse files
committed
refactor: deduplicate fetch_json, EOL filtering, and version sort
- Extract shared fetch_json into rhdh_lifecycle.redhat; remove local copies from check_aks_lifecycle.py, check_eks_lifecycle.py, check_gke_lifecycle.py, and pg.py - Extract filter_supported_eol_entries helper to deduplicate the identical EOL cross-verification blocks in AKS and EKS scripts - Replace all inline version sort lambdas with ver_sort_key from rhdh_lifecycle.redhat (lifecycle scripts) and rhdh_prow.__init__ (prow scripts); add AGENTS.md sync rule for the prow copy Assisted-by: OpenCode
1 parent 56e08bf commit 8a51130

11 files changed

Lines changed: 85 additions & 110 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ Bump all three when releasing.
7878

7979
`skills/prow/scripts/rhdh_prow/repo.py` and `skills/prow/scripts/rhdh_prow/yaml.py` are copies of `skills/lifecycle/scripts/rhdh_lifecycle/repo.py` and `skills/lifecycle/scripts/rhdh_lifecycle/yaml.py`. The only difference is the internal import path (`rhdh_prow.repo` vs `rhdh_lifecycle.repo`). When modifying either copy, update both to keep them in sync.
8080

81+
`ver_sort_key` in `skills/prow/scripts/rhdh_prow/__init__.py` is a copy of the same function in `skills/lifecycle/scripts/rhdh_lifecycle/redhat.py`. Keep both in sync.
82+
8183
## Agent skills
8284

8385
### Issue tracker

skills/lifecycle/scripts/check_aks_lifecycle.py

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,17 @@
1414
import argparse
1515
import json
1616
import sys
17-
import urllib.error
18-
import urllib.request
1917
from datetime import datetime, timezone
2018

2119
from rhdh_lifecycle.configured_versions import print_configured_versions
20+
from rhdh_lifecycle.redhat import fetch_json, filter_supported_eol_entries, ver_sort_key
2221
from rhdh_lifecycle.repo import resolve_repo_root
2322

2423
AKS_API_URL = "https://releases.aks.azure.com/parsed_data.json"
2524
EOL_API_URL = "https://endoflife.date/api/azure-kubernetes-service.json"
2625
CONFIG_DIR = "ci-operator/config/redhat-developer/rhdh"
2726

2827

29-
def fetch_json(url):
30-
"""Fetch JSON from a URL."""
31-
req = urllib.request.Request(url, headers={"User-Agent": "rhdh-skill"})
32-
try:
33-
with urllib.request.urlopen(req, timeout=30) as resp:
34-
return json.loads(resp.read().decode("utf-8"))
35-
except (urllib.error.URLError, OSError) as exc:
36-
print(f"ERROR: Failed to fetch {url}: {exc}", file=sys.stderr)
37-
return None
38-
39-
4028
def main(argv=None):
4129
parser = argparse.ArgumentParser(description="Check AKS K8s version lifecycle.")
4230
parser.add_argument("--mapt-ref", help="Path to MAPT ref YAML (repo-relative)")
@@ -83,7 +71,7 @@ def main(argv=None):
8371

8472
sorted_versions = sorted(
8573
minor_versions.items(),
86-
key=lambda x: [int(n) for n in x[0].split(".")],
74+
key=lambda x: ver_sort_key(x[0]),
8775
reverse=True,
8876
)
8977

@@ -98,23 +86,7 @@ def main(argv=None):
9886
# Cross-verify with endoflife.date
9987
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
10088
eol_data = fetch_json(EOL_API_URL)
101-
eol_supported = []
102-
if eol_data:
103-
for entry in eol_data:
104-
eol = entry.get("eol", "N/A")
105-
ext = entry.get("extendedSupport", "N/A")
106-
has_support = False
107-
if eol == "N/A":
108-
has_support = True
109-
elif isinstance(eol, bool):
110-
has_support = not eol
111-
elif isinstance(eol, str) and eol > today:
112-
has_support = True
113-
if not has_support and isinstance(ext, str) and ext > today:
114-
has_support = True
115-
if has_support:
116-
eol_supported.append(entry)
117-
eol_supported.sort(key=lambda e: [int(x) for x in e["cycle"].split(".")], reverse=True)
89+
eol_supported = filter_supported_eol_entries(eol_data, today) if eol_data else []
11890

11991
# JSON output
12092
if args.json_output:

skills/lifecycle/scripts/check_eks_lifecycle.py

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from datetime import datetime, timezone
2121

2222
from rhdh_lifecycle.configured_versions import print_configured_versions
23+
from rhdh_lifecycle.redhat import fetch_json, filter_supported_eol_entries
2324
from rhdh_lifecycle.repo import resolve_repo_root
2425

2526
EKS_DOCS_URL = (
@@ -41,17 +42,6 @@ def fetch_text(url):
4142
return None
4243

4344

44-
def fetch_json(url):
45-
"""Fetch JSON from a URL."""
46-
req = urllib.request.Request(url, headers={"User-Agent": "rhdh-skill"})
47-
try:
48-
with urllib.request.urlopen(req, timeout=30) as resp:
49-
return json.loads(resp.read().decode("utf-8"))
50-
except (urllib.error.URLError, OSError) as exc:
51-
print(f"ERROR: Failed to fetch {url}: {exc}", file=sys.stderr)
52-
return None
53-
54-
5545
def parse_supported_versions(docs):
5646
"""Extract supported versions and their tiers from the AsciiDoc."""
5747
section = ""
@@ -125,23 +115,7 @@ def main(argv=None):
125115
# Cross-verify with endoflife.date
126116
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
127117
eol_data = fetch_json(EOL_API_URL)
128-
eol_supported = []
129-
if eol_data:
130-
for entry in eol_data:
131-
eol = entry.get("eol", "N/A")
132-
ext = entry.get("extendedSupport", "N/A")
133-
has_support = False
134-
if eol == "N/A":
135-
has_support = True
136-
elif isinstance(eol, bool):
137-
has_support = not eol
138-
elif isinstance(eol, str) and eol > today:
139-
has_support = True
140-
if not has_support and isinstance(ext, str) and ext > today:
141-
has_support = True
142-
if has_support:
143-
eol_supported.append(entry)
144-
eol_supported.sort(key=lambda e: [int(x) for x in e["cycle"].split(".")], reverse=True)
118+
eol_supported = filter_supported_eol_entries(eol_data, today) if eol_data else []
145119

146120
# JSON output
147121
if args.json_output:

skills/lifecycle/scripts/check_gke_lifecycle.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,11 @@
1515
import argparse
1616
import json
1717
import sys
18-
import urllib.error
19-
import urllib.request
2018
from datetime import datetime, timezone
2119

22-
API_URL = "https://endoflife.date/api/google-kubernetes-engine.json"
23-
20+
from rhdh_lifecycle.redhat import fetch_json, ver_sort_key
2421

25-
def fetch_api():
26-
"""Fetch GKE lifecycle data from endoflife.date."""
27-
req = urllib.request.Request(API_URL, headers={"User-Agent": "rhdh-skill"})
28-
try:
29-
with urllib.request.urlopen(req, timeout=30) as resp:
30-
return json.loads(resp.read().decode("utf-8"))
31-
except (urllib.error.URLError, OSError) as exc:
32-
print(f"ERROR: Failed to fetch {API_URL}: {exc}", file=sys.stderr)
33-
sys.exit(1)
22+
API_URL = "https://endoflife.date/api/google-kubernetes-engine.json"
3423

3524

3625
def is_supported(entry, today):
@@ -66,10 +55,12 @@ def main(argv=None):
6655
args = parser.parse_args(argv)
6756

6857
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
69-
data = fetch_api()
58+
data = fetch_json(API_URL)
59+
if not data:
60+
sys.exit(1)
7061

7162
supported = [e for e in data if is_supported(e, today)]
72-
supported.sort(key=lambda e: [int(x) for x in e["cycle"].split(".")], reverse=True)
63+
supported.sort(key=lambda e: ver_sort_key(e["cycle"]), reverse=True)
7364

7465
if args.json_output:
7566
result = []

skills/lifecycle/scripts/rhdh_lifecycle/ocp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import re
1414

15-
from rhdh_lifecycle.redhat import is_date, to_date
15+
from rhdh_lifecycle.redhat import is_date, to_date, ver_sort_key
1616

1717

1818
def classify_ocp_versions(api_data, today):
@@ -100,5 +100,5 @@ def classify_ocp_versions(api_data, today):
100100
)
101101

102102
# Sort by version
103-
results.sort(key=lambda v: [int(x) for x in v["version"].split(".")])
103+
results.sort(key=lambda v: ver_sort_key(v["version"]))
104104
return results

skills/lifecycle/scripts/rhdh_lifecycle/pg.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@
1212

1313
from __future__ import annotations
1414

15-
import json
16-
import sys
17-
import urllib.error
18-
import urllib.request
15+
from rhdh_lifecycle.redhat import fetch_json
1916

2017
PROVIDERS = {
2118
"upstream": "https://endoflife.date/api/postgresql.json",
@@ -24,17 +21,6 @@
2421
}
2522

2623

27-
def _fetch_json(url):
28-
"""Fetch JSON from a URL, return None on failure."""
29-
req = urllib.request.Request(url, headers={"User-Agent": "rhdh-skill"})
30-
try:
31-
with urllib.request.urlopen(req, timeout=30) as resp:
32-
return json.loads(resp.read().decode("utf-8"))
33-
except (urllib.error.URLError, OSError) as exc:
34-
print(f"WARNING: Failed to fetch {url}: {exc}", file=sys.stderr)
35-
return None
36-
37-
3824
def _normalize_eol(val):
3925
"""Normalize EOL value to a date string or 'N/A'."""
4026
if val is None or val == "N/A":
@@ -56,7 +42,7 @@ def fetch_pg_lifecycle(today=None):
5642
# Fetch all providers
5743
provider_data = {}
5844
for provider, url in PROVIDERS.items():
59-
data = _fetch_json(url)
45+
data = fetch_json(url)
6046
if data:
6147
provider_data[provider] = {str(e["cycle"]): e for e in data}
6248
else:

skills/lifecycle/scripts/rhdh_lifecycle/redhat.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,51 @@ def ver_sort_key(version_str):
6666
return [0]
6767

6868

69+
def filter_supported_eol_entries(eol_data, today):
70+
"""Filter endoflife.date entries to those still supported.
71+
72+
Considers both ``eol`` and ``extendedSupport`` fields. Returns the
73+
filtered list sorted by cycle version (newest first).
74+
"""
75+
supported = []
76+
for entry in eol_data:
77+
eol = entry.get("eol", "N/A")
78+
ext = entry.get("extendedSupport", "N/A")
79+
has_support = False
80+
if eol == "N/A":
81+
has_support = True
82+
elif isinstance(eol, bool):
83+
has_support = not eol
84+
elif isinstance(eol, str) and eol > today:
85+
has_support = True
86+
if not has_support and isinstance(ext, str) and ext > today:
87+
has_support = True
88+
if has_support:
89+
supported.append(entry)
90+
supported.sort(key=lambda e: ver_sort_key(e["cycle"]), reverse=True)
91+
return supported
92+
93+
6994
def resolve_product_name(product):
7095
"""Resolve a product alias to the full API product name."""
7196
return PRODUCT_ALIASES.get(product.lower(), product)
7297

7398

99+
def fetch_json(url):
100+
"""Fetch JSON from a URL.
101+
102+
Returns the parsed JSON, or None on failure. Shared by all lifecycle
103+
scripts that consume external APIs (endoflife.date, AKS, EKS, etc.).
104+
"""
105+
req = urllib.request.Request(url, headers={"User-Agent": "rhdh-skill"})
106+
try:
107+
with urllib.request.urlopen(req, timeout=30) as resp:
108+
return json.loads(resp.read().decode("utf-8"))
109+
except (urllib.error.URLError, OSError) as exc:
110+
print(f"ERROR: Failed to fetch {url}: {exc}", file=sys.stderr)
111+
return None
112+
113+
74114
def fetch_api(product_name):
75115
"""Fetch raw lifecycle data from the Red Hat Product Life Cycles API.
76116

skills/prow/scripts/analyze_coverage.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from datetime import datetime, timezone
2525
from pathlib import Path
2626

27+
from rhdh_prow import ver_sort_key
2728
from rhdh_prow.repo import resolve_repo_root
2829
from rhdh_prow.yaml import extract_branch, fetch_yaml, list_yaml_files
2930

@@ -32,11 +33,6 @@
3233
LIFECYCLE_API_URL = "https://access.redhat.com/product-life-cycles/api/v1/products"
3334

3435

35-
def ver_key(v):
36-
"""Sort key for version strings like '4.16'."""
37-
return [int(x) for x in v.split(".")]
38-
39-
4036
def _fetch_lifecycle_json(script_name):
4137
"""Run a lifecycle script with --json and return parsed output.
4238
@@ -97,7 +93,7 @@ def _get_rhdh_lifecycle():
9793
"ocp_versions": ocp_versions,
9894
}
9995
)
100-
results.sort(key=lambda v: ver_key(v["version"]) if "." in v["version"] else [0])
96+
results.sort(key=lambda v: ver_sort_key(v["version"]) if "." in v["version"] else [0])
10197
return results
10298

10399

@@ -166,7 +162,7 @@ def _to_date(val):
166162
"phase": current_phase,
167163
}
168164
)
169-
results.sort(key=lambda v: ver_key(v["version"]))
165+
results.sort(key=lambda v: ver_sort_key(v["version"]))
170166
return results
171167

172168

@@ -228,15 +224,15 @@ def main(argv=None):
228224
for t in data["tests"]
229225
if t.get("cluster_claim", {}).get("version")
230226
},
231-
key=ver_key,
227+
key=ver_sort_key,
232228
)
233229
if versions:
234230
branch_versions[branch] = versions
235231
all_test_versions.extend(versions)
236232
print(f" {branch}: {' '.join(versions)}")
237233
print()
238234

239-
unique_test_versions = sorted(set(all_test_versions), key=ver_key)
235+
unique_test_versions = sorted(set(all_test_versions), key=ver_sort_key)
240236

241237
# ------- 3. RHDH lifecycle -------
242238
print("--- RHDH Lifecycle ---")
@@ -254,7 +250,7 @@ def main(argv=None):
254250

255251
rhdh_supported_ocp = sorted(
256252
{ocp for v in rhdh_data if v["supported"] for ocp in v.get("ocp_versions", [])},
257-
key=ver_key,
253+
key=ver_sort_key,
258254
)
259255
print()
260256
print(f" OCP versions supported by active RHDH releases: {' '.join(rhdh_supported_ocp)}")
@@ -285,14 +281,14 @@ def main(argv=None):
285281

286282
# Compute "main" branch OCP support
287283
if latest_rhdh_ocp:
288-
max_rhdh = max(latest_rhdh_ocp, key=ver_key)
289-
max_parts = ver_key(max_rhdh)
284+
max_rhdh = max(latest_rhdh_ocp, key=ver_sort_key)
285+
max_parts = ver_sort_key(max_rhdh)
290286
main_ocp = list(latest_rhdh_ocp)
291287
for ocp_ver in ocp_supported:
292-
ocp_parts = ver_key(ocp_ver)
288+
ocp_parts = ver_sort_key(ocp_ver)
293289
if ocp_parts > max_parts and ocp_ver not in main_ocp:
294290
main_ocp.append(ocp_ver)
295-
rhdh_branch_ocp["main"] = sorted(main_ocp, key=ver_key)
291+
rhdh_branch_ocp["main"] = sorted(main_ocp, key=ver_sort_key)
296292

297293
# ------- 5. OCP version matrix -------
298294
print("--- OCP Version Matrix ---")
@@ -306,7 +302,7 @@ def main(argv=None):
306302

307303
all_relevant = sorted(
308304
set(pool_versions + unique_test_versions + rhdh_supported_ocp + ocp_supported),
309-
key=ver_key,
305+
key=ver_sort_key,
310306
)
311307

312308
ocp_phase_map = {v["version"]: v for v in ocp_lifecycle}
@@ -379,7 +375,7 @@ def main(argv=None):
379375
print("--- RHDH-Supported OCP Versions Missing Pools (ADD) ---")
380376
all_rhdh_ocp = sorted(
381377
{ver for ocp_list in rhdh_branch_ocp.values() for ver in ocp_list},
382-
key=ver_key,
378+
key=ver_sort_key,
383379
)
384380
for ver in all_rhdh_ocp:
385381
if ver in ocp_eol:

skills/prow/scripts/list_cluster_pools.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import sys
1212
from pathlib import Path
1313

14+
from rhdh_prow import ver_sort_key
1415
from rhdh_prow.repo import resolve_repo_root
1516
from rhdh_prow.yaml import fetch_yaml, list_yaml_files
1617

@@ -63,7 +64,7 @@ def main(argv=None):
6364
rows.append((ver, pool_name, size, max_size, running, image_set, filename))
6465

6566
# Sort by version
66-
rows.sort(key=lambda r: [int(x) for x in r[0].split(".")])
67+
rows.sort(key=lambda r: ver_sort_key(r[0]))
6768
for ver, pool_name, size, max_size, running, image_set, filename in rows:
6869
print(
6970
f" {ver:<8s} {pool_name:<50s} {size:<5s} {max_size:<5s} "

0 commit comments

Comments
 (0)