Skip to content

Commit d4b252e

Browse files
committed
refactor: extract generic utilities into utils.py modules
Move fetch_json, ver_sort_key, is_date, to_date, and filter_supported_eol_entries from redhat.py into a new rhdh_lifecycle/utils.py module. These functions are used by AKS/EKS/GKE/PG scripts that have no relation to the Red Hat Product Life Cycles API. Create rhdh_prow/utils.py with ver_sort_key (subset of lifecycle utils). Restore rhdh_prow/__init__.py to docstring-only. Update all imports across lifecycle and prow scripts. redhat.py re-exports is_date, to_date, ver_sort_key for backward compatibility. Update AGENTS.md shared modules sync rule to reference utils.py. Assisted-by: OpenCode
1 parent 8a51130 commit d4b252e

14 files changed

Lines changed: 106 additions & 84 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ 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.
81+
`skills/prow/scripts/rhdh_prow/utils.py` is a subset of `skills/lifecycle/scripts/rhdh_lifecycle/utils.py`. When modifying either copy, update both to keep them in sync.
8282

8383
## Agent skills
8484

skills/lifecycle/scripts/check_aks_lifecycle.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
from datetime import datetime, timezone
1818

1919
from rhdh_lifecycle.configured_versions import print_configured_versions
20-
from rhdh_lifecycle.redhat import fetch_json, filter_supported_eol_entries, ver_sort_key
2120
from rhdh_lifecycle.repo import resolve_repo_root
21+
from rhdh_lifecycle.utils import fetch_json, filter_supported_eol_entries, ver_sort_key
2222

2323
AKS_API_URL = "https://releases.aks.azure.com/parsed_data.json"
2424
EOL_API_URL = "https://endoflife.date/api/azure-kubernetes-service.json"

skills/lifecycle/scripts/check_eks_lifecycle.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
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
2423
from rhdh_lifecycle.repo import resolve_repo_root
24+
from rhdh_lifecycle.utils import fetch_json, filter_supported_eol_entries
2525

