-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy patheol.py
167 lines (139 loc) · 4.08 KB
/
eol.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import datetime
import dateutil.parser
import typing
import cachetools
import requests
import ci.util
import delivery.model as dm
def normalise_os_id(os_id: str) -> str:
'''
Some product identifiers differ from the ones we know.
This function translates known difference from "our" identifier to the
one EOL API can process.
'''
if os_id == 'amzn':
return 'amazon-linux'
return os_id
def os_release_info_from_release_cycle(
release_cycle: dict,
) -> dm.OsReleaseInfo:
def eol_date() -> bool | datetime.datetime | None:
eol_date = release_cycle.get('extendedSupport')
if eol_date is None:
eol_date = release_cycle.get('eol')
# unfortunately, eol-api yields inconsistent values for `eol` attribute (bool vs timestamp)
if isinstance(eol_date, bool):
return eol_date
elif isinstance(eol_date, str):
return dateutil.parser.isoparse(eol_date)
else:
return None
def reached_eol(
eol_date: datetime.datetime | bool=eol_date(),
) -> bool | None:
if isinstance(eol_date, bool):
return eol_date
elif isinstance(eol_date, datetime.datetime):
return eol_date < datetime.datetime.today()
else:
return None
return dm.OsReleaseInfo(
name=release_cycle['cycle'],
# not provided for all products
greatest_version=release_cycle.get('latest'),
eol_date=eol_date(),
reached_eol=reached_eol(),
)
class EolRoutes:
def __init__(
self,
base_url: str = 'https://endoflife.date/api',
):
self._base_url = base_url
def all_products(self):
return ci.util.urljoin(
self._base_url,
'all.json',
)
def cycles(
self,
product: str,
):
return ci.util.urljoin(
self._base_url,
f'{product}.json',
)
def cycle(
self,
cycle: int,
product: str,
):
return ci.util.urljoin(
self._base_url,
product,
f'{cycle}.json',
)
class EolClient:
'''
API client for https://endoflife.date/docs/api.
'''
def __init__(
self,
routes: EolRoutes = EolRoutes(),
):
self._routes = routes
@cachetools.cached(cachetools.TTLCache(maxsize=1, ttl=60 * 60 * 24)) # 24h
def all_products(self) -> typing.List[str]:
res = requests.get(url=self._routes.all_products())
res.raise_for_status()
return res.json()
@cachetools.cached(cachetools.TTLCache(maxsize=200, ttl=60 * 60 * 24)) # 24h
def cycles(
self,
product: str,
absent_ok: bool = False,
) -> typing.Optional[list[dict]]:
'''
Returns release_cycles as described here https://endoflife.date/docs/api.
If `absent_ok`, HTTP 404 returns `None`.
'''
res = requests.get(
url=self._routes.cycles(
product=product,
)
)
try:
res.raise_for_status()
except requests.exceptions.HTTPError as e:
if not absent_ok:
raise
if e.response.status_code == 404:
return None
raise
return res.json()
@cachetools.cached(cachetools.TTLCache(maxsize=200, ttl=60 * 60 * 24)) # 24h
def cycle(
self,
product: str,
cycle: str,
absent_ok: bool = False,
) -> typing.Optional[dict]:
'''
Returns single release_cycle as described here https://endoflife.date/docs/api.
If `absent_ok`, HTTP 404 returns `None`.
'''
res = requests.get(
url=self._routes.cycle(
product=product,
cycle=cycle,
),
)
try:
res.raise_for_status()
except requests.exceptions.HTTPError as e:
if not absent_ok:
raise
if e.response.status_code == 404:
return None
raise
return res.json()