qontract-reconcile 0.10.2.dev263__py3-none-any.whl → 0.10.2.dev264__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qontract-reconcile
3
- Version: 0.10.2.dev263
3
+ Version: 0.10.2.dev264
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Project-URL: homepage, https://github.com/app-sre/qontract-reconcile
6
6
  Project-URL: repository, https://github.com/app-sre/qontract-reconcile
@@ -8,7 +8,7 @@ reconcile/aws_iam_password_reset.py,sha256=FpAqAXngmLFqkOtOjrz_i90qteUyLHJL0GMjo
8
8
  reconcile/aws_support_cases_sos.py,sha256=PDhilxQ4TBxVnxUPIUdTbKEaNUI0wzPiEsB91oHT2fY,3384
9
9
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=O1wFp52EyF538c6txaWBs8eMtUIy19gyHZ6VzJ6QXS8,3512
10
10
  reconcile/checkpoint.py,sha256=gjtS8g6KIyKFYlHMSZjAqDUOlVh83nh4go-9yNrhWZU,5016
11
- reconcile/cli.py,sha256=GJ15k4DljB0aMHvtsX7zxPI-tY1j4DTHpd9FUBPSkKk,112289
11
+ reconcile/cli.py,sha256=C4x30JxgSmKfqhOliWlJaBMsJjtLkd7UpWC9c5wjB4A,112236
12
12
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=_OKz7K7HHw0-gzxeEma8PcUCtd70pRBy7JMoaAm8IVU,4940
13
13
  reconcile/cluster_deployment_mapper.py,sha256=5gumAaRCcFXsabUJ1dnuUy9WrP_FEEM5JnOnE8ch9sE,2326
14
14
  reconcile/dashdotdb_base.py,sha256=83ZWIf5JJk3P_D69y2TmXRcQr6ELJGlv10OM0h7fJVs,4767
@@ -43,21 +43,21 @@ reconcile/jenkins_webhooks_cleaner.py,sha256=tFbAzsFGvJ6UrHRZFdIuLdqG-Ocd5_Oknfs
43
43
  reconcile/jenkins_worker_fleets.py,sha256=azjrrZ2mJyheuh96Cpv8eGkKO_86uKVlQzcSq0NQ2TI,5382
44
44
  reconcile/jira_permissions_validator.py,sha256=nVHZg7kNn04Q-ryNM20wthMrhXos28g3O9b0ahzxAKc,14690
45
45
  reconcile/ldap_users.py,sha256=oP1CAxmgSi3zDJ3vKTPySjap6WmEX1U469FmFrov5l4,4599
46
- reconcile/mr_client_gateway.py,sha256=WhjMd-sIXDFCV8-rt8CEjurJ5OYB1pOD0K3o0tZRXQg,1885
47
- reconcile/ocm_additional_routers.py,sha256=KfcFDVbNoc6n5dHWjYdAf1_DiVqVG6Tw23WLKoV8cdg,3306
48
- reconcile/ocm_addons.py,sha256=qqAyqRBRbdZQvAcjb-QlSVyRAyQBZk6iVlgnI4jyi7s,3353
46
+ reconcile/mr_client_gateway.py,sha256=3L21YncbetuUI3HYvDAEb5JX5HO5KG2CfUyjapX3w8E,2063
47
+ reconcile/ocm_additional_routers.py,sha256=e5P_OJAaV0jp0626dJ0Hp1-xUsEKXRCnrJzFTIQBdJg,3854
48
+ reconcile/ocm_addons.py,sha256=b59NWAMmd_zW4pxAH7hm8jzAkis0VZrUBmPYgduVlJc,3684
49
49
  reconcile/ocm_addons_upgrade_tests_trigger.py,sha256=A9zXeYG-_52DsS1dz47yDSnHz62du5XpPBlaeRa6zxY,3975
50
- reconcile/ocm_aws_infrastructure_access.py,sha256=rrlcQxvRTmKyYOUR1C-Nlxj_o_iCDSxc7PNoELUDiew,6892
51
- reconcile/ocm_clusters.py,sha256=1a6J1xYgfVLGZJe0Ob-lBFN9R6HKZ38xRjuWHQDamCo,17013
52
- reconcile/ocm_external_configuration_labels.py,sha256=imEpDv1RBpCSj8tHDv0R76hmNCFtcUzVNgS1yOVl8vs,3870
53
- reconcile/ocm_groups.py,sha256=nz6n4a0ZVDClpm1HbPfcwJ8fw3AYg7o4DCH-SBItVbs,3389
54
- reconcile/ocm_machine_pools.py,sha256=iiqReJMdzmvl9Ae0X1nT48WyROAoLS4pcI4Wk9NdWE0,16637
50
+ reconcile/ocm_aws_infrastructure_access.py,sha256=f-zVCEf0ucdslhWvfeLcXxQ6Y4xtfCXUG6pxswGgWnU,7251
51
+ reconcile/ocm_clusters.py,sha256=_nfrI9EeJ4PtLt7RWCHODqaA3G23sKkHMRCQK13twTA,17045
52
+ reconcile/ocm_external_configuration_labels.py,sha256=dGE2-WeuLdz9kmkuRLmpbHlOkwI3GyY6Jd_cvRpsyJo,4342
53
+ reconcile/ocm_groups.py,sha256=m-1-ho_pYadaldULF3ogCmC5GZ6nxEbbaLuME4UsMKw,4089
54
+ reconcile/ocm_machine_pools.py,sha256=KnTYAOw25N-QRb3Y7zbNY8w8xCR55WyqVjycYz24Yxk,17243
55
55
  reconcile/ocm_update_recommended_version.py,sha256=Vi3Y2sX-OQxx1mv_xiPQXnmrpsZzGIE38No0yBcTaD4,4204
