Skip to content

Commit 5a4bc35

Browse files
authored
Refactor QuayApi to extend ApiBase (#5353)
* Refactor tests to use httpserver * Refactor QuayApi to extend ApiBase * Refactor quay_membership test to rely on short lived sessions * Creates QuayApi sessions on demand and destroy after use * Refactor tests to use QuayApiStore * Use QUayApiStore to manage sessions * Fix: function documentation
1 parent d21c954 commit 5a4bc35

File tree

10 files changed

+401
-313
lines changed

10 files changed

+401
-313
lines changed

reconcile/quay_base.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from collections import namedtuple
1+
from collections import UserDict, namedtuple
22
from typing import Any, TypedDict
33

44
from reconcile import queries
@@ -10,22 +10,34 @@
1010

1111
class OrgInfo(TypedDict):
1212
url: str
13-
api: QuayApi
1413
push_token: dict[str, str] | None
1514
teams: list[str]
1615
managedRepos: bool
1716
mirror: OrgKey | None
1817
mirror_filters: dict[str, Any]
18+
api: QuayApi
19+
1920

21+
class QuayApiStore(UserDict[OrgKey, OrgInfo]):
22+
def __init__(self) -> None:
23+
super().__init__(get_quay_api_store())
2024

21-
QuayApiStore = dict[OrgKey, OrgInfo]
25+
def cleanup(self) -> None:
26+
"""Close all QuayApi sessions."""
27+
for org_info in self.data.values():
28+
org_info["api"].cleanup()
2229

30+
def __enter__(self) -> "QuayApiStore":
31+
return self
2332

24-
def get_quay_api_store() -> QuayApiStore:
33+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
34+
self.cleanup()
35+
36+
37+
def get_quay_api_store() -> dict[OrgKey, OrgInfo]:
2538
"""
2639
Returns a dictionary with a key for each Quay organization
2740
managed in app-interface.
28-
Each key contains an initiated QuayApi instance.
2941
"""
3042
quay_orgs = queries.get_quay_orgs()
3143
settings = queries.get_app_interface_settings()
@@ -61,14 +73,21 @@ def get_quay_api_store() -> QuayApiStore:
6173
else:
6274
push_token = None
6375

76+
# Create QuayApi instance for this org
77+
api = QuayApi(
78+
token=token,
79+
organization=org_name,
80+
base_url=base_url,
81+
)
82+
6483
org_info: OrgInfo = {
6584
"url": base_url,
66-
"api": QuayApi(token, org_name, base_url=base_url),
6785
"push_token": push_token,
6886
"teams": org_data.get("managedTeams") or [],
6987
"managedRepos": bool(org_data.get("managedRepos")),
7088
"mirror": mirror,
7189
"mirror_filters": mirror_filters,
90+
"api": api,
7291
}
7392

7493
store[org_key] = org_info

reconcile/quay_membership.py

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
PermissionQuayOrgTeamV1,
1111
UserV1,
1212
)
13-
from reconcile.quay_base import QuayApiStore, get_quay_api_store
13+
from reconcile.quay_base import QuayApiStore
1414
from reconcile.status import ExitCodes
1515
from reconcile.utils import (
1616
expiration,
@@ -58,10 +58,11 @@ def fetch_current_state(quay_api_store: QuayApiStore) -> AggregatedList:
5858
state = AggregatedList()
5959

6060
for org_key, org_data in quay_api_store.items():
61-
quay_api = org_data["api"]
6261
teams = org_data["teams"]
6362
if not teams:
6463
continue
64+
65+
quay_api = org_data["api"]
6566
for team in teams:
6667
try:
6768
members = quay_api.list_team_members(team)
@@ -77,7 +78,11 @@ def fetch_current_state(quay_api_store: QuayApiStore) -> AggregatedList:
7778
# Teams are only added to the state if they exist so that
7879
# there is a proper diff between the desired and current state.
7980
state.add(
80-
{"service": "quay-membership", "org": org_key, "team": team},
81+
{
82+
"service": "quay-membership",
83+
"org": org_key.org_name,
84+
"team": team,
85+
},
8186
members,
8287
)
8388
return state
@@ -117,9 +122,10 @@ def add_to_team(self) -> Action:
117122
def action(params: dict, items: list) -> bool:
118123
org = params["org"]
119124
team = params["team"]
125+
org_data = self.quay_api_store[org]
126+
quay_api = org_data["api"]
120127

121128
missing_users = False
122-
quay_api = self.quay_api_store[org]["api"]
123129
for member in items:
124130
logging.info([label, member, org, team])
125131
user_exists = quay_api.user_exists(member)
@@ -147,10 +153,11 @@ def create_team(self) -> Action:
147153
def action(params: dict, items: list) -> bool:
148154
org = params["org"]
149155
team = params["team"]
156+
org_data = self.quay_api_store[org]
150157

151158
# Ensure all quay org/teams are declared as dependencies in a
152159
# `/dependencies/quay-org-1.yml` datafile.
153-
if team not in self.quay_api_store[org]["teams"]:
160+
if team not in org_data["teams"]:
154161
raise RunnerError(
155162
f"Quay team {team} is not defined as a "
156163
f"managedTeam in the {org} org."
@@ -159,7 +166,7 @@ def action(params: dict, items: list) -> bool:
159166
logging.info([label, org, team])
160167

161168
if not self.dry_run:
162-
quay_api = self.quay_api_store[org]["api"]
169+
quay_api = org_data["api"]
163170
quay_api.create_or_update_team(team)
164171

165172
return True
@@ -172,12 +179,13 @@ def del_from_team(self) -> Action:
172179
def action(params: dict, items: list) -> bool:
173180
org = params["org"]
174181
team = params["team"]
182+
org_data = self.quay_api_store[org]
175183

184+
quay_api = org_data["api"]
176185
if self.dry_run:
177186
for member in items:
178187
logging.info([label, member, org, team])
179188
else:
180-
quay_api = self.quay_api_store[org]["api"]
181189
for member in items:
182190
logging.info([label, member, org, team])
183191
quay_api.remove_user_from_team(member, team)
@@ -188,24 +196,23 @@ def action(params: dict, items: list) -> bool:
188196

189197

190198
def run(dry_run: bool) -> None:
191-
quay_api_store = get_quay_api_store()
192-
193-
current_state = fetch_current_state(quay_api_store)
194-
desired_state = fetch_desired_state()
195-
196-
# calculate diff
197-
diff = current_state.diff(desired_state)
198-
logging.debug("State diff: %s", diff)
199-
200-
# Run actions
201-
runner_action = RunnerAction(dry_run, quay_api_store)
202-
runner = AggregatedDiffRunner(diff)
203-
204-
runner.register("insert", runner_action.create_team())
205-
runner.register("update-insert", runner_action.add_to_team())
206-
runner.register("update-delete", runner_action.del_from_team())
207-
runner.register("delete", runner_action.del_from_team())
208-
209-
status = runner.run()
210-
if not status:
211-
sys.exit(ExitCodes.ERROR)
199+
with QuayApiStore() as quay_api_store:
200+
current_state = fetch_current_state(quay_api_store)
201+
desired_state = fetch_desired_state()
202+
203+
# calculate diff
204+
diff = current_state.diff(desired_state)
205+
logging.debug("State diff: %s", diff)
206+
207+
# Run actions
208+
runner_action = RunnerAction(dry_run, quay_api_store)
209+
runner = AggregatedDiffRunner(diff)
210+
211+
runner.register("insert", runner_action.create_team())
212+
runner.register("update-insert", runner_action.add_to_team())
213+
runner.register("update-delete", runner_action.del_from_team())
214+
runner.register("delete", runner_action.del_from_team())
215+
216+
status = runner.run()
217+
if not status:
218+
sys.exit(ExitCodes.ERROR)

reconcile/quay_mirror_org.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
)
1818
from sretoolbox.container.skopeo import SkopeoCmdError
1919

20-
from reconcile.quay_base import get_quay_api_store
20+
from reconcile.quay_base import QuayApiStore
2121
from reconcile.quay_mirror import QuayMirror
2222
from reconcile.utils.quay_mirror import record_timestamp, sync_tag
2323

@@ -45,7 +45,7 @@ def __init__(
4545
) -> None:
4646
self.dry_run = dry_run
4747
self.skopeo_cli = Skopeo(dry_run)
48-
self.quay_api_store = get_quay_api_store()
48+
self.quay_api_store = QuayApiStore()
4949
self.compare_tags = compare_tags
5050
self.compare_tags_interval = compare_tags_interval
5151
self.orgs = orgs
@@ -71,6 +71,7 @@ def __enter__(self) -> Self:
7171

7272
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
7373
self.session.close()
74+
self.quay_api_store.cleanup()
7475

7576
def run(self) -> None:
7677
sync_tasks = self.process_sync_tasks()
@@ -101,11 +102,9 @@ def process_org_mirrors(self) -> dict[OrgKey, list[dict[str, Any]]]:
101102
if self.orgs and org_key.org_name not in self.orgs:
102103
continue
103104

104-
quay_api = org_info["api"]
105105
upstream_org_key = org_info["mirror"]
106106
assert upstream_org_key is not None
107107
upstream_org = self.quay_api_store[upstream_org_key]
108-
upstream_quay_api = upstream_org["api"]
109108

110109
push_token = upstream_org["push_token"]
111110

@@ -114,7 +113,10 @@ def process_org_mirrors(self) -> dict[OrgKey, list[dict[str, Any]]]:
114113
username = push_token["user"]
115114
token = push_token["token"]
116115

116+
quay_api = org_info["api"]
117117
org_repos = [item["name"] for item in quay_api.list_images()]
118+
119+
upstream_quay_api = upstream_org["api"]
118120
for repo in upstream_quay_api.list_images():
119121
if repo["name"] not in org_repos:
120122
continue

0 commit comments

Comments
 (0)