qontract-reconcile 0.10.2.dev135__py3-none-any.whl → 0.10.2.dev136__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.
Files changed (35) hide show
  1. {qontract_reconcile-0.10.2.dev135.dist-info → qontract_reconcile-0.10.2.dev136.dist-info}/METADATA +1 -1
  2. {qontract_reconcile-0.10.2.dev135.dist-info → qontract_reconcile-0.10.2.dev136.dist-info}/RECORD +35 -29
  3. reconcile/aws_ami_share.py +4 -2
  4. reconcile/aws_ecr_image_pull_secrets.py +15 -7
  5. reconcile/aws_garbage_collector.py +1 -1
  6. reconcile/aws_iam_keys.py +24 -17
  7. reconcile/aws_iam_password_reset.py +4 -2
  8. reconcile/aws_support_cases_sos.py +19 -6
  9. reconcile/closedbox_endpoint_monitoring_base.py +4 -3
  10. reconcile/cna/client.py +3 -3
  11. reconcile/cna/integration.py +4 -5
  12. reconcile/cna/state.py +3 -3
  13. reconcile/email_sender.py +65 -56
  14. reconcile/gcr_mirror.py +11 -9
  15. reconcile/github_org.py +57 -52
  16. reconcile/github_owners.py +8 -5
  17. reconcile/github_repo_invites.py +1 -1
  18. reconcile/github_repo_permissions_validator.py +3 -3
  19. reconcile/github_users.py +16 -12
  20. reconcile/github_validator.py +1 -1
  21. reconcile/gitlab_fork_compliance.py +9 -8
  22. reconcile/gitlab_labeler.py +1 -1
  23. reconcile/gitlab_mr_sqs_consumer.py +6 -3
  24. reconcile/gitlab_owners.py +29 -12
  25. reconcile/gql_definitions/email_sender/__init__.py +0 -0
  26. reconcile/gql_definitions/email_sender/apps.py +64 -0
  27. reconcile/gql_definitions/email_sender/emails.py +133 -0
  28. reconcile/gql_definitions/email_sender/users.py +62 -0
  29. reconcile/gql_definitions/fragments/email_service.py +32 -0
  30. reconcile/gql_definitions/fragments/email_user.py +28 -0
  31. reconcile/queries.py +0 -44
  32. reconcile/utils/jinja2/utils.py +4 -2
  33. reconcile/utils/smtp_client.py +1 -1
  34. {qontract_reconcile-0.10.2.dev135.dist-info → qontract_reconcile-0.10.2.dev136.dist-info}/WHEEL +0 -0
  35. {qontract_reconcile-0.10.2.dev135.dist-info → qontract_reconcile-0.10.2.dev136.dist-info}/entry_points.txt +0 -0
reconcile/email_sender.py CHANGED
@@ -1,13 +1,21 @@
1
1
  import logging
2
2
  import sys
3
+ from collections.abc import Callable
3
4
 