56
56
  reconcile/ocm_upgrade_scheduler_org_updater.py,sha256=aLgyInt9oIWAg0XtCiwJRUSwdPx3masKV8kHzkyEEOQ,4282
57
57
  reconcile/openshift_base.py,sha256=S9CUvTaJhO_ibfqiIJ11ezMfSxiji-CEaMvCwK48OFk,52831
58
58
  reconcile/openshift_cluster_bots.py,sha256=Dgh7QhmPn1OKAHnzvmRI9kAvzdmuW7INNj11VmcT4Os,10934
59
59
  reconcile/openshift_clusterrolebindings.py,sha256=sDgHi_t2ayE3O6zZ5CLao7uBmihxRK8K70w2GSADz-w,5822
60
- reconcile/openshift_groups.py,sha256=2crqdhTOMw-21VMNhsOnKySyAWLxqAwGfiVLxL7ejx8,9329
60
+ reconcile/openshift_groups.py,sha256=zAXAF2Eej-TsUcOZqPe-dmlfXDlM8MD3AUsOC1-7Ltg,9188
61
61
  reconcile/openshift_limitranges.py,sha256=UvCGo_OQ4XoDK55TJmn55qEhhlkhLzhU12tX8nT5kPQ,3442
62
62
  reconcile/openshift_namespace_labels.py,sha256=4pC6uZICldzID0L2-cUUTC7UQawfmVXf8AEa2p24RZo,15588
63
63
  reconcile/openshift_namespaces.py,sha256=xG2scrmsi7VFeGv1Kc1d5kPY_r_JO6s5P85Ty4rRoys,5806
@@ -797,7 +797,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
797
797
  tools/saas_promotion_state/saas_promotion_state.py,sha256=oF7C4hpIgyMTwTRm3Aun3cDCHIjVar65JoLp6NcJHlU,3909
798
798
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
799
799
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
800
- qontract_reconcile-0.10.2.dev263.dist-info/METADATA,sha256=gu4n2dE6D3KKJFgAM7Cilxe7jLMAgq0LZP9GfZL5K94,24501
801
- qontract_reconcile-0.10.2.dev263.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
802
- qontract_reconcile-0.10.2.dev263.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
803
- qontract_reconcile-0.10.2.dev263.dist-info/RECORD,,
800
+ qontract_reconcile-0.10.2.dev264.dist-info/METADATA,sha256=6PUt4AX0OmDwYV7hjsuzytGQngumyFCkFFozm3YiFyw,24501
801
+ qontract_reconcile-0.10.2.dev264.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
802
+ qontract_reconcile-0.10.2.dev264.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
803
+ qontract_reconcile-0.10.2.dev264.dist-info/RECORD,,
reconcile/cli.py CHANGED
@@ -2944,12 +2944,11 @@ def ocm_update_recommended_version(
2944
2944
 
2945
2945
 
2946
2946
  @integration.command(short_help="Manages cluster Addons in OCM.")
2947
- @threaded()
2948
2947
  @click.pass_context
2949
- def ocm_addons(ctx: click.Context, thread_pool_size: int) -> None:
2948
+ def ocm_addons(ctx: click.Context) -> None:
2950
2949
  import reconcile.ocm_addons
2951
2950
 
2952
- run_integration(reconcile.ocm_addons, ctx, thread_pool_size)
2951
+ run_integration(reconcile.ocm_addons, ctx)
2953
2952
 
2954
2953
 
2955
2954
  @integration.command(
@@ -27,14 +27,19 @@ MR_GW_QUERY = """
27
27
  def get_mr_gateway_settings() -> Mapping[str, Any]:
28
28
  """Returns SecretReader settings"""
29
29
  gqlapi = gql.get_api()
30
- settings = gqlapi.query(MR_GW_QUERY)["settings"]
30
+ data = gqlapi.query(MR_GW_QUERY)
31
+ if not data:
32
+ raise ValueError("no app-interface-settings found from gql query")
33
+ settings = data.get("settings")
31
34
  if settings:
32
35
  # assuming a single settings file for now
33
36
  return settings[0]
34
37
  raise ValueError("no app-interface-settings found")
35
38
 
36
39
 
37
- def init(gitlab_project_id=None, sqs_or_gitlab=None):
40
+ def init(
41
+ gitlab_project_id: str | int | None = None, sqs_or_gitlab: str | None = None
42
+ ) -> GitLabApi | SQSGateway:
38
43
  """
39
44
  Creates the Merge Request client to of a given type.
40
45
 
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import logging
3
3
  import sys
4
- from collections.abc import Mapping
4
+ from collections.abc import Iterable, Mapping, MutableMapping
5
5
  from typing import Any
6
6
 
7
7
  from reconcile import queries
@@ -17,7 +17,9 @@ QONTRACT_INTEGRATION = "ocm-additional-routers"
17
17
  SUPPORTED_OCM_PRODUCTS = [OCM_PRODUCT_OSD]
18
18
 
19
19
 
20
- def fetch_current_state(clusters):
20
+ def fetch_current_state(
21
+ clusters: list[Mapping[str, Any]],
22
+ ) -> tuple[OCMMap, list[dict[str, Any]]]:
21
23
  settings = queries.get_app_interface_settings()
22
24
  ocm_map = OCMMap(
23
25
  clusters=clusters, integration=QONTRACT_INTEGRATION, settings=settings
@@ -35,13 +37,13 @@ def fetch_current_state(clusters):
35
37
  return ocm_map, current_state
36
38
 
37
39
 
38
- def fetch_desired_state(clusters):
40
+ def fetch_desired_state(clusters: Iterable[Mapping[str, Any]]) -> list[dict[str, Any]]:
39
41
  desired_state = []
40
42
  for cluster in clusters:
41
43
  cluster_name = cluster["name"]
42
44
  for router in cluster["additionalRouters"]:
43
45
  listening = "internal" if router["private"] else "external"
44
- item = {"listening": listening, "cluster": cluster_name}
46
+ item: dict[str, Any] = {"listening": listening, "cluster": cluster_name}
45
47
  selectors = router.get("route_selectors", None)
46
48
  if selectors:
47
49
  item["route_selectors"] = json.loads(selectors)
@@ -50,31 +52,34 @@ def fetch_desired_state(clusters):
50
52
  return desired_state
51
53
 
52
54
 
53
- def calculate_diff(current_state, desired_state):
55
+ def calculate_diff(
56
+ current_state: Iterable[MutableMapping[str, Any]],
57
+ desired_state: Iterable[MutableMapping[str, Any]],
58
+ ) -> list[MutableMapping[str, Any]]:
54
59
  diffs = []
55
- for d in desired_state:
56
- c = [c for c in current_state if d.items() <= c.items()]
57
- if not c:
58
- d["action"] = "create"
59
- diffs.append(d)
60
-
61
- for c in current_state:
62
- d = [d for d in desired_state if d.items() <= c.items()]
63
- if not d:
64
- c["action"] = "delete"
65
- diffs.append(c)
60
+ for d_item in desired_state:
61
+ c_items = [c for c in current_state if d_item.items() <= c.items()]
62
+ if not c_items:
63
+ d_item["action"] = "create"
64
+ diffs.append(d_item)
65
+
66
+ for c_item in current_state:
67
+ d_items = [d for d in desired_state if d.items() <= c_item.items()]
68
+ if not d_items:
69
+ c_item["action"] = "delete"
70
+ diffs.append(c_item)
66
71
 
67
72
  return diffs
68
73
 
69
74
 
70
- def sort_diffs(diff):
75
+ def sort_diffs(diff: Mapping[str, Any]) -> int:
71
76
  """Sort diffs so we delete first and create later"""
72
77
  if diff["action"] == "delete":
73
78
  return 1
74
79
  return 2
75
80
 
76
81
 
77
- def act(dry_run, diffs, ocm_map):
82
+ def act(dry_run: bool, diffs: list[MutableMapping[str, Any]], ocm_map: OCMMap) -> None:
78
83
  diffs.sort(key=sort_diffs)
79
84
  for diff in diffs:
80
85
  action = diff.pop("action")
@@ -82,6 +87,9 @@ def act(dry_run, diffs, ocm_map):
82
87
  logging.info([action, cluster])
83
88
  if not dry_run:
84
89
  ocm = ocm_map.get(cluster)
90
+ if ocm is None:
91
+ logging.error(f"OCM client for cluster {cluster} not found.")
92
+ continue
85
93
  if action == "create":
86
94
  ocm.create_additional_router(cluster, diff)
87
95
  elif action == "delete":
@@ -96,11 +104,10 @@ def _cluster_is_compatible(cluster: Mapping[str, Any]) -> bool:
96
104
  )
97
105
 
98
106
 
99
- def run(dry_run):
100
- clusters = queries.get_clusters()
107
+ def run(dry_run: bool) -> None:
101
108
  clusters = [
102
109
  c
103
- for c in clusters
110
+ for c in queries.get_clusters()
104
111
  if integration_is_enabled(QONTRACT_INTEGRATION, c) and _cluster_is_compatible(c)
105
112
  ]
106
113
  if not clusters:
reconcile/ocm_addons.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  import sys
3
- from collections.abc import Mapping
3
+ from collections.abc import Iterable, Mapping, MutableMapping
4
4
  from operator import itemgetter
5
5
  from typing import Any
6
6
 
@@ -12,7 +12,9 @@ from reconcile.utils.ocm import OCMMap
12
12
  QONTRACT_INTEGRATION = "ocm-addons"
13
13
 
14
14
 
15
- def fetch_current_state(clusters):
15
+ def fetch_current_state(
16
+ clusters: Iterable[Mapping[str, Any]],
17
+ ) -> tuple[OCMMap, list[dict[str, Any]]]:
16
18
  settings = queries.get_app_interface_settings()
17
19
  ocm_map = OCMMap(
18
20
  clusters=clusters,
@@ -35,7 +37,7 @@ def fetch_current_state(clusters):
35
37
  return ocm_map, current_state
36
38
 
37
39
 
38
- def fetch_desired_state(clusters):
40
+ def fetch_desired_state(clusters: Iterable[Mapping[str, Any]]) -> list[dict[str, Any]]:
39
41
  desired_state = []
40
42
  for cluster in clusters:
41
43
  cluster_name = cluster["name"]
@@ -49,13 +51,16 @@ def fetch_desired_state(clusters):
49
51
  return desired_state
50
52
 
51
53
 
52
- def sort_parameters(addon):
54
+ def sort_parameters(addon: MutableMapping[str, Any]) -> None:
53
55
  addon_parameters = addon.get("parameters")
54
56
  if addon_parameters:
55
57
  addon["parameters"] = sorted(addon_parameters, key=itemgetter("id"))
56
58
 
57
59
 
58
- def calculate_diff(current_state, desired_state):
60
+ def calculate_diff(
61
+ current_state: Iterable[Mapping[str, Any]],
62
+ desired_state: Iterable[MutableMapping[str, Any]],
63
+ ) -> list[MutableMapping[str, Any]]:
59
64
  diffs = []
60
65
  for d in desired_state:
61
66
  c = [c for c in current_state if d.items() <= c.items()]
@@ -66,7 +71,9 @@ def calculate_diff(current_state, desired_state):
66
71
  return diffs
67
72
 
68
73
 
69
- def act(dry_run, diffs, ocm_map):
74
+ def act(
75
+ dry_run: bool, diffs: Iterable[MutableMapping[str, Any]], ocm_map: OCMMap
76
+ ) -> bool:
70
77
  err = False
71
78
  for diff in diffs:
72
79
  action = diff.pop("action")
@@ -95,7 +102,7 @@ def _cluster_is_compatible(cluster: Mapping[str, Any]) -> bool:
95
102
  return cluster.get("ocm") is not None and cluster.get("addons") is not None
96
103
 
97
104
 
98
- def run(dry_run, gitlab_project_id=None, thread_pool_size=10):
105
+ def run(dry_run: bool) -> None:
99
106
  clusters = queries.get_clusters()
100
107
  clusters = [
101
108
  c
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  import sys
3
- from collections.abc import Mapping
3
+ from collections.abc import Iterable, Mapping
4
4
  from typing import Any
5
5
 
6
6
  from reconcile import queries
@@ -22,7 +22,9 @@ QONTRACT_INTEGRATION = "ocm-aws-infrastructure-access"
22
22
  SUPPORTED_OCM_PRODUCTS = [OCM_PRODUCT_OSD]
23
23
 
24
24
 
25
- def fetch_current_state(clusters):
25
+ def fetch_current_state(
26
+ clusters: Iterable[Mapping[str, Any]],
27
+ ) -> tuple[OCMMap, list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]:
26
28
  current_state = []
27
29
  current_failed = []
28
30
  current_deleting = []
@@ -52,7 +54,7 @@ def fetch_current_state(clusters):
52
54
  return ocm_map, current_state, current_failed, current_deleting
53
55
 
54
56
 
55
- def fetch_desired_state(clusters):
57
+ def fetch_desired_state(clusters: Iterable[Mapping[str, Any]]) -> list[dict[str, Any]]:
56
58
  desired_state = []
57
59
 
58
60
  for cluster_info in clusters:
@@ -129,8 +131,13 @@ def fetch_desired_state(clusters):
129
131
 
130
132
 
131
133
  def act(
132
- dry_run, ocm_map, current_state, current_failed, desired_state, current_deleting
133
- ):
134
+ dry_run: bool,
135
+ ocm_map: OCMMap,
136
+ current_state: list[dict[str, Any]],
137
+ current_failed: Iterable[dict[str, Any]],
138
+ desired_state: Iterable[dict[str, Any]],
139
+ current_deleting: list[dict[str, Any]],
140
+ ) -> None:
134
141
  to_delete = [c for c in current_state if c not in desired_state]
135
142
  to_delete += current_failed
136
143
  for item in to_delete:
@@ -173,7 +180,7 @@ def _cluster_is_compatible(cluster: Mapping[str, Any]) -> bool:
173
180
  )
174
181
 
175
182
 
176
- def get_clusters():
183
+ def get_clusters() -> list[dict[str, Any]]:
177
184
  return [
178
185
  c
179
186
  for c in queries.get_clusters(aws_infrastructure_access=True)
@@ -181,7 +188,7 @@ def get_clusters():
181
188
  ]
182
189
 
183
190
 
184
- def run(dry_run):
191
+ def run(dry_run: bool) -> None:
185
192
  clusters = get_clusters()
186
193
  if not clusters:
187
194
  logging.debug(
reconcile/ocm_clusters.py CHANGED
@@ -40,7 +40,7 @@ QONTRACT_INTEGRATION = "ocm-clusters"
40
40
  QONTRACT_INTEGRATION_VERSION = make_semver(0, 1, 0)
41
41
 
42
42
 
43
- def _set_rosa_ocm_attrs(cluster: Mapping[str, Any]):
43
+ def _set_rosa_ocm_attrs(cluster: Mapping[str, Any]) -> None:
44
44
  """Cluster account (aws) attribute from app-interface differs from the OCMSpec.
45
45
  app-interface's account includes the details for all the OCM environments
46
46
  but the cluster only needs the target OCM environment where it belongs.
@@ -78,8 +78,7 @@ def _set_rosa_ocm_attrs(cluster: Mapping[str, Any]):
78
78
 
79
79
  # doing this allows to exclude account fields which can be queried in graphql
80
80
  rosa_cluster_aws_account = ROSAClusterAWSAccount(
81
- uid=uid,
82
- rosa=rosa,
81
+ uid=uid, rosa=rosa, billing_account_id=None
83
82
  )
84
83
  if billing_account := account.get("billingAccount"):
85
84
  rosa_cluster_aws_account.billing_account_id = billing_account["uid"]
@@ -293,7 +292,7 @@ def get_cluster_ocm_update_spec(
293
292
 
294
293
  def _app_interface_updates_mr(
295
294
  clusters_updates: Mapping[str, Any], gitlab_project_id: str | None, dry_run: bool
296
- ):
295
+ ) -> None:
297
296
  """Creates an MR to app-interface with the necessary cluster manifest updates
298
297
 
299
298
  :param clusters_updates: Updates to perform. Format required by the MR utils code
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import logging
3
3
  import sys
4
- from collections.abc import Mapping
4
+ from collections.abc import Iterable, Mapping
5
5
  from typing import Any
6
6
 
7
7
  from reconcile import queries
@@ -12,14 +12,16 @@ from reconcile.utils.ocm import OCMMap
12
12
  QONTRACT_INTEGRATION = "ocm-external-configuration-labels"
13
13
 
14
14
 
15
- def get_allowed_labels_for_cluster(cluster: dict[str, Any]) -> set[str]:
15
+ def get_allowed_labels_for_cluster(cluster: Mapping[str, Any]) -> set[str]:
16
16
  allowed_labels = cluster.get("ocm", {}).get(
17
17
  "allowedClusterExternalConfigLabels", []
18
18
  )
19
19
  return set(allowed_labels)
20
20
 
21
21
 
22
- def fetch_current_state(clusters):
22
+ def fetch_current_state(
23
+ clusters: Iterable[Mapping[str, Any]],
24
+ ) -> tuple[OCMMap, list[dict[str, Any]]]:
23
25
  settings = queries.get_app_interface_settings()
24
26
  ocm_map = OCMMap(
25
27
  clusters=clusters, integration=QONTRACT_INTEGRATION, settings=settings
@@ -34,13 +36,16 @@ def fetch_current_state(clusters):
34
36
  for key, value in labels.items():
35
37
  if key not in allowed_labels:
36
38
  continue
37
- item = {"label": {"key": key, "value": value}, "cluster": cluster_name}
39
+ item = {
40
+ "label": {"key": key, "value": value},
41
+ "cluster": cluster_name,
42
+ }
38
43
  current_state.append(item)
39
44
 
40
45
  return ocm_map, current_state
41
46
 
42
47
 
43
- def fetch_desired_state(clusters):
48
+ def fetch_desired_state(clusters: Iterable[Mapping[str, Any]]) -> list[dict[str, Any]]:
44
49
  desired_state = []
45
50
  for cluster in clusters:
46
51
  cluster_name = cluster["name"]
@@ -57,32 +62,35 @@ def fetch_desired_state(clusters):
57
62
  return desired_state
58
63
 
59
64
 
60
- def calculate_diff(current_state, desired_state):
65
+ def calculate_diff(
66
+ current_state: Iterable[dict[str, Any]],
67
+ desired_state: Iterable[dict[str, Any]],
68
+ ) -> tuple[list[dict[str, Any]], bool]:
61
69
  diffs = []
62
70
  err = False
63
- for d in desired_state:
64
- c = [c for c in current_state if d == c]
65
- if not c:
66
- d["action"] = "create"
67
- diffs.append(d)
68
-
69
- for c in current_state:
70
- d = [d for d in desired_state if c == d]
71
- if not d:
72
- c["action"] = "delete"
73
- diffs.append(c)
71
+ for d_item in desired_state:
72
+ c_items = [c for c in current_state if d_item == c]
73
+ if not c_items:
74
+ d_item["action"] = "create"
75
+ diffs.append(d_item)
76
+
77
+ for c_item in current_state:
78
+ d_items = [d for d in desired_state if c_item == d]
79
+ if not d_items:
80
+ c_item["action"] = "delete"
81
+ diffs.append(c_item)
74
82
 
75
83
  return diffs, err
76
84
 
77
85
 
78
- def sort_diffs(diff):
86
+ def sort_diffs(diff: Mapping[str, Any]) -> int:
79
87
  """Sort diffs so we delete first and create later"""
80
88
  if diff["action"] == "delete":
81
89
  return 1
82
90
  return 2
83
91
 
84
92
 
85
- def act(dry_run, diffs, ocm_map):
93
+ def act(dry_run: bool, diffs: list[dict[str, Any]], ocm_map: OCMMap) -> None:
86
94
  diffs.sort(key=sort_diffs)
87
95
  for diff in diffs:
88
96
  action = diff["action"]
@@ -104,7 +112,11 @@ def _cluster_is_compatible(cluster: Mapping[str, Any]) -> bool:
104
112
  )
105
113
 
106
114
 
107
- def run(dry_run, gitlab_project_id=None, thread_pool_size=10):
115
+ def run(
116
+ dry_run: bool,
117
+ gitlab_project_id: str | None = None,
118
+ thread_pool_size: int = 10,
119
+ ) -> None:
108
120
  clusters = queries.get_clusters()
109
121
  clusters = [
110
122
  c
reconcile/ocm_groups.py CHANGED
@@ -1,7 +1,10 @@
1
1
  import itertools
2
2
  import logging
3
3
  import sys
4
- from collections.abc import Mapping
4
+ from collections.abc import (
5
+ Iterable,
6
+ Mapping,
7
+ )
5
8
  from typing import Any
6
9
 
7
10
  from sretoolbox.utils import threaded
@@ -18,7 +21,20 @@ from reconcile.utils.ocm.base import OCMClusterGroupId
18
21
  QONTRACT_INTEGRATION = "ocm-groups"
19
22
 
20
23
 
21
- def get_cluster_state(group_items, ocm_map):
24
+ def create_groups_list(clusters: Iterable[Mapping[str, Any]]) -> list[dict[str, str]]:
25
+ groups_list: list[dict[str, str]] = []
26
+ for cluster_info in clusters:
27
+ cluster = cluster_info["name"]
28
+ groups = cluster_info["managedGroups"] or []
29
+ groups_list.extend(
30
+ {"cluster": cluster, "group_name": group_name} for group_name in groups
31
+ )
32
+ return groups_list
33
+
34
+
35
+ def get_cluster_state(
36
+ group_items: Mapping[str, str], ocm_map: OCMMap
37
+ ) -> list[dict[str, str]]:
22
38
  cluster = group_items["cluster"]
23
39
  ocm = ocm_map.get(cluster)
24
40
  group_name = group_items["group_name"]
@@ -27,17 +43,18 @@ def get_cluster_state(group_items, ocm_map):
27
43
  return []
28
44
  return [
29
45
  {"cluster": cluster, "group": group_name, "user": user}
30
- for user in group["users"] or []
46
+ for user in group.get("users") or []
31
47
  ]
32
48
 
33
49
 
34
- def fetch_current_state(clusters, thread_pool_size):
35
- current_state = []
50
+ def fetch_current_state(
51
+ clusters: Iterable[Mapping[str, Any]], thread_pool_size: int
52
+ ) -> tuple[OCMMap, list[dict[str, str]]]:
36
53
  settings = queries.get_app_interface_settings()
37
54
  ocm_map = OCMMap(
38
55
  clusters=clusters, integration=QONTRACT_INTEGRATION, settings=settings
39
56
  )
40
- groups_list = openshift_groups.create_groups_list(clusters, oc_map=ocm_map)
57
+ groups_list = create_groups_list(clusters)
41
58
  results = threaded.run(
42
59
  get_cluster_state, groups_list, thread_pool_size, ocm_map=ocm_map
43
60
  )
@@ -46,7 +63,7 @@ def fetch_current_state(clusters, thread_pool_size):
46
63
  return ocm_map, current_state
47
64
 
48
65
 
49
- def act(diff, ocm_map):
66
+ def act(diff: Mapping[str, Any], ocm_map: OCMMap) -> None:
50
67
  cluster = diff["cluster"]
51
68
  group = diff["group"]
52
69
  user = diff["user"]
@@ -63,8 +80,12 @@ def _cluster_is_compatible(cluster: Mapping[str, Any]) -> bool:
63
80
  return cluster.get("ocm") is not None
64
81
 
65
82
 
66
- def run(dry_run, thread_pool_size=10):
83
+ def run(dry_run: bool, thread_pool_size: int = 10) -> None:
67
84
  clusters = queries.get_clusters()
85
+ if not clusters:
86
+ logging.debug("No clusters found in app-interface")
87
+ sys.exit(ExitCodes.SUCCESS)
88
+
68
89
  clusters = [
69
90
  c
70
91
  for c in clusters
@@ -97,10 +118,10 @@ def run(dry_run, thread_pool_size=10):
97
118
  act(diff, ocm_map)
98
119
 
99
120
 
100
- def early_exit_desired_state(*args, **kwargs) -> dict[str, Any]:
121
+ def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
101
122
  clusters = [
102
123
  c["name"]
103
- for c in queries.get_clusters()
124
+ for c in queries.get_clusters() or []
104
125
  if integration_is_enabled(QONTRACT_INTEGRATION, c) and _cluster_is_compatible(c)
105
126
  ]
106
127
  desired_state = openshift_groups.fetch_desired_state(clusters=clusters)
@@ -5,6 +5,7 @@ from abc import (
5
5
  )
6
6
  from collections.abc import Iterable, Mapping
7
7
  from enum import Enum
8
+ from typing import Any, Self
8
9
 
9
10
  from pydantic import (
10
11
  BaseModel,
@@ -48,11 +49,11 @@ class AbstractAutoscaling(BaseModel):
48
49
  )
49
50
 
50
51
  @abstractmethod
51
- def get_min(self):
52
+ def get_min(self) -> int:
52
53
  pass
53
54
 
54
55
  @abstractmethod
55
- def get_max(self):
56
+ def get_max(self) -> int:
56
57
  pass
57
58
 
58
59
 
@@ -61,9 +62,12 @@ class MachinePoolAutoscaling(AbstractAutoscaling):
61
62
  max_replicas: int
62
63
 
63
64
  @root_validator()
64
- @classmethod
65
- def max_greater_min(cls, field_values):
66
- if field_values.get("min_replicas") > field_values.get("max_replicas"):
65
+ def max_greater_min(cls, field_values: Mapping[str, Any]) -> Mapping[str, Any]:
66
+ min_replicas = field_values.get("min_replicas")
67
+ max_replicas = field_values.get("max_replicas")
68
+ if min_replicas is None or max_replicas is None:
69
+ raise ValueError("min_replicas and max_replicas must be set")
70
+ if min_replicas > max_replicas:
67
71
  raise ValueError("max_replicas must be greater than min_replicas")
68
72
  return field_values
69
73
 
@@ -79,10 +83,13 @@ class NodePoolAutoscaling(AbstractAutoscaling):
79
83
  max_replica: int
80
84
 
81
85
  @root_validator()
82
- @classmethod
83
- def max_greater_min(cls, field_values):
84
- if field_values.get("min_replica") > field_values.get("max_replica"):
85
- raise ValueError("max_replicas must be greater than min_replicas")
86
+ def max_greater_min(cls, field_values: Mapping[str, Any]) -> Mapping[str, Any]:
87
+ min_replica = field_values.get("min_replica")
88
+ max_replica = field_values.get("max_replica")
89
+ if min_replica is None or max_replica is None:
90
+ raise ValueError("min_replica and max_replica must be set")
91
+ if min_replica > max_replica:
92
+ raise ValueError("max_replica must be greater than min_replica")
86
93
  return field_values
87
94
 
88
95
  def get_min(self) -> int:
@@ -104,8 +111,7 @@ class AbstractPool(ABC, BaseModel):
104
111
  autoscaling: AbstractAutoscaling | None
105
112
 
106
113
  @root_validator()
107
- @classmethod
108
- def validate_scaling(cls, field_values):
114
+ def validate_scaling(cls, field_values: Mapping[str, Any]) -> Mapping[str, Any]:
109
115
  if field_values.get("autoscaling") and field_values.get("replicas"):
110
116
  raise ValueError("autoscaling and replicas are mutually exclusive")
111
117
  return field_values
@@ -134,14 +140,12 @@ class AbstractPool(ABC, BaseModel):
134
140
  def deletable(self) -> bool:
135
141
  pass
136
142
 
137
- def _has_diff_autoscale(self, pool):
138
- match (self.autoscaling, pool.autoscale):
139
- case (None, None):
140
- return False
141
- case (None, _) | (_, None):
142
- return True
143
- case _:
144
- return self.autoscaling.has_diff(pool.autoscale)
143
+ def _has_diff_autoscale(self, pool: ClusterMachinePoolV1) -> bool:
144
+ if self.autoscaling is None and pool.autoscale is None:
145
+ return False
146
+ if self.autoscaling is None or pool.autoscale is None:
147
+ return True
148
+ return self.autoscaling.has_diff(pool.autoscale)
145
149
 
146
150
 
147
151
  class MachinePool(AbstractPool):
@@ -198,7 +202,7 @@ class MachinePool(AbstractPool):
198
202
  pool: ClusterMachinePoolV1,
199
203
  cluster: str,
200
204
  cluster_type: ClusterType,
201
- ):
205
+ ) -> Self:
202
206
  autoscaling: MachinePoolAutoscaling | None = None
203
207
  if pool.autoscale:
204
208
  autoscaling = MachinePoolAutoscaling(
@@ -278,7 +282,7 @@ class NodePool(AbstractPool):
278
282
  pool: ClusterMachinePoolV1,
279
283
  cluster: str,
280
284
  cluster_type: ClusterType,
281
- ):
285
+ ) -> Self:
282
286
  autoscaling: NodePoolAutoscaling | None = None
283
287
  if pool.autoscale:
284
288
  autoscaling = NodePoolAutoscaling(
@@ -369,7 +373,7 @@ def _classify_cluster_type(cluster: ClusterV1) -> ClusterType:
369
373
  raise ValueError(f"unknown cluster type for cluster {cluster.name}")
370
374
 
371
375
 
372
- def fetch_current_state_for_cluster(cluster, ocm):
376
+ def fetch_current_state_for_cluster(cluster: ClusterV1, ocm: OCM) -> list[AbstractPool]:
373
377
  cluster_type = _classify_cluster_type(cluster)
374
378
  if cluster_type == ClusterType.ROSA_HCP:
375
379
  return [
@@ -505,14 +509,15 @@ def act(dry_run: bool, diffs: Iterable[PoolHandler], ocm_map: OCMMap) -> None:
505
509
  logging.info([diff.action, diff.pool.cluster, diff.pool.id])
506
510
  if not dry_run:
507
511
  ocm = ocm_map.get(diff.pool.cluster)
508
- diff.act(dry_run, ocm)
512
+ if ocm is not None:
513
+ diff.act(dry_run, ocm)
509
514
 
510
515
 
511
516
  def _cluster_is_compatible(cluster: ClusterV1) -> bool:
512
517
  return cluster.ocm is not None and cluster.machine_pools is not None
513
518
 
514
519
 
515
- def run(dry_run: bool):
520
+ def run(dry_run: bool) -> None:
516
521
  clusters = get_clusters()
517
522
 
518
523
  filtered_clusters = [
@@ -5,11 +5,11 @@ from collections.abc import (
5
5
  Iterable,
6
6
  Mapping,
7
7
  )
8
- from typing import Any
9
8
 
10
9
  from sretoolbox.utils import threaded
11
10
 
12
11
  import reconcile.openshift_base as ob
12
+ from reconcile.gql_definitions.common.clusters import ClusterV1
13
13
  from reconcile.gql_definitions.openshift_groups.managed_groups import (
14
14
  query as query_managed_groups,
15
15
  )
@@ -62,20 +62,16 @@ def get_cluster_state(
62
62
 
63
63
 
64
64
  def create_groups_list(
65
- clusters: Iterable[Mapping[str, Any]], oc_map: ClusterMap
65
+ clusters: Iterable[ClusterV1], oc_map: ClusterMap
66
66
  ) -> list[dict[str, str]]:
67
- """
68
- Also used by ocm-groups integration and thus requires to work with dict for now
69
- """
70
67
  groups_list: list[dict[str, str]] = []
71
- for cluster_info in clusters:
72
- cluster = cluster_info["name"]
73
- oc = oc_map.get(cluster)
68
+ for cluster in clusters:
69
+ oc = oc_map.get(cluster.name)
74
70
  if isinstance(oc, OCLogMsg):
75
71
  logging.log(level=oc.log_level, msg=oc.message)
76
- groups = cluster_info["managedGroups"] or []
72
+ groups = cluster.managed_groups or []
77
73
  groups_list.extend(
78
- {"cluster": cluster, "group_name": group_name} for group_name in groups
74
+ {"cluster": cluster.name, "group_name": group_name} for group_name in groups
79
75
  )
80
76
  return groups_list
81
77
 
@@ -97,7 +93,7 @@ def fetch_current_state(
97
93
  thread_pool_size=thread_pool_size,
98
94
  )
99
95
 
100
- groups_list = create_groups_list([c.dict(by_alias=True) for c in clusters], oc_map)
96
+ groups_list = create_groups_list(clusters, oc_map)
101
97
  results = threaded.run(
102
98
  get_cluster_state, groups_list, thread_pool_size, oc_map=oc_map
103
99
  )