2626
EKS_DOCS_URL = (
2727
"https://raw.githubusercontent.com/awsdocs/amazon-eks-user-guide"

skills/lifecycle/scripts/check_gke_lifecycle.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import sys
1818
from datetime import datetime, timezone
1919

20-
from rhdh_lifecycle.redhat import fetch_json, ver_sort_key
20+
from rhdh_lifecycle.utils import fetch_json, ver_sort_key
2121

2222
API_URL = "https://endoflife.date/api/google-kubernetes-engine.json"
2323

skills/lifecycle/scripts/rhdh_lifecycle/ocp.py

Lines changed: 1 addition & 1 deletion
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, ver_sort_key
15+
from rhdh_lifecycle.utils import is_date, to_date, ver_sort_key
1616

1717

1818
def classify_ocp_versions(api_data, today):

skills/lifecycle/scripts/rhdh_lifecycle/pg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from __future__ import annotations
1414

15-
from rhdh_lifecycle.redhat import fetch_json
15+
from rhdh_lifecycle.utils import fetch_json
1616

1717
PROVIDERS = {
1818
"upstream": "https://endoflife.date/api/postgresql.json",

skills/lifecycle/scripts/rhdh_lifecycle/redhat.py

Lines changed: 2 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import urllib.parse
2222
import urllib.request
2323

24+
from rhdh_lifecycle.utils import is_date, to_date, ver_sort_key # noqa: F401
25+
2426
LIFECYCLE_API_URL = "https://access.redhat.com/product-life-cycles/api/v1/products"
2527

2628
PRODUCT_ALIASES = {
@@ -33,20 +35,6 @@
3335
}
3436

3537

36-
def is_date(val):
37-
"""Return True if val looks like a YYYY-MM-DD date string."""
38-
if not val or not isinstance(val, str):
39-
return False
40-
return bool(re.match(r"^\d{4}-\d{2}-\d{2}", val))
41-
42-
43-
def to_date(val):
44-
"""Extract YYYY-MM-DD from a date string, or None."""
45-
if is_date(val):
46-
return val[:10]
47-
return None
48-
49-
5038
def _phase_date(phases, phase_name):
5139
"""Extract the end_date for a named phase, formatted as YYYY-MM-DD or raw string."""
5240
for p in phases:
@@ -58,59 +46,11 @@ def _phase_date(phases, phase_name):
5846
return "N/A"
5947

6048

61-
def ver_sort_key(version_str):
62-
"""Sort key for version strings like '4.16' or '26.2'."""
63-
try:
64-
return [int(x) for x in version_str.split(".")]
65-
except ValueError:
66-
return [0]
67-
68-
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-
9449
def resolve_product_name(product):
9550
"""Resolve a product alias to the full API product name."""
9651
return PRODUCT_ALIASES.get(product.lower(), product)
9752

9853

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-
11454
def fetch_api(product_name):
11555
"""Fetch raw lifecycle data from the Red Hat Product Life Cycles API.
11656

skills/lifecycle/scripts/rhdh_lifecycle/rhdh.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
from __future__ import annotations
99

10-
from rhdh_lifecycle.redhat import fetch_api, fetch_product_lifecycle, parse_versions, ver_sort_key
10+
from rhdh_lifecycle.redhat import fetch_api, fetch_product_lifecycle, parse_versions
11+
from rhdh_lifecycle.utils import ver_sort_key
1112

1213

1314
def _enrich_rhdh_version(v):
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""Generic utility functions shared across lifecycle scripts.
2+
3+
Provides URL fetching, date parsing, version sorting, and endoflife.date
4+
helpers used by all lifecycle and prow scripts.
5+
6+
Usage:
7+
from rhdh_lifecycle.utils import fetch_json, ver_sort_key
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import json
13+
import re
14+
import sys
15+
import urllib.error
16+
import urllib.request
17+
18+
19+
def fetch_json(url):
20+
"""Fetch JSON from a URL.
21+
22+
Returns the parsed JSON, or None on failure. Shared by all lifecycle
23+
scripts that consume external APIs (endoflife.date, AKS, EKS, etc.).
24+
"""
25+
req = urllib.request.Request(url, headers={"User-Agent": "rhdh-skill"})
26+
try:
27+
with urllib.request.urlopen(req, timeout=30) as resp:
28+
return json.loads(resp.read().decode("utf-8"))
29+
except (urllib.error.URLError, OSError) as exc:
30+
print(f"ERROR: Failed to fetch {url}: {exc}", file=sys.stderr)
31+
return None
32+
33+
34+
def is_date(val):
35+
"""Return True if val looks like a YYYY-MM-DD date string."""
36+
if not val or not isinstance(val, str):
37+
return False
38+
return bool(re.match(r"^\d{4}-\d{2}-\d{2}", val))
39+
40+
41+
def to_date(val):
42+
"""Extract YYYY-MM-DD from a date string, or None."""
43+
if is_date(val):
44+
return val[:10]
45+
return None
46+
47+
48+
def ver_sort_key(version_str):
49+
"""Sort key for version strings like '4.16' or '26.2'."""
50+
try:
51+
return [int(x) for x in version_str.split(".")]
52+
except ValueError:
53+
return [0]
54+
55+
56+
def filter_supported_eol_entries(eol_data, today):
57+
"""Filter endoflife.date entries to those still supported.
58+
59+
Considers both ``eol`` and ``extendedSupport`` fields. Returns the
60+
filtered list sorted by cycle version (newest first).
61+
"""
62+
supported = []
63+
for entry in eol_data:
64+
eol = entry.get("eol", "N/A")
65+
ext = entry.get("extendedSupport", "N/A")
66+
has_support = False
67+
if eol == "N/A":
68+
has_support = True
69+
elif isinstance(eol, bool):
70+
has_support = not eol
71+
elif isinstance(eol, str) and eol > today:
72+
has_support = True
73+
if not has_support and isinstance(ext, str) and ext > today:
74+
has_support = True
75+
if has_support:
76+
supported.append(entry)
77+
supported.sort(key=lambda e: ver_sort_key(e["cycle"]), reverse=True)
78+
return supported

skills/prow/scripts/analyze_coverage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
from datetime import datetime, timezone
2525
from pathlib import Path
2626

27-
from rhdh_prow import ver_sort_key
2827
from rhdh_prow.repo import resolve_repo_root
28+
from rhdh_prow.utils import ver_sort_key
2929
from rhdh_prow.yaml import extract_branch, fetch_yaml, list_yaml_files
3030

3131
POOL_DIR = "clusters/hosted-mgmt/hive/pools/rhdh"

0 commit comments

Comments
 (0)