Skip to content

Commit bf7e0c3

Browse files
committed
Use QUayApiStore to manage sessions
1 parent 232a30f commit bf7e0c3

File tree

5 files changed

+187
-203
lines changed

5 files changed

+187
-203
lines changed

reconcile/quay_base.py

Lines changed: 23 additions & 20 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
@@ -15,28 +15,26 @@ class OrgInfo(TypedDict):
1515
managedRepos: bool
1616
mirror: OrgKey | None
1717
mirror_filters: dict[str, Any]
18-
# Metadata for creating QuayApi instances on-demand
19-
token: str
20-
org_name: str
21-
base_url: str
18+
api: QuayApi
2219

2320

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

25+
def cleanup(self) -> None:
26+
"""Close all QuayApi sessions."""
27+
for org_info in self.data.values():
28+
org_info["api"].cleanup()
2629

27-
def get_quay_api_for_org(org_key: OrgKey, org_info: OrgInfo) -> QuayApi:
28-
"""
29-
Create a QuayApi instance for a specific org on-demand.
30-
The instance should be used within a context manager to ensure cleanup.
31-
"""
32-
return QuayApi(
33-
token=org_info["token"],
34-
organization=org_info["org_name"],
35-
base_url=org_info["base_url"],
36-
)
30+
def __enter__(self) -> "QuayApiStore":
31+
return self
3732

33+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
34+
self.cleanup()
3835