4
- from reconcile import (
5
- queries,
6
- typed_queries,
5
+ from reconcile import typed_queries
6
+ from reconcile.gql_definitions.email_sender.apps import query as apps_query
7
+ from reconcile.gql_definitions.email_sender.emails import (
8
+ AppInterfaceEmailAudienceV1,
9
+ AppInterfaceEmailV1,
7
10
  )
11
+ from reconcile.gql_definitions.email_sender.emails import query as emails_query
12
+ from reconcile.gql_definitions.email_sender.users import query as users_query
13
+ from reconcile.gql_definitions.fragments.email_service import EmailServiceOwners
14
+ from reconcile.gql_definitions.fragments.email_user import EmailUser
8
15
  from reconcile.typed_queries.app_interface_vault_settings import (
9
16
  get_app_interface_vault_settings,
10
17
  )
18
+ from reconcile.utils import gql
11
19
  from reconcile.utils.defer import defer
12
20
  from reconcile.utils.secret_reader import create_secret_reader
13
21
  from reconcile.utils.smtp_client import (
@@ -20,11 +28,17 @@ from reconcile.utils.state import init_state
20
28
  QONTRACT_INTEGRATION = "email-sender"
21
29
 
22
30
 
23
- def collect_to(to):
31
+ def collect_to(
32
+ to: AppInterfaceEmailAudienceV1,
33
+ all_users: list[EmailUser],
34
+ all_services: list[EmailServiceOwners],
35
+ ) -> set[str]:
24
36
  """Collect audience to send email to from to object
25
37
 
26
38
  Arguments:
27
- to {dict} -- AppInterfaceEmailAudience_v1 object
39
+ to -- AppInterfaceEmailAudience_v1 object
40
+ all_users -- List of all app-interface users
41
+ all_services -- List of all app-interface apps/services with owners
28
42
 
29
43
  Raises:
30
44
  AttributeError: Unknown alias
@@ -32,63 +46,58 @@ def collect_to(to):
32
46
  Returns:
33
47
  set -- Audience to send email to
34
48
  """
35
- audience = set()
36
-
37
- aliases = to.get("aliases")
38
- if aliases:
39
- for alias in aliases:
40
- if alias == "all-users":
41
- users = queries.get_users()
42
- to["users"] = users
43
- elif alias == "all-service-owners":
44
- services = queries.get_apps()
45
- to["services"] = services
46
- else:
49
+ audience: set[str] = set()
50
+
51
+ for alias in to.aliases or []:
52
+ match alias:
53
+ case "all-users":
54
+ to.users = all_users
55
+ case "all-service-owners":
56
+ to.services = all_services
57
+ case _:
47
58
  raise AttributeError(f"unknown alias: {alias}")
48
59
 
49
- services = to.get("services")
50
- if services:
51
- for service in services:
52
- service_owners = service.get("serviceOwners")
53
- if not service_owners:
54
- continue
60
+ for service in to.services or []:
61
+ audience.update(
62
+ service_owner.email for service_owner in service.service_owners or []
63
+ )
55
64
 
56
- audience.update(service_owner["email"] for service_owner in service_owners)
65
+ for account in to.aws_accounts or []:
66
+ audience.update(
67
+ account_owner.email for account_owner in account.account_owners or []
68
+ )
57
69
 
58
- # TODO: implement clusters and namespaces
70
+ for role in to.roles or []:
71
+ audience.update(user.org_username for user in role.users or [])
59
72
 
60
- aws_accounts = to.get("aws_accounts")
61
- if aws_accounts:
62
- for account in aws_accounts:
63
- account_owners = account.get("accountOwners")
64
- if not account_owners:
65
- continue
73
+ # SmtpClient supports sending to org_username and email addresses
74
+ audience.update(user.org_username for user in to.users or [])
66
75
 
67
- audience.update(account_owner["email"] for account_owner in account_owners)
68
-
69
- roles = to.get("roles")
70
- if roles:
71
- for role in roles:
72
- users = role.get("users")
73
- if not users:
74
- continue
75
-
76
- audience.update(user["org_username"] for user in users)
76
+ if to.clusters or to.namespaces:
77
+ raise NotImplementedError("clusters and namespaces are not implemented yet")
78
+ return audience
77
79
 
78
- users = to.get("users")
79
- if users:
80
- audience.update(user["org_username"] for user in users)
81
80
 
82
- return audience
81
+ def get_emails(query_func: Callable) -> list[AppInterfaceEmailV1]:
82
+ return emails_query(query_func).emails or []
83
83
 
84
84
 
85
85
  @defer
86
- def run(dry_run, defer=None):
86
+ def run(dry_run: bool, defer: Callable | None = None) -> None:
87
+ gql_api = gql.get_api()
87
88
  vault_settings = get_app_interface_vault_settings()
88
89
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
89
90
  state = init_state(integration=QONTRACT_INTEGRATION, secret_reader=secret_reader)
90
- defer(state.cleanup)
91
- emails = queries.get_app_interface_emails()
91
+ if defer:
92
+ defer(state.cleanup)
93
+
94
+ emails = get_emails(gql_api.query)
95
+ if not emails:
96
+ logging.info("no emails to send")
97
+ sys.exit(0)
98
+
99
+ all_users = users_query(gql_api.query).users or []
100
+ all_services = apps_query(gql_api.query).apps or []
92
101
  smtp_settings = typed_queries.smtp.settings()
93
102
  smtp_client = SmtpClient(
94
103
  server=get_smtp_server_connection(
@@ -99,18 +108,18 @@ def run(dry_run, defer=None):
99
108
  timeout=smtp_settings.timeout or DEFAULT_SMTP_TIMEOUT,
100
109
  )
101
110
  # validate no 2 emails have the same name
102
- email_names = {e["name"] for e in emails}
111
+ email_names = {e.name for e in emails}
103
112
  if len(emails) != len(email_names):
104
113
  logging.error("email names must be unique.")
105
114
  sys.exit(1)
106
115
 
107
- emails_to_send = [e for e in emails if not state.exists(e["name"])]
116
+ emails_to_send = [e for e in emails if not state.exists(e.name)]
108
117
  for email in emails_to_send:
109
- logging.info(["send_email", email["name"], email["subject"]])
118
+ logging.info(["send_email", email.name, email.subject])
110
119
 
111
120
  if not dry_run:
112
- names = collect_to(email["to"])
113
- subject = email["subject"]
114
- body = email["body"]
115
- smtp_client.send_mail(names, subject, body)
116
- state.add(email["name"])
121
+ names = collect_to(
122
+ email.q_to, all_users=all_users, all_services=all_services
123
+ )
124
+ smtp_client.send_mail(names, email.subject, email.body)
125
+ state.add(email.name)
reconcile/gcr_mirror.py CHANGED
@@ -66,7 +66,7 @@ class QuayMirror:
66
66
  }
67
67
  """
68
68
 
69
- def __init__(self, dry_run=False):
69
+ def __init__(self, dry_run: bool = False) -> None:
70
70
  self.dry_run = dry_run
71
71
  self.gqlapi = gql.get_api()
72
72
  settings = queries.get_app_interface_settings()
@@ -81,7 +81,7 @@ class QuayMirror:
81
81
  def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
82
82
  self.session.close()
83
83
 
84
- def run(self):
84
+ def run(self) -> None:
85
85
  sync_tasks = self.process_sync_tasks()
86
86
  for org, data in sync_tasks.items():
87
87
  for item in data:
@@ -95,7 +95,7 @@ class QuayMirror:
95
95
  except SkopeoCmdError as details:
96
96
  _LOG.error("[%s]", details)
97
97
 
98
- def process_repos_query(self):
98
+ def process_repos_query(self) -> dict[str, list[dict[str, Any]]]:
99
99
  result = self.gqlapi.query(self.GCR_REPOS_QUERY)
100
100
 
101
101
  summary = defaultdict(list)
@@ -122,7 +122,9 @@ class QuayMirror:
122
122
  return summary
123
123
 
124
124
  @staticmethod
125
- def sync_tag(tags, tags_exclude, candidate):
125
+ def sync_tag(
126
+ tags: list[str] | None, tags_exclude: list[str] | None, candidate: str
127
+ ) -> bool:
126
128
  if tags is not None:
127
129
  # When tags is defined, we don't look at tags_exclude
128
130
  return any(re.match(tag, candidate) for tag in tags)
@@ -137,7 +139,7 @@ class QuayMirror:
137
139
  # tag must be synced
138
140
  return True
139
141
 
140
- def process_sync_tasks(self):
142
+ def process_sync_tasks(self) -> dict[str, list[dict[str, Any]]]:
141
143
  eight_hours = 28800 # 60 * 60 * 8
142
144
  is_deep_sync = self._is_deep_sync(interval=eight_hours)
143
145
 
@@ -233,7 +235,7 @@ class QuayMirror:
233
235
 
234
236
  return sync_tasks
235
237
 
236
- def _is_deep_sync(self, interval):
238
+ def _is_deep_sync(self, interval: int) -> bool:
237
239
  control_file_name = "qontract-reconcile-gcr-mirror.timestamp"
238
240
  control_file_path = os.path.join(tempfile.gettempdir(), control_file_name)
239
241
  try:
@@ -251,11 +253,11 @@ class QuayMirror:
251
253
  return False
252
254
 
253
255
  @staticmethod
254
- def _record_timestamp(path):
256
+ def _record_timestamp(path: str) -> None:
255
257
  with open(path, "w", encoding="locale") as file_object:
256
258
  file_object.write(str(time.time()))
257
259
 
258
- def _get_push_creds(self):
260
+ def _get_push_creds(self) -> dict[str, str]:
259
261
  result = self.gqlapi.query(self.GCR_PROJECT_CATALOG_QUERY)
260
262
 
261
263
  creds = {}
@@ -271,6 +273,6 @@ class QuayMirror:
271
273
  return creds
272
274
 
273
275
 
274
- def run(dry_run):
276
+ def run(dry_run: bool) -> None:
275
277
  with QuayMirror(dry_run) as gcr_mirror:
276
278
  gcr_mirror.run()
reconcile/github_org.py CHANGED
@@ -1,9 +1,12 @@
1
1
  import logging
2
2
  import os
3
+ from collections.abc import Callable, Iterable, KeysView
3
4
  from typing import Any
4
5
 
5
6
  from github import Github
6
7
  from github.GithubObject import NotSet # type: ignore
8
+ from github.Organization import Organization
9
+ from github.Team import Team
7
10
  from sretoolbox.utils import retry
8
11
 
9
12
  from reconcile import (
@@ -95,15 +98,15 @@ CLUSTERS_QUERY = """
95
98
  QONTRACT_INTEGRATION = "github"
96
99
 
97
100
 
98
- def get_orgs():
101
+ def get_orgs() -> list[dict[str, Any]]:
99
102
  gqlapi = gql.get_api()
100
103
  return gqlapi.query(ORGS_QUERY)["orgs"]
101
104
 
102
105
 
103
- def get_config(default=False):
106
+ def get_config(default: bool = False) -> dict[str, Any]:
104
107
  orgs = get_orgs()
105
108
  secret_reader = SecretReader(queries.get_secret_reader_settings())
106
- config = {"github": {}}
109
+ config: dict[str, Any] = {"github": {}}
107
110
  found_defaults = []
108
111
  for org in orgs:
109
112
  org_name = org["name"]
@@ -126,24 +129,52 @@ def get_config(default=False):
126
129
  return config
127
130
 
128
131
 
129
- def get_default_config():
132
+ def get_default_config() -> dict[str, Any]:
130
133
  github_config = get_config(default=True)
131
134
  return next(iter(github_config["github"].values()))
132
135
 
133
136
 
134
137
  @retry()
135
- def get_org_and_teams(github, org_name):
138
+ def get_org_and_teams(
139
+ github: Github, org_name: str
140
+ ) -> tuple[Organization, Iterable[Team]]:
136
141
  org = github.get_organization(org_name)
137
142
  teams = org.get_teams()
138
143
  return org, teams
139
144
 
140
145
 
141
146
  @retry()
142
- def get_members(unit):
147
+ def get_members(unit: Organization) -> list[str]:
143
148
  return [member.login for member in unit.get_members()]
144
149
 
145
150
 
146
- def fetch_current_state(gh_api_store):
151
+ class GHApiStore:
152
+ _orgs: dict[str, Any] = {}
153
+
154
+ def __init__(self, config: dict) -> None:
155
+ for org_name, org_config in config["github"].items():
156
+ token = org_config["token"]
157
+ managed_teams = org_config.get("managed_teams", None)
158
+ self._orgs[org_name] = (
159
+ Github(token, base_url=GH_BASE_URL),
160
+ RawGithubApi(token),
161
+ managed_teams,
162
+ )
163
+
164
+ def orgs(self) -> KeysView[str]:
165
+ return self._orgs.keys()
166
+
167
+ def github(self, org_name: str) -> Github:
168
+ return self._orgs[org_name][0]
169
+
170
+ def raw_github_api(self, org_name: str) -> RawGithubApi:
171
+ return self._orgs[org_name][1]
172
+
173
+ def managed_teams(self, org_name: str) -> list[str] | None:
174
+ return self._orgs[org_name][2]
175
+
176
+
177
+ def fetch_current_state(gh_api_store: GHApiStore) -> AggregatedList:
147
178
  state = AggregatedList()
148
179
 
149
180
  for org_name in gh_api_store.orgs():
@@ -164,7 +195,7 @@ def fetch_current_state(gh_api_store):
164
195
 
165
196
  all_team_members = []
166
197
  for team in teams:
167
- if not is_managed and team.name not in managed_teams:
198
+ if not is_managed and team.name not in (managed_teams or []):
168
199
  continue
169
200
 
170
201
  members = get_members(team)
@@ -190,11 +221,11 @@ def fetch_current_state(gh_api_store):
190
221
  return state
191
222
 
192
223
 
193
- def fetch_desired_state(infer_clusters=True):
224
+ def fetch_desired_state(infer_clusters: bool = True) -> AggregatedList:
194
225
  gqlapi = gql.get_api()
195
226
  state = AggregatedList()
196
227
 
197
- roles = expiration.filter(gqlapi.query(ROLES_QUERY)["roles"])
228
+ roles: list[dict[str, Any]] = expiration.filter(gqlapi.query(ROLES_QUERY)["roles"])
198
229
  for role in roles:
199
230
  permissions = list(
200
231
  filter(
@@ -264,41 +295,15 @@ def fetch_desired_state(infer_clusters=True):
264
295
  return state
265
296
 
266
297
 
267
- class GHApiStore:
268
- _orgs: dict[str, Any] = {}
269
-
270
- def __init__(self, config):
271
- for org_name, org_config in config["github"].items():
272
- token = org_config["token"]
273
- managed_teams = org_config.get("managed_teams", None)
274
- self._orgs[org_name] = (
275
- Github(token, base_url=GH_BASE_URL),
276
- RawGithubApi(token),
277
- managed_teams,
278
- )
279
-
280
- def orgs(self):
281
- return self._orgs.keys()
282
-
283
- def github(self, org_name):
284
- return self._orgs[org_name][0]
285
-
286
- def raw_github_api(self, org_name):
287
- return self._orgs[org_name][1]
288
-
289
- def managed_teams(self, org_name):
290
- return self._orgs[org_name][2]
291
-
292
-
293
298
  class RunnerAction:
294
- def __init__(self, dry_run, gh_api_store):
299
+ def __init__(self, dry_run: bool, gh_api_store: GHApiStore) -> None:
295
300
  self.dry_run = dry_run
296
301
  self.gh_api_store = gh_api_store
297
302
 
298
- def add_to_team(self):
303
+ def add_to_team(self) -> Callable:
299
304
  label = "add_to_team"
300
305
 
301
- def action(params, items):
306
+ def action(params: dict, items: dict) -> None:
302
307
  org = params["org"]
303
308
  team = params["team"]
304
309
 
@@ -318,10 +323,10 @@ class RunnerAction:
318
323
 
319
324
  return action
320
325
 
321
- def del_from_team(self):
326
+ def del_from_team(self) -> Callable:
322
327
  label = "del_from_team"
323
328
 
324
- def action(params, items):
329
+ def action(params: dict, items: dict) -> None:
325
330
  org = params["org"]
326
331
  team = params["team"]
327
332
 
@@ -346,10 +351,10 @@ class RunnerAction:
346
351
 
347
352
  return action
348
353
 
349
- def create_team(self):
354
+ def create_team(self) -> Callable:
350
355
  label = "create_team"
351
356
 
352
- def action(params, items):
357
+ def action(params: dict, items: dict) -> None:
353
358
  org = params["org"]
354
359
  team = params["team"]
355
360
 
@@ -367,10 +372,10 @@ class RunnerAction:
367
372
 
368
373
  return action
369
374
 
370
- def add_to_org(self):
375
+ def add_to_org(self) -> Callable:
371
376
  label = "add_to_org"
372
377
 
373
- def action(params, items):
378
+ def action(params: dict, items: dict) -> None:
374
379
  org = params["org"]
375
380
 
376
381
  if self.dry_run:
@@ -387,10 +392,10 @@ class RunnerAction:
387
392
 
388
393
  return action
389
394
 
390
- def del_from_org(self):
395
+ def del_from_org(self) -> Callable:
391
396
  label = "del_from_org"
392
397
 
393
- def action(params, items):
398
+ def action(params: dict, items: dict) -> None:
394
399
  org = params["org"]
395
400
 
396
401
  if self.dry_run:
@@ -410,18 +415,18 @@ class RunnerAction:
410
415
  return action
411
416
 
412
417
  @staticmethod
413
- def raise_exception(msg):
414
- def raiseException(params, items):
418
+ def raise_exception(msg: str) -> Callable:
419
+ def raiseException(params: dict, items: dict) -> None:
415
420
  raise Exception(msg)
416
421
 
417
422
  return raiseException
418
423
 
419
424
 
420
- def service_is(service):
425
+ def service_is(service: str) -> Callable:
421
426
  return lambda params: params.get("service") == service
422
427
 
423
428
 
424
- def run(dry_run):
429
+ def run(dry_run: bool) -> None:
425
430
  config = get_config()
426
431
  gh_api_store = GHApiStore(config)
427
432
 
@@ -507,7 +512,7 @@ def run(dry_run):
507
512
  runner.run()
508
513
 
509
514
 
510
- def early_exit_desired_state(*args, **kwargs) -> dict[str, Any]:
515
+ def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
511
516
  return {
512
517
  "github_orgs": get_orgs(),
513
518
  "github_org_members": fetch_desired_state().dump(),
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import os
3
3
 
4
+ import github
4
5
  from github import Github
5
6
  from sretoolbox.utils import retry
6
7
 
@@ -40,10 +41,10 @@ ROLES_QUERY = """
40
41
  QONTRACT_INTEGRATION = "github-owners"
41
42
 
42
43
 
43
- def fetch_desired_state():
44
- desired_state = {}
44
+ def fetch_desired_state() -> dict[str, list[str]]:
45
+ desired_state: dict[str, list[str]] = {}
45
46
  gqlapi = gql.get_api()
46
- roles = expiration.filter(gqlapi.query(ROLES_QUERY)["roles"])
47
+ roles: list[dict] = expiration.filter(gqlapi.query(ROLES_QUERY)["roles"])
47
48
  for role in roles:
48
49
  permissions = [
49
50
  p
@@ -67,7 +68,9 @@ def fetch_desired_state():
67
68
 
68
69
 
69
70
  @retry()
70
- def get_current_github_usernames(github_org_name, github, raw_github):
71
+ def get_current_github_usernames(
72
+ github_org_name: str, github: Github, raw_github: RawGithubApi
73
+ ) -> tuple[github.Organization.Organization, list[str]]:
71
74
  gh_org = github.get_organization(github_org_name)
72
75
  gh_org_members = gh_org.get_members(role="admin")
73
76
  current_github_usernames = [m.login for m in gh_org_members]
@@ -77,7 +80,7 @@ def get_current_github_usernames(github_org_name, github, raw_github):
77
80
  return gh_org, current_github_usernames
78
81
 
79
82
 
80
- def run(dry_run):
83
+ def run(dry_run: bool) -> None:
81
84
  base_url = os.environ.get("GITHUB_API", "https://api.github.com")
82
85
  config = get_config()
83
86
  desired_state = fetch_desired_state()
@@ -95,7 +95,7 @@ def get_settings() -> Mapping[str, Any]:
95
95
  raise ValueError("no app-interface-settings found")
96
96
 
97
97
 
98
- def run(dry_run):
98
+ def run(dry_run: bool) -> set[str]:
99
99
  gqlapi = gql.get_api()
100
100
  settings = get_settings()
101
101
  secret_reader = SecretReader(settings=settings)
@@ -16,7 +16,7 @@ QONTRACT_INTEGRATION = "github-repo-permissions-validator"
16
16
  QONTRACT_INTEGRATION_VERSION = make_semver(0, 1, 0)
17
17
 
18
18
 
19
- def get_jobs(jjb: JJB, instance_name: str):
19
+ def get_jobs(jjb: JJB, instance_name: str) -> list[dict] | None:
20
20
  pr_check_jobs = jjb.get_all_jobs(
21
21
  job_types=["gh-pr-check"], instance_name=instance_name
22
22
  ).get(instance_name)
@@ -24,13 +24,13 @@ def get_jobs(jjb: JJB, instance_name: str):
24
24
  return pr_check_jobs
25
25
 
26
26
 
27
- def init_github():
27
+ def init_github() -> Github:
28
28
  base_url = os.environ.get("GITHUB_API", "https://api.github.com")
29
29
  token = get_default_config()["token"]
30
30
  return Github(token, base_url=base_url)
31
31
 
32
32
 
33
- def run(dry_run, instance_name):
33
+ def run(dry_run: bool, instance_name: str) -> None:
34
34
  secret_reader = SecretReader(queries.get_secret_reader_settings())
35
35
  jjb: JJB = init_jjb(secret_reader)
36
36
  pr_check_jobs = get_jobs(jjb, instance_name)
reconcile/github_users.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import os
3
3
  import re
4
+ from collections.abc import Callable
4
5
 
5
6
  from github import Github
6
7
  from github.GithubException import GithubException
@@ -30,19 +31,21 @@ GH_BASE_URL = os.environ.get("GITHUB_API", "https://api.github.com")
30
31
 
31
32
  QONTRACT_INTEGRATION = "github-users"
32
33
 
34
+ UserAndCompany = tuple[str, str | None]
33
35
 
34
- def init_github():
36
+
37
+ def init_github() -> Github:
35
38
  token = get_default_config()["token"]
36
39
  return Github(token, base_url=GH_BASE_URL)
37
40
 
38
41
 
39
42
  @retry(exceptions=(GithubException, ReadTimeout))
40
- def get_user_company(user, github):
43
+ def get_user_company(user: dict, github: Github) -> UserAndCompany:
41
44
  gh_user = github.get_user(login=user["github_username"])
42
45
  return user["org_username"], gh_user.company
43
46
 
44
47
 
45
- def get_users_to_delete(results):
48
+ def get_users_to_delete(results: list[UserAndCompany]) -> list[dict]:
46
49
  pattern = r"^.*[Rr]ed ?[Hh]at.*$"
47
50
  org_usernames_to_delete = [
48
51
  u for u, c in results if c is None or not re.search(pattern, c)
@@ -51,7 +54,7 @@ def get_users_to_delete(results):
51
54
  return [u for u in users_and_paths if u["username"] in org_usernames_to_delete]
52
55
 
53
56
 
54
- def send_email_notification(user, smtp_client: SmtpClient):
57
+ def send_email_notification(user: dict, smtp_client: SmtpClient) -> None:
55
58
  msg_template = """
56
59
  Hello,
57
60
 
@@ -79,13 +82,13 @@ App-Interface repository: https://gitlab.cee.redhat.com/service/app-interface
79
82
 
80
83
  @defer
81
84
  def run(
82
- dry_run,
83
- gitlab_project_id=None,
84
- thread_pool_size=10,
85
- enable_deletion=False,
86
- send_mails=False,
87
- defer=None,
88
- ):
85
+ dry_run: bool,
86
+ gitlab_project_id: str | None = None,
87
+ thread_pool_size: int = 10,
88
+ enable_deletion: bool = False,
89
+ send_mails: bool = False,
90
+ defer: Callable | None = None,
91
+ ) -> None:
89
92
  smtp_settings = typed_queries.smtp.settings()
90
93
  smtp_client = SmtpClient(
91
94
  server=get_smtp_server_connection(
@@ -104,7 +107,8 @@ def run(
104
107
 
105
108
  if not dry_run and enable_deletion:
106
109
  mr_cli = mr_client_gateway.init(gitlab_project_id=gitlab_project_id)
107
- defer(mr_cli.cleanup)
110
+ if defer:
111
+ defer(mr_cli.cleanup)
108
112
 
109
113
  for user in users_to_delete:
110
114
  username = user["username"]
@@ -10,7 +10,7 @@ from reconcile.utils.secret_reader import SecretReader
10
10
  QONTRACT_INTEGRATION = "github-validator"
11
11
 
12
12
 
13
- def run(dry_run):
13
+ def run(dry_run: bool) -> None:
14
14
  base_url = os.environ.get("GITHUB_API", "https://api.github.com")
15
15
  orgs = queries.get_github_orgs()
16
16
  settings = queries.get_app_interface_settings()