qontract-reconcile 0.10.2.dev456__py3-none-any.whl → 0.10.2.dev473__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of qontract-reconcile might be problematic. Click here for more details.

@@ -1,13 +1,16 @@
1
+ import contextlib
1
2
  from typing import Any
2
3
 
3
4
  import requests
4
5
 
6
+ from reconcile.utils.rest_api_base import ApiBase, BearerTokenAuth
7
+
5
8
 
6
9
  class QuayTeamNotFoundError(Exception):
7
10
  pass
8
11
 
9
12
 
10
- class QuayApi:
13
+ class QuayApi(ApiBase):
11
14
  LIMIT_FOLLOWS = 15
12
15
 
13
16
  def __init__(
@@ -17,14 +20,18 @@ class QuayApi:
17
20
  base_url: str = "quay.io",
18
21
  timeout: int = 60,
19
22
  ) -> None:
20
- self.token = token
23
+ # Support both hostname (e.g., "quay.io") and full URLs (e.g., "http://localhost:12345")
24
+ if base_url.startswith(("http://", "https://")):
25
+ host = base_url
26
+ else:
27
+ host = f"https://{base_url}"
28
+ super().__init__(
29
+ host=host,
30
+ auth=BearerTokenAuth(token),
31
+ read_timeout=timeout,
32
+ )
21
33
  self.organization = organization
22
- self.auth_header = {"Authorization": "Bearer %s" % (token,)}
23
34
  self.team_members: dict[str, Any] = {}
24
- self.api_url = f"https://{base_url}/api/v1"
25
-
26
- self._timeout = timeout
27
- """Timeout to use for HTTP calls to Quay (seconds)."""
28
35
 
29
36
  def list_team_members(self, team: str, **kwargs: Any) -> list[dict]:
30
37
  """
@@ -38,19 +45,20 @@ class QuayApi:
38
45
  if cache_members:
39
46
  return cache_members
40
47
 
41
- url = f"{self.api_url}/organization/{self.organization}/team/{team}/members?includePending=true"
42
-
43
- r = requests.get(url, headers=self.auth_header, timeout=self._timeout)
44
- if r.status_code == 404:
45
- raise QuayTeamNotFoundError(
46
- f"team {team} is not found in "
47
- f"org {self.organization}. "
48
- f"contact org owner to create the "
49
- f"team manually."
50
- )
51
- r.raise_for_status()
52
-
53
- body = r.json()
48
+ url = f"/api/v1/organization/{self.organization}/team/{team}/members"
49
+ params = {"includePending": "true"}
50
+
51
+ try:
52
+ body = self._get(url, params=params)
53
+ except requests.exceptions.HTTPError as e:
54
+ if e.response.status_code == 404:
55
+ raise QuayTeamNotFoundError(
56
+ f"team {team} is not found in "
57
+ f"org {self.organization}. "
58
+ f"contact org owner to create the "
59
+ f"team manually."
60
+ ) from e
61
+ raise
54
62
 
55
63
  # Using a set because members may be repeated
56
64
  members = {member["name"] for member in body["members"]}
@@ -61,30 +69,37 @@ class QuayApi:
61
69
  return members_list
62
70
 
63
71
  def user_exists(self, user: str) -> bool:
64
- url = f"{self.api_url}/users/{user}"
65
- r = requests.get(url, headers=self.auth_header, timeout=self._timeout)
66
- return r.ok
72
+ url = f"/api/v1/users/{user}"
73
+ try:
74
+ self._get(url)
75
+ return True
76
+ except requests.exceptions.HTTPError:
77
+ return False
67
78
 
68
79
  def remove_user_from_team(self, user: str, team: str) -> bool:
69
80
  """Deletes an user from a team.
70
81
 
71
82
  :raises HTTPError if there are any problems with the request
72
83
  """
73
- url_team = f"{self.api_url}/organization/{self.organization}/team/{team}/members/{user}"
84
+ url_team = (
85
+ f"/api/v1/organization/{self.organization}/team/{team}/members/{user}"
86
+ )
74
87
 
75
- r = requests.delete(url_team, headers=self.auth_header, timeout=self._timeout)
76
- if not r.ok:
77
- message = r.json().get("message", "")
88
+ try:
89
+ self._delete(url_team)
90
+ except requests.exceptions.HTTPError as e:
91
+ message = ""
92
+ if e.response is not None:
93
+ with contextlib.suppress(ValueError, AttributeError):
94
+ message = e.response.json().get("message", "")
78
95
 
79
96
  expected_message = f"User {user} does not belong to team {team}"
80
97
 
81
98
  if message != expected_message:
82
- r.raise_for_status()
99
+ raise
83
100
 
84
- url_org = f"{self.api_url}/organization/{self.organization}/members/{user}"
85
-
86
- r = requests.delete(url_org, headers=self.auth_header, timeout=self._timeout)
87
- r.raise_for_status()
101
+ url_org = f"/api/v1/organization/{self.organization}/members/{user}"
102
+ self._delete(url_org)
88
103
 
89
104
  return True
90
105
 
@@ -96,9 +111,8 @@ class QuayApi:
96
111
  if user in self.list_team_members(team, cache=True):
97
112
  return True
98
113
 
99
- url = f"{self.api_url}/organization/{self.organization}/team/{team}/members/{user}"
100
- r = requests.put(url, headers=self.auth_header, timeout=self._timeout)
101
- r.raise_for_status()
114
+ url = f"/api/v1/organization/{self.organization}/team/{team}/members/{user}"
115
+ self._put(url)
102
116
  return True
103
117
 
104
118
  def create_or_update_team(
@@ -115,17 +129,14 @@ class QuayApi:
115
129
  :raises HTTPError: unsuccessful attempt to create the team
116
130
  """
117
131
 
118
- url = f"{self.api_url}/organization/{self.organization}/team/{team}"
132
+ url = f"/api/v1/organization/{self.organization}/team/{team}"
119
133
 
120
134
  payload = {"role": role}
121
135
 
122
136
  if description:
123
137
  payload.update({"description": description})
124
138
 
125
- r = requests.put(
126
- url, headers=self.auth_header, json=payload, timeout=self._timeout
127
- )
128
- r.raise_for_status()
139
+ self._put(url, data=payload)
129
140
 
130
141
  def list_images(
131
142
  self, images: list | None = None, page: str | None = None, count: int = 0
@@ -140,7 +151,7 @@ class QuayApi:
140
151
  if count > self.LIMIT_FOLLOWS:
141
152
  raise ValueError("Too many page follows")
142
153
 
143
- url = f"{self.api_url}/repository"
154
+ url = "/api/v1/repository"
144
155
 
145
156
  # params
146
157
  params = {"namespace": self.organization}
@@ -148,13 +159,7 @@ class QuayApi:
148
159
  params["next_page"] = page
149
160
 
150
161
  # perform request
151
- r = requests.get(
152
- url, params=params, headers=self.auth_header, timeout=self._timeout
153
- )
154
- r.raise_for_status()
155
-
156
- # read body
157
- body = r.json()
162
+ body = self._get(url, params=params)
158
163
  repositories = body.get("repositories", [])
159
164
  next_page = body.get("next_page")
160
165
 
@@ -176,7 +181,7 @@ class QuayApi:
176
181
  """
177
182
  visibility = "public" if public else "private"
178
183
 
179
- url = f"{self.api_url}/repository"
184
+ url = "/repository"
180
185
 
181
186
  params = {
182
187
  "repo_kind": "image",
@@ -186,29 +191,16 @@ class QuayApi:
186
191
  "description": description,
187
192
  }
188
193
 
189
- # perform request
190
- r = requests.post(
191
- url, json=params, headers=self.auth_header, timeout=self._timeout
192
- )
193
- r.raise_for_status()
194
+ self._post(url, data=params)
194
195
 
195
196
  def repo_delete(self, repo_name: str) -> None:
196
- url = f"{self.api_url}/repository/{self.organization}/{repo_name}"
197
-
198
- # perform request
199
- r = requests.delete(url, headers=self.auth_header, timeout=self._timeout)
200
- r.raise_for_status()
197
+ url = f"/api/v1/repository/{self.organization}/{repo_name}"
198
+ self._delete(url)
201
199
 
202
200
  def repo_update_description(self, repo_name: str, description: str) -> None:
203
- url = f"{self.api_url}/repository/{self.organization}/{repo_name}"
204
-
201
+ url = f"/api/v1/repository/{self.organization}/{repo_name}"
205
202
  params = {"description": description}
206
-
207
- # perform request
208
- r = requests.put(
209
- url, json=params, headers=self.auth_header, timeout=self._timeout
210
- )
211
- r.raise_for_status()
203
+ self._put(url, data=params)
212
204
 
213
205
  def repo_make_public(self, repo_name: str) -> None:
214
206
  self._repo_change_visibility(repo_name, "public")
@@ -217,39 +209,34 @@ class QuayApi:
217
209
  self._repo_change_visibility(repo_name, "private")
218
210
 
219
211
  def _repo_change_visibility(self, repo_name: str, visibility: str) -> None:
220
- url = f"{self.api_url}/repository/{self.organization}/{repo_name}/changevisibility"
221
-
212
+ url = f"/api/v1/repository/{self.organization}/{repo_name}/changevisibility"
222
213
  params = {"visibility": visibility}
223
-
224
- # perform request
225
- r = requests.post(
226
- url, json=params, headers=self.auth_header, timeout=self._timeout
227
- )
228
- r.raise_for_status()
214
+ self._post(url, data=params)
229
215
 
230
216
  def get_repo_team_permissions(self, repo_name: str, team: str) -> str | None:
231
217
  url = (
232
- f"{self.api_url}/repository/{self.organization}/"
218
+ f"/api/v1/repository/{self.organization}/"
233
219
  + f"{repo_name}/permissions/team/{team}"
234
220
  )
235
- r = requests.get(url, headers=self.auth_header, timeout=self._timeout)
236
- if not r.ok:
237
- message = r.json().get("message")
221
+ try:
222
+ body = self._get(url)
223
+ return body.get("role") or None
224
+ except requests.exceptions.HTTPError as e:
225
+ message = ""
226
+ if e.response is not None:
227
+ with contextlib.suppress(ValueError, AttributeError):
228
+ message = e.response.json().get("message", "")
229
+
238
230
  expected_message = "Team does not have permission for repo."
239
231
  if message == expected_message:
240
232
  return None
241
233
 
242
- r.raise_for_status()
243
-
244
- return r.json().get("role") or None
234
+ raise
245
235
 
246
236
  def set_repo_team_permissions(self, repo_name: str, team: str, role: str) -> None:
247
237
  url = (
248
- f"{self.api_url}/repository/{self.organization}/"
238
+ f"/api/v1/repository/{self.organization}/"
249
239
  + f"{repo_name}/permissions/team/{team}"
250
240
  )
251
241
  body = {"role": role}
252
- r = requests.put(
253
- url, json=body, headers=self.auth_header, timeout=self._timeout
254
- )
255
- r.raise_for_status()
242
+ self._put(url, data=body)