39-
def get_quay_api_store() -> QuayApiStore:
36+
37+
def get_quay_api_store() -> dict[OrgKey, OrgInfo]:
4038
"""
4139
Returns a dictionary with a key for each Quay organization
4240
managed in app-interface.
@@ -77,16 +75,21 @@ def get_quay_api_store() -> QuayApiStore:
7775
else:
7876
push_token = None
7977

78+
# Create QuayApi instance for this org
79+
api = QuayApi(
80+
token=token,
81+
organization=org_name,
82+
base_url=base_url,
83+
)
84+
8085
org_info: OrgInfo = {
8186
"url": base_url,
8287
"push_token": push_token,
8388
"teams": org_data.get("managedTeams") or [],
8489
"managedRepos": bool(org_data.get("managedRepos")),
8590
"mirror": mirror,
8691
"mirror_filters": mirror_filters,
87-
"token": token,
88-
"org_name": org_name,
89-
"base_url": base_url,
92+
"api": api,
9093
}
9194

9295
store[org_key] = org_info

reconcile/quay_membership.py

Lines changed: 60 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@
1010
PermissionQuayOrgTeamV1,
1111
UserV1,
1212
)
13-
from reconcile.quay_base import (
14-
QuayApiStore,
15-
get_quay_api_for_org,
16-
get_quay_api_store,
17-
)
13+
from reconcile.quay_base import QuayApiStore
1814
from reconcile.status import ExitCodes
1915
from reconcile.utils import (
2016
expiration,
@@ -66,30 +62,29 @@ def fetch_current_state(quay_api_store: QuayApiStore) -> AggregatedList:
6662
if not teams:
6763
continue
6864

69-
# Create QuayApi instance on-demand and close immediately after use
70-
with get_quay_api_for_org(org_key, org_data) as quay_api:
71-
for team in teams:
72-
try:
73-
members = quay_api.list_team_members(team)
74-
except QuayTeamNotFoundError:
75-
logging.warning(
76-
"Attempted to list members for team %s in "
77-
"org %s/%s, but it doesn't exist",
78-
team,
79-
org_key.instance,
80-
org_key.org_name,
81-
)
82-
else:
83-
# Teams are only added to the state if they exist so that
84-
# there is a proper diff between the desired and current state.
85-
state.add(
86-
{
87-
"service": "quay-membership",
88-
"org": org_key.org_name,
89-
"team": team,
90-
},
91-
members,
92-
)
65+
quay_api = org_data["api"]
66+
for team in teams:
67+
try:
68+
members = quay_api.list_team_members(team)
69+
except QuayTeamNotFoundError:
70+
logging.warning(
71+
"Attempted to list members for team %s in "
72+
"org %s/%s, but it doesn't exist",
73+
team,
74+
org_key.instance,
75+
org_key.org_name,
76+
)
77+
else:
78+
# Teams are only added to the state if they exist so that
79+
# there is a proper diff between the desired and current state.
80+
state.add(
81+
{
82+
"service": "quay-membership",
83+
"org": org_key.org_name,
84+
"team": team,
85+
},
86+
members,
87+
)
9388
return state
9489

9590

@@ -128,19 +123,18 @@ def action(params: dict, items: list) -> bool:
128123
org = params["org"]
129124
team = params["team"]
130125
org_data = self.quay_api_store[org]
126+
quay_api = org_data["api"]
131127

132128
missing_users = False
133-
# Create QuayApi instance on-demand and close immediately after use
134-
with get_quay_api_for_org(org, org_data) as quay_api:
135-
for member in items:
136-
logging.info([label, member, org, team])
137-
user_exists = quay_api.user_exists(member)
138-
if user_exists:
139-
if not self.dry_run:
140-
quay_api.add_user_to_team(member, team)
141-
else:
142-
missing_users = True
143-
logging.error(f"quay user {member} does not exist.")
129+
for member in items:
130+
logging.info([label, member, org, team])
131+
user_exists = quay_api.user_exists(member)
132+
if user_exists:
133+
if not self.dry_run:
134+
quay_api.add_user_to_team(member, team)
135+
else:
136+
missing_users = True
137+
logging.error(f"quay user {member} does not exist.")
144138

145139
# This will be evaluated by AggregatedDiffRunner.run(). The happy
146140
# case is to return True: no missing users
@@ -172,9 +166,8 @@ def action(params: dict, items: list) -> bool:
172166
logging.info([label, org, team])
173167

174168
if not self.dry_run:
175-
# Create QuayApi instance on-demand and close immediately after use
176-
with get_quay_api_for_org(org, org_data) as quay_api:
177-
quay_api.create_or_update_team(team)
169+
quay_api = org_data["api"]
170+
quay_api.create_or_update_team(team)
178171

179172
return True
180173

@@ -188,39 +181,38 @@ def action(params: dict, items: list) -> bool:
188181
team = params["team"]
189182
org_data = self.quay_api_store[org]
190183

184+
quay_api = org_data["api"]
191185
if self.dry_run:
192186
for member in items:
193187
logging.info([label, member, org, team])
194188
else:
195-
# Create QuayApi instance on-demand and close immediately after use
196-
with get_quay_api_for_org(org, org_data) as quay_api:
197-
for member in items:
198-
logging.info([label, member, org, team])
199-
quay_api.remove_user_from_team(member, team)
189+
for member in items:
190+
logging.info([label, member, org, team])
191+
quay_api.remove_user_from_team(member, team)
200192

201193
return True
202194

203195
return action
204196

205197

206198
def run(dry_run: bool) -> None:
207-
quay_api_store = get_quay_api_store()
208-
current_state = fetch_current_state(quay_api_store)
209-
desired_state = fetch_desired_state()
210-
211-
# calculate diff
212-
diff = current_state.diff(desired_state)
213-
logging.debug("State diff: %s", diff)
214-
215-
# Run actions
216-
runner_action = RunnerAction(dry_run, quay_api_store)
217-
runner = AggregatedDiffRunner(diff)
218-
219-
runner.register("insert", runner_action.create_team())
220-
runner.register("update-insert", runner_action.add_to_team())
221-
runner.register("update-delete", runner_action.del_from_team())
222-
runner.register("delete", runner_action.del_from_team())
223-
224-
status = runner.run()
225-
if not status:
226-
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: 28 additions & 30 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_for_org, 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()
@@ -112,34 +113,31 @@ def process_org_mirrors(self) -> dict[OrgKey, list[dict[str, Any]]]:
112113
username = push_token["user"]
113114
token = push_token["token"]
114115

115-
# Create QuayApi instances on-demand and close immediately after use
116-
with get_quay_api_for_org(org_key, org_info) as quay_api:
117-
org_repos = [item["name"] for item in quay_api.list_images()]
118-
119-
with get_quay_api_for_org(
120-
upstream_org_key, upstream_org
121-
) as upstream_quay_api:
122-
for repo in upstream_quay_api.list_images():
123-
if repo["name"] not in org_repos:
124-
continue
125-
126-
if self.repositories and repo["name"] not in self.repositories:
127-
continue
128-
129-
server_url = upstream_org["url"]
130-
url = f"{server_url}/{org_key.org_name}/{repo['name']}"
131-
data = {
132-
"name": repo["name"],
133-
"mirror": {
134-
"url": url,
135-
"username": username,
136-
"token": token,
137-
},
138-
"mirror_filters": org_info.get("mirror_filters", {}).get(
139-
repo["name"], {}
140-
),
141-
}
142-
summary[org_key].append(data)
116+
quay_api = org_info["api"]
117+
org_repos = [item["name"] for item in quay_api.list_images()]
118+
119+
upstream_quay_api = upstream_org["api"]
120+
for repo in upstream_quay_api.list_images():
121+
if repo["name"] not in org_repos:
122+
continue
123+
124+
if self.repositories and repo["name"] not in self.repositories:
125+
continue
126+
127+
server_url = upstream_org["url"]
128+
url = f"{server_url}/{org_key.org_name}/{repo['name']}"
129+
data = {
130+
"name": repo["name"],
131+
"mirror": {
132+
"url": url,
133+
"username": username,
134+
"token": token,
135+
},
136+
"mirror_filters": org_info.get("mirror_filters", {}).get(
137+
repo["name"], {}
138+
),
139+
}
140+
summary[org_key].append(data)
143141

144142
return summary
145143

0 commit comments

Comments
 (0)