forked from konflux-ci/release-service-utils
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pyxis.py
237 lines (185 loc) · 6.82 KB
/
pyxis.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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import logging
import os
import sys
from typing import Any, Dict, Optional, Tuple
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import requests
LOGGER = logging.getLogger("pyxis")
session = None
def _get_session(auth_required: bool = True) -> requests.Session:
"""Create a Pyxis http session with auth based on env variables.
Auth is optional and can be set to use either API key or certificate + key.
Args:
auth_required (bool): Whether authentication should be required for the session
Raises:
Exception: Exception is raised when auth ENV variables are missing.
:return: Pyxis session
"""
cert_string = "PYXIS_CERT_PATH"
key_string = "PYXIS_KEY_PATH"
cert = os.environ.get(cert_string)
key = os.environ.get(key_string)
session = requests.Session()
add_session_retries(session)
if not auth_required:
LOGGER.debug("Pyxis session without authentication is created")
return session
if cert and key:
if os.path.exists(cert) and os.path.exists(key):
LOGGER.debug("Pyxis session using cert + key is created")
session.cert = (cert, key)
else:
raise Exception(
f"{cert_string} or {key_string} does not point to a file that exists."
)
else:
# cert + key need to be provided using env variable
raise Exception(
f"No auth details provided for Pyxis. Define {cert_string} + {key_string}"
)
return session
def post(url: str, body: Dict[str, Any]) -> requests.Response:
"""POST pyxis API request to given URL with given payload
Args:
url (str): Pyxis API URL
body (Dict[str, Any]): Request payload
:return: Pyxis response
"""
global session
if session is None:
session = _get_session()
LOGGER.debug(f"POST request URL: {url}")
LOGGER.debug(f"POST request body: {body}")
resp = session.post(url, json=body)
try:
LOGGER.debug(f"POST request response: {resp.text}")
resp.raise_for_status()
except requests.HTTPError:
LOGGER.exception(
f"Pyxis POST query failed with {url} - {resp.status_code} - {resp.text}"
)
raise
return resp
def patch(url: str, body: Dict[str, Any]) -> requests.Response:
"""PATCH pyxis API request to given URL with given payload
Args:
url (str): Pyxis API URL
body (Dict[str, Any]): Request payload
:return: Pyxis response
"""
global session
if session is None:
session = _get_session()
LOGGER.debug(f"PATCH request URL: {url}")
LOGGER.debug(f"PATCH request body: {body}")
resp = session.patch(url, json=body)
try:
LOGGER.debug(f"PATCH request response: {resp.text}")
resp.raise_for_status()
except requests.HTTPError:
LOGGER.exception(
f"Pyxis PATCH query failed with {url} - {resp.status_code} - {resp.text}"
)
raise
return resp
def graphql_query(graphql_api: str, body: Dict[str, Any]) -> Dict[str, Any]:
"""Make a request to Pyxis GraphQL API
This will make a POST request and then check the result
for errors and return the data json if no errors found.
Args:
graphql_api (str): Pyxis GraphQL API URL
body (Dict[str, Any]): Request payload
:return: Pyxis response
"""
resp = post(graphql_api, body)
resp_json = resp.json()
error_msg = "Pyxis GraphQL query failed"
if resp_json.get("data") is None or any(
[query["error"] is not None for query in resp_json["data"].values()]
):
LOGGER.error(error_msg)
LOGGER.error(f"Pyxis response: {resp_json}")
LOGGER.error(f"Pyxis trace_id: {resp.headers.get('trace_id')}")
raise RuntimeError(error_msg)
return resp_json["data"]
def put(url: str, body: Dict[str, Any]) -> Dict[str, Any]:
"""PUT pyxis API request to given URL with given payload
Args:
url (str): Pyxis API URL
body (Dict[str, Any]): Request payload
:return: Pyxis response
"""
global session
if session is None:
session = _get_session()
LOGGER.debug(f"PATCH Pyxis request: {url}")
resp = session.put(url, json=body)
try:
resp.raise_for_status()
except requests.HTTPError:
LOGGER.exception(
f"Pyxis PUT query failed with {url} - {resp.status_code} - {resp.text}"
)
raise
return resp.json()
def get(url: str, params: Optional[Dict[str, str]] = None, auth_required: bool = True) -> Any:
"""Pyxis GET request
Args:
url (str): Pyxis URL
params (dict): Additional query parameters
auth_required (bool): Whether authentication should be required for the session
:return: Pyxis GET request response
"""
global session
if session is None:
session = _get_session()
LOGGER.debug(f"GET Pyxis request url: {url}")
LOGGER.debug(f"GET Pyxis request params: {params}")
resp = session.get(url, params=params)
# Not raising exception for error statuses, because GET request can be used to check
# if something exists. We don't want a 404 to cause failures.
return resp
def add_session_retries(
session: requests.Session,
total: int = 10,
backoff_factor: float = 1.0,
status_forcelist: Optional[Tuple[int, ...]] = (408, 500, 502, 503, 504),
) -> None:
"""Adds retries to a requests HTTP/HTTPS session.
The default values provide exponential backoff for a max wait of ~8.5 mins
Reference the urllib3 documentation for more details about the kwargs.
Args:
session (Session): A requests session
total (int): See urllib3 docs
backoff_factor (int): See urllib3 docs
status_forcelist (tuple[int]|None): See urllib3 docs
"""
retries = Retry(
total=total,
backoff_factor=backoff_factor,
status_forcelist=status_forcelist,
# Don't raise a MaxRetryError for codes in status_forcelist.
# This allows for more graceful exception handling using
# Response.raise_for_status.
raise_on_status=False,
)
adapter = HTTPAdapter(max_retries=retries)
session.mount("http://", adapter)
session.mount("https://", adapter)
def setup_logger(level: int = logging.INFO, log_format: Any = None):
"""Set up and configure 'pyxis' logger.
Args:
level (str, optional): Logging level. Defaults to logging.INFO.
log_format (Any, optional): Logging message format. Defaults to None.
:return: Logger object
"""
if log_format is None:
log_format = "%(asctime)s [%(name)s] %(levelname)s %(message)s"
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(level)
logging.basicConfig(
level=level,
format=log_format,
handlers=[stream_handler],
)