-
Notifications
You must be signed in to change notification settings - Fork 0
feat(rhel): add Red Hat Enterprise Linux support #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8260bcc
d6213f6
bb6b739
a34e680
439d9e0
a2df9d0
0a68ef6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| { | ||
| "6": { | ||
| "distribution": "rhel", | ||
| "version": "6", | ||
| "begin_support": "2010-11-10", | ||
| "end_support": "2020-11-30", | ||
| "begin_dev": "2010-11-10", | ||
| "end_extended_support": "2024-06-30" | ||
| }, | ||
| "7": { | ||
| "distribution": "rhel", | ||
| "version": "7", | ||
| "begin_support": "2014-06-10", | ||
| "end_support": "2024-06-30", | ||
| "begin_dev": "2014-06-10", | ||
| "end_extended_support": "2029-05-31" | ||
| }, | ||
| "8": { | ||
| "distribution": "rhel", | ||
| "version": "8", | ||
| "begin_support": "2019-05-07", | ||
| "end_support": "2029-05-31", | ||
| "begin_dev": "2019-05-07", | ||
| "end_extended_support": "2032-05-31" | ||
| }, | ||
| "9": { | ||
| "distribution": "rhel", | ||
| "version": "9", | ||
| "begin_support": "2022-05-18", | ||
| "end_support": "2032-05-31", | ||
| "begin_dev": "2022-05-18", | ||
| "end_extended_support": "2035-05-31" | ||
| }, | ||
| "10": { | ||
| "distribution": "rhel", | ||
| "version": "10", | ||
| "begin_support": "2025-05-20", | ||
| "end_support": "2035-05-31", | ||
| "begin_dev": "2025-05-20", | ||
| "end_extended_support": "2038-05-31" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||||||||
| """Information about Red Hat Enterprise Linux support.""" | ||||||||||
|
|
||||||||||
| import json | ||||||||||
| from urllib import request | ||||||||||
|
|
||||||||||
| SUPPORT_INFO_URL = "https://access.redhat.com/product-life-cycles/api/v1/products?name=Red+Hat+Enterprise+Linux" | ||||||||||
|
|
||||||||||
| _PHASE_GA = "general availability" | ||||||||||
| _PHASE_MAINTENANCE = "maintenance support" | ||||||||||
| _PHASE_ELS = "extended life cycle support (els) add-on" | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def _parse_date(value: str | None) -> str | None: | ||||||||||
| """Return an ISO date string (YYYY-MM-DD), or None for missing/non-date values.""" | ||||||||||
| if not value or value.upper() == "N/A" or value.lower() == "ongoing": | ||||||||||
| return None | ||||||||||
| return value[:10] | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def get_distro_info() -> dict[str, dict[str, str | None]]: | ||||||||||
| req = request.Request(SUPPORT_INFO_URL, headers={"User-Agent": "distro-support"}) | ||||||||||
| with request.urlopen(req) as response: # nosec B310 | ||||||||||
|
Check warning on line 22 in src/distro_support/rhel.py
|
||||||||||
| if response.status != 200: | ||||||||||
| raise RuntimeError( | ||||||||||
| f"Unexpected HTTP status from Red Hat API: {response.status}" | ||||||||||
| ) | ||||||||||
|
Comment on lines
+24
to
+26
|
||||||||||
| raise RuntimeError( | |
| f"Unexpected HTTP status from Red Hat API: {response.status}" | |
| ) | |
| raise ConnectionError(response.status) |
Copilot
AI
Apr 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR description notes begin_dev is set to the GA date so is_in_development_on always returns False, but if GA is missing (_parse_date() returns None for "N/A"/"Ongoing"), then begin_dev becomes None and SupportRange.is_in_development_on() will raise NoDevelopmentInfoError. Either ensure GA is always present for supported RHEL versions (so this invariant holds), or adjust the stated behavior/tests to reflect that development info may be unavailable when GA is missing.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,178 @@ | ||||||
| """Tests for the RHEL downloader.""" | ||||||
|
|
||||||
| import json | ||||||
| from unittest.mock import MagicMock, patch | ||||||
|
|
||||||
| import pytest | ||||||
|
|
||||||
| from distro_support.rhel import _parse_date, get_distro_info | ||||||
|
|
||||||
| # Minimal realistic API response mirroring the Red Hat lifecycle API structure. | ||||||
| _FAKE_API_RESPONSE = { | ||||||
| "data": [ | ||||||
| { | ||||||
| "versions": [ | ||||||
| { | ||||||
| "name": "9", | ||||||
| "phases": [ | ||||||
| { | ||||||
| "name": "General availability", | ||||||
| "end_date": "2022-05-18T00:00:00.000Z", | ||||||
| }, | ||||||
| { | ||||||
| "name": "Full support", | ||||||
| "end_date": "2027-05-31T00:00:00.000Z", | ||||||
| }, | ||||||
| { | ||||||
| "name": "Maintenance support", | ||||||
| "end_date": "2032-05-31T00:00:00.000Z", | ||||||
| }, | ||||||
| { | ||||||
| "name": "Extended life cycle support (ELS) add-on", | ||||||
| "end_date": "2035-05-31T00:00:00.000Z", | ||||||
| }, | ||||||
| { | ||||||
| "name": "Extended life phase", | ||||||
| "end_date": "Ongoing", | ||||||
| }, | ||||||
| ], | ||||||
| }, | ||||||
| { | ||||||
| "name": "7", | ||||||
| "phases": [ | ||||||
| { | ||||||
| "name": "General availability", | ||||||
| "end_date": "2014-06-10T00:00:00.000Z", | ||||||
| }, | ||||||
| { | ||||||
| "name": "Full support", | ||||||
| "end_date": "2019-08-06T00:00:00.000Z", | ||||||
| }, | ||||||
| { | ||||||
| "name": "Maintenance support", | ||||||
| "end_date": "2024-06-30T00:00:00.000Z", | ||||||
| }, | ||||||
| { | ||||||
| "name": "Extended life cycle support (ELS) add-on", | ||||||
| "end_date": "2029-05-31T00:00:00.000Z", | ||||||
| }, | ||||||
| { | ||||||
| "name": "Extended life phase", | ||||||
| "end_date": "Ongoing", | ||||||
| }, | ||||||
| ], | ||||||
| }, | ||||||
| { | ||||||
| # Version with N/A GA date (as seen in some older entries) | ||||||
| "name": "6", | ||||||
| "phases": [ | ||||||
| { | ||||||
| "name": "General availability", | ||||||
| "end_date": "N/A", | ||||||
| }, | ||||||
| { | ||||||
| "name": "Maintenance support", | ||||||
| "end_date": "2020-11-30T00:00:00.000Z", | ||||||
| }, | ||||||
| # No ELS phase for this entry | ||||||
| ], | ||||||
| }, | ||||||
| ] | ||||||
| } | ||||||
| ] | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
| def _make_mock_response(data: dict): | ||||||
| mock_response = MagicMock() | ||||||
| mock_response.status = 200 | ||||||
| mock_response.read.return_value = json.dumps(data).encode() | ||||||
| mock_response.__enter__ = lambda s: s | ||||||
|
||||||
| mock_response.__enter__ = lambda s: s | |
| mock_response.__enter__ = MagicMock(return_value=mock_response) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
urlopen()is called without a timeout, so an unresponsive Red Hat API can hang indefinitely. The Alpine downloader uses an explicit timeout; consider adding a reasonable timeout here as well to avoid blocking callers whenget_online=True.