qontract-reconcile 0.9.1rc94__py3-none-any.whl → 0.9.1rc95__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.1
2
2
  Name: qontract-reconcile
3
- Version: 0.9.1rc94
3
+ Version: 0.9.1rc95
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Home-page: https://github.com/app-sre/qontract-reconcile
6
6
  Author: Red Hat App-SRE Team
@@ -79,7 +79,7 @@ reconcile/openshift_base.py,sha256=-vEJEQAES6-QDGnGkWKGwjkG7rt4DLWd8YngZn0zhlo,4
79
79
  reconcile/openshift_clusterrolebindings.py,sha256=cztreWXqrkxLgKvJGGWJg4ZleU1hxvY8Z6rRgCbyahc,5771
80
80
  reconcile/openshift_groups.py,sha256=t-wlYPA5a_futYATEyJCPrehHRr-2iAcIH29S3Qnr6M,9390
81
81
  reconcile/openshift_limitranges.py,sha256=SZkHhGS3m9oBUkzcclRiszfLEB7jQ8z8xh9P2Y8KTzg,3393
82
- reconcile/openshift_namespace_labels.py,sha256=TwDwPwc4XMuMieRwOn3XexcVnPfgIfof2NGGONoUFk0,15161
82
+ reconcile/openshift_namespace_labels.py,sha256=Kgk91c0-iQ9czTVaNfh5Q8h0uvEhosM3dKagzCu_tK0,15843
83
83
  reconcile/openshift_namespaces.py,sha256=K_Okla8OTt9EFvdT__wjujbLnR9Iqhy-9dDqUPduVjA,4945
84
84
  reconcile/openshift_network_policies.py,sha256=X48jTvKOXhrLwWymkxbieYqJg01bDn90FnXFDr0WBI8,4189
85
85
  reconcile/openshift_resourcequotas.py,sha256=QR73mIa7rCEQgUqfRCCvlg9trqFNPjeIJZ_qVzY-mSQ,2795
@@ -278,7 +278,7 @@ reconcile/test/test_ocm_update_recommended_version.py,sha256=ONY3slwUHwEdonvIYRI
278
278
  reconcile/test/test_ocm_upgrade_scheduler.py,sha256=tBbCUQ0ZyunfF4R5Ang5yquYhzwQ_4QMUP6ZaHpqW0E,20567
279
279
  reconcile/test/test_ocm_upgrade_scheduler_org_updater.py,sha256=zYRGUX7pAmxSv9oFYw2ZnPGa-YAPgDfmqXOJM4eE-8A,4353
280
280
  reconcile/test/test_openshift_base.py,sha256=OpTsZ2hwcSjov4wpjg5Yw6ZtlVTCOvR2uU57hlM_AV0,19378
281
- reconcile/test/test_openshift_namespace_labels.py,sha256=wzyw4fibMBnT2FFzqLk23jzgq9x_f2C90y_Z_KUXSsI,10956
281
+ reconcile/test/test_openshift_namespace_labels.py,sha256=MqjESFjJQsRku7SKW17d9A5SmlK0ZUyDSV4-2BVS6Pg,12101
282
282
  reconcile/test/test_openshift_namespaces.py,sha256=fHfI3wDA3glYrdCxwqE8X9WCVYguyVwuUhudoubQQps,8188
283
283
  reconcile/test/test_openshift_resource.py,sha256=YxXONreobUzn43iw4pCx4AZC4dONjCeZ6hvEwYsBin0,12106
284
284
  reconcile/test/test_openshift_resources_base.py,sha256=zqPsk_FHovOBjj5lPs4OQboP4PDhxz6D35cRWkvQx50,12963
@@ -353,6 +353,7 @@ reconcile/typed_queries/__init__.py,sha256=rRk4CyslLsBr4vAh1pIPgt6s3P4R1M9NSEPLn
353
353
  reconcile/typed_queries/app_interface_vault_settings.py,sha256=N0GiRYDzlPipTOMM50ieR08BxsXaGAga6GxiIKjQDS8,1002
354
354
  reconcile/typed_queries/clusters.py,sha256=mjEudUxMl0pqT0CIbDhgRONLQqfhTvDPS4SVyl0zIpM,393
355
355
  reconcile/typed_queries/clusters_minimal.py,sha256=MwFj2F_rqBFDI3sPbR4QbiME6HIYEPhrOyy07i_-oA8,409
356
+ reconcile/typed_queries/namespaces.py,sha256=vItPrn7sfcHOix-VvkzQkf54_ljzI_ymyxh5esdBJ5Y,262
356
357
  reconcile/typed_queries/ocp_release_mirror.py,sha256=jwX29Tcdvov8oEDNkE4t1j-2Hz8QJrjg9ITppP-panQ,313
357
358
  reconcile/typed_queries/pagerduty_instances.py,sha256=QCHqEAakiH6eSob0Pnnn3IBd8Ga0zpEp1Z6Qu3v2uH4,733
358
359
  reconcile/typed_queries/smtp.py,sha256=aSLglYa5bHKmlGwKkxq2RZqyMWuAf0a4S_mOuhDa084,542
@@ -467,8 +468,8 @@ tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y
467
468
  tools/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
468
469
  tools/test/test_qontract_cli.py,sha256=taXJPi2yJrV5ql6G3IivU3Lj5mNXje-nUEjp6uXa1oc,1396
469
470
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
470
- qontract_reconcile-0.9.1rc94.dist-info/METADATA,sha256=VDGUTyAS8UwL_n3hKYop6O5Y73FVF_a9zCPaQfGW96Y,2256
471
- qontract_reconcile-0.9.1rc94.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
472
- qontract_reconcile-0.9.1rc94.dist-info/entry_points.txt,sha256=3BPvsRryM1C4S_mb5kXmP5AVv-wJBzVCrOJyv6qUmc0,195
473
- qontract_reconcile-0.9.1rc94.dist-info/top_level.txt,sha256=j0CHPIc8TsVRB50wOz_jhxjjaRyCJB3NOQeXhuHS67c,34
474
- qontract_reconcile-0.9.1rc94.dist-info/RECORD,,
471
+ qontract_reconcile-0.9.1rc95.dist-info/METADATA,sha256=VdW6w4GerY--SRx9wGBl7k1CYkAIpaf3CNWpR_B-Uig,2256
472
+ qontract_reconcile-0.9.1rc95.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
473
+ qontract_reconcile-0.9.1rc95.dist-info/entry_points.txt,sha256=3BPvsRryM1C4S_mb5kXmP5AVv-wJBzVCrOJyv6qUmc0,195
474
+ qontract_reconcile-0.9.1rc95.dist-info/top_level.txt,sha256=j0CHPIc8TsVRB50wOz_jhxjjaRyCJB3NOQeXhuHS67c,34
475
+ qontract_reconcile-0.9.1rc95.dist-info/RECORD,,
@@ -1,7 +1,9 @@
1
- import json
2
1
  import logging
3
2
  import sys
4
- from collections.abc import Generator
3
+ from collections.abc import (
4
+ Callable,
5
+ Generator,
6
+ )
5
7
  from threading import Lock
6
8
  from typing import (
7
9
  Any,
@@ -14,13 +16,22 @@ from sretoolbox.utils import threaded
14
16
 
15
17
  import reconcile.openshift_base as ob
16
18
  from reconcile import queries
19
+ from reconcile.gql_definitions.common.namespaces import NamespaceV1
20
+ from reconcile.typed_queries.app_interface_vault_settings import (
21
+ get_app_interface_vault_settings,
22
+ )
23
+ from reconcile.typed_queries.namespaces import get_namespaces
17
24
  from reconcile.utils.defer import defer
18
25
  from reconcile.utils.oc import (
19
- OC_Map,
20
- OCNative,
21
26
  StatusCodeError,
22
27
  validate_labels,
23
28
  )
29
+ from reconcile.utils.oc_map import (
30
+ OCLogMsg,
31
+ OCMap,
32
+ init_oc_map_from_namespaces,
33
+ )
34
+ from reconcile.utils.secret_reader import create_secret_reader
24
35
  from reconcile.utils.sharding import is_in_shard
25
36
  from reconcile.utils.state import State
26
37
 
@@ -68,7 +79,7 @@ class LabelInventory:
68
79
  Defaults to []"""
69
80
  return self._errors.setdefault(cluster, {}).setdefault(namespace, [])
70
81
 
71
- def add_error(self, cluster: str, namespace: str, err: str):
82
+ def add_error(self, cluster: str, namespace: str, err: str) -> None:
72
83
  """Add an error to the given cluster / namespace"""
73
84
  self.errors(cluster, namespace).append(err)
74
85
 
@@ -110,7 +121,7 @@ class LabelInventory:
110
121
  self._ns(cluster, namespace)[type] = labels
111
122
  return labels
112
123
 
113
- def delete(self, cluster: str, namespace: str):
124
+ def delete(self, cluster: str, namespace: str) -> None:
114
125
  """Delete the given cluster / namespace from the inventory"""
115
126
  with self._lock:
116
127
  self._inv.get(cluster, {}).pop(namespace, None)
@@ -122,7 +133,7 @@ class LabelInventory:
122
133
  for namespace, types in namespaces.items():
123
134
  yield cluster, namespace, types
124
135
 
125
- def update_managed_keys(self, cluster: str, namespace: str, key: str):
136
+ def update_managed_keys(self, cluster: str, namespace: str, key: str) -> None:
126
137
  """
127
138
  Add or remove a key from the managed key list.
128
139
  This actually handles a copy of the managed keys dict and updates it.
@@ -188,52 +199,31 @@ class LabelInventory:
188
199
  changed[k] = None
189
200
 
190
201
 
191
- def get_names_for_namespace(namespace: dict[str, Any]) -> tuple[str, str]:
202
+ def get_names_for_namespace(namespace: NamespaceV1) -> tuple[str, str]:
192
203
  """
193
204
  Get the cluster and namespace names from the provided
194
205
  namespace qontract info
195
206
  """
196
- return namespace["cluster"]["name"], namespace["name"]
207
+ return namespace.cluster.name, namespace.name
197
208
 
198
209
 
199
- def get_gql_namespaces_in_shard() -> list[Any]:
210
+ def get_gql_namespaces_in_shard() -> list[NamespaceV1]:
200
211
  """
201
212
  Get all namespaces from qontract-server and filter those which are in
202
213
  our shard
203
214
  """
204
- all_namespaces = queries.get_namespaces()
215
+ all_namespaces = get_namespaces()
205
216
 
206
217
  return [
207
218
  ns
208
219
  for ns in all_namespaces
209
- if not ob.is_namespace_deleted(ns)
210
- and is_in_shard(f"{ns['cluster']['name']}/{ns['name']}")
220
+ if not ob.is_namespace_deleted(ns.dict(by_alias=True))
221
+ and is_in_shard(f"{ns.cluster.name}/{ns.name}")
211
222
  ]
212
223
 
213
224
 
214
- def get_oc_map(
215
- namespaces: list[Any],
216
- internal: Optional[bool],
217
- use_jump_host: bool,
218
- thread_pool_size: int,
219
- ) -> OC_Map:
220
- """
221
- Get an OC_Map for our namespaces
222
- """
223
- settings = queries.get_app_interface_settings()
224
- return OC_Map(
225
- namespaces=namespaces,
226
- integration=QONTRACT_INTEGRATION,
227
- settings=settings,
228
- internal=internal,
229
- use_jump_host=use_jump_host,
230
- thread_pool_size=thread_pool_size,
231
- init_projects=True,
232
- )
233
-
234
-
235
225
  def get_desired(
236
- inventory: LabelInventory, oc_map: OC_Map, namespaces: list[Any]
226
+ inventory: LabelInventory, oc_map: OCMap, namespaces: list[NamespaceV1]
237
227
  ) -> None:
238
228
  """
239
229
  Fill the provided label inventory with every desired info from the
@@ -242,7 +232,7 @@ def get_desired(
242
232
  """
243
233
  to_be_ignored = []
244
234
  for ns in namespaces:
245
- if "labels" not in ns:
235
+ if not ns.labels:
246
236
  continue
247
237
 
248
238
  cluster, ns_name = get_names_for_namespace(ns)
@@ -250,21 +240,22 @@ def get_desired(
250
240
  # eg: internal settings may not match --internal / --external param
251
241
  if cluster not in oc_map.clusters():
252
242
  continue
253
- labels = json.loads(ns["labels"])
254
243
 
255
- validation_errors = validate_labels(labels)
244
+ validation_errors = validate_labels(ns.labels)
256
245
  for err in validation_errors:
257
- inventory.add_error(cluster, ns_name, err)
258
- if inventory.errors(cluster, ns_name):
246
+ inventory.add_error(cluster=cluster, namespace=ns_name, err=err)
247
+ if inventory.errors(cluster=cluster, namespace=ns_name):
259
248
  continue
260
249
 
261
- if inventory.get(cluster, ns_name, DESIRED) is not None:
250
+ if inventory.get(cluster=cluster, namespace=ns_name, type=DESIRED) is not None:
262
251
  # delete at the end of the loop to avoid having a reinsertion at
263
252
  # the third/fifth/.. occurrences
264
253
  to_be_ignored.append((cluster, ns_name))
265
254
  continue
266
255
 
267
- inventory.set(cluster, ns_name, DESIRED, labels)
256
+ inventory.set(
257
+ cluster=cluster, namespace=ns_name, type=DESIRED, labels=ns.labels
258
+ )
268
259
 
269
260
  for cluster, ns_name in to_be_ignored:
270
261
  # Log only a warning here and do not report errors nor fail the
@@ -274,10 +265,10 @@ def get_desired(
274
265
  _LOG.debug(
275
266
  f"Found several namespace definitions for " f"{cluster}/{ns_name}. Ignoring"
276
267
  )
277
- inventory.delete(cluster, ns_name)
268
+ inventory.delete(cluster=cluster, namespace=ns_name)
278
269
 
279
270
 
280
- def state_key(cluster: str, namespace: str):
271
+ def state_key(cluster: str, namespace: str) -> str:
281
272
  return f"{cluster}/{namespace}-managed-labels"
282
273
 
283
274
 
@@ -297,18 +288,21 @@ def get_managed(inventory: LabelInventory, state: State) -> None:
297
288
  if f"/{key}" not in keys:
298
289
  continue
299
290
  managed = state.get(key, [])
300
- inventory.set(cluster, ns_name, MANAGED, managed)
291
+ inventory.set(cluster=cluster, namespace=ns_name, type=MANAGED, labels=managed)
301
292
 
302
293
 
303
- def lookup_namespaces(cluster: str, oc_map: OC_Map):
294
+ def lookup_namespaces(
295
+ cluster: str, oc_map: OCMap
296
+ ) -> tuple[str, Optional[dict[str, Any]]]:
304
297
  """
305
298
  Retrieve all namespaces from the given cluster
306
299
  """
307
300
  try:
308
301
  oc = oc_map.get(cluster)
309
- if not oc:
302
+ if isinstance(oc, OCLogMsg):
310
303
  # cluster is not reachable (may be used --internal / --external ?)
311
304
  _LOG.debug(f"Skipping not-handled cluster: {cluster}")
305
+ logging.debug(msg=oc.message)
312
306
  return cluster, None
313
307
  _LOG.debug(f"Looking up namespaces on {cluster}")
314
308
  namespaces = oc.get_all("Namespace")
@@ -328,7 +322,7 @@ def lookup_namespaces(cluster: str, oc_map: OC_Map):
328
322
 
329
323
 
330
324
  def get_current(
331
- inventory: LabelInventory, oc_map: OC_Map, thread_pool_size: int
325
+ inventory: LabelInventory, oc_map: OCMap, thread_pool_size: int
332
326
  ) -> None:
333
327
  """
334
328
  Fill the provided label inventory with every current info from the
@@ -349,15 +343,17 @@ def get_current(
349
343
  if inventory.get(cluster, ns_name, DESIRED) is None:
350
344
  continue
351
345
  labels = ns_meta.get("labels", {})
352
- inventory.set(cluster, ns_name, CURRENT, labels)
346
+ inventory.set(
347
+ cluster=cluster, namespace=ns_name, type=CURRENT, labels=labels
348
+ )
353
349
 
354
350
 
355
351
  def label(
356
352
  inv_item: tuple[str, str, Types],
357
- oc_map: OC_Map,
353
+ oc_map: OCMap,
358
354
  dry_run: bool,
359
355
  inventory: LabelInventory,
360
- ):
356
+ ) -> None:
361
357
  cluster, namespace, types = inv_item
362
358
  if inventory.errors(cluster, namespace):
363
359
  return
@@ -366,14 +362,17 @@ def label(
366
362
  prefix = "[dry-run] " if dry_run else ""
367
363
  _LOG.info(prefix + f"Updating labels on {cluster}/{namespace}: {changed}")
368
364
  if not dry_run:
369
- oc: OCNative = oc_map.get(cluster)
365
+ oc = oc_map.get(cluster)
366
+ if isinstance(oc, OCLogMsg):
367
+ logging.log(level=oc.log_level, msg=oc.message)
368
+ return
370
369
  oc.label(None, "Namespace", namespace, changed, overwrite=True)
371
370
 
372
371
 
373
372
  def realize(
374
373
  inventory: LabelInventory,
375
374
  state: State,
376
- oc_map: OC_Map,
375
+ oc_map: OCMap,
377
376
  dry_run: bool,
378
377
  thread_pool_size: int,
379
378
  ) -> None:
@@ -411,25 +410,36 @@ def run(
411
410
  thread_pool_size: int = 10,
412
411
  internal: Optional[bool] = None,
413
412
  use_jump_host: bool = True,
414
- defer=None,
415
- raise_errors=False,
416
- ):
413
+ defer: Optional[Callable] = None,
414
+ raise_errors: bool = False,
415
+ ) -> None:
417
416
  _LOG.debug("Collecting GQL data ...")
418
417
  namespaces = get_gql_namespaces_in_shard()
419
418
 
420
419
  inventory = LabelInventory()
421
420
 
422
421
  _LOG.debug("Initializing OC_Map ...")
423
- oc_map = get_oc_map(namespaces, internal, use_jump_host, thread_pool_size)
424
- defer(oc_map.cleanup)
422
+ vault_settings = get_app_interface_vault_settings()
423
+ secret_reader = create_secret_reader(use_vault=vault_settings.vault)
424
+ oc_map = init_oc_map_from_namespaces(
425
+ namespaces=namespaces,
426
+ integration=QONTRACT_INTEGRATION,
427
+ secret_reader=secret_reader,
428
+ internal=internal,
429
+ use_jump_host=use_jump_host,
430
+ thread_pool_size=thread_pool_size,
431
+ init_projects=True,
432
+ )
433
+
434
+ if defer:
435
+ defer(oc_map.cleanup)
425
436
 
426
437
  _LOG.debug("Collecting desired state ...")
427
438
  get_desired(inventory, oc_map, namespaces)
428
439
 
429
- settings = queries.get_app_interface_settings()
430
440
  accounts = queries.get_state_aws_accounts()
431
441
  state = State(
432
- integration=QONTRACT_INTEGRATION, accounts=accounts, settings=settings
442
+ integration=QONTRACT_INTEGRATION, accounts=accounts, secret_reader=secret_reader
433
443
  )
434
444
  _LOG.debug("Collecting managed state ...")
435
445
  get_managed(inventory, state)
@@ -1,17 +1,32 @@
1
- import json
1
+ from dataclasses import dataclass
2
2
  from typing import Optional
3
3
  from unittest import TestCase
4
4
  from unittest.mock import (
5
5
  Mock,
6
6
  call,
7
+ create_autospec,
7
8
  patch,
8
9
  )
9
10
 
10
11
  from reconcile import openshift_namespace_labels
12
+ from reconcile.gql_definitions.common.app_interface_vault_settings import (
13
+ AppInterfaceSettingsV1,
14
+ )
15
+ from reconcile.gql_definitions.common.namespaces import NamespaceV1
11
16
  from reconcile.openshift_namespace_labels import state_key
17
+ from reconcile.test.fixtures import Fixtures
18
+ from reconcile.utils.oc_map import OCMap
19
+ from reconcile.utils.secret_reader import SecretReaderBase
20
+
21
+ fxt = Fixtures("openshift_namespace_labels")
22
+
23
+
24
+ def load_namespace(name: str) -> NamespaceV1:
25
+ content = fxt.get_anymarkup(name)
26
+ return NamespaceV1(**content)
12
27
 
13
28
 
14
- # TODO: use a dataclass when python > 3.6
29
+ @dataclass
15
30
  class NS:
16
31
  """
17
32
  Simple utility class holding test information about current,
@@ -20,28 +35,21 @@ class NS:
20
35
  Some methods are there to convert this data into GQL, State or OC outputs.
21
36
  """
22
37
 
23
- def __init__(
24
- self,
25
- cluster: str,
26
- name: str,
27
- current: Optional[dict[str, str]],
28
- managed: Optional[list[str]],
29
- desired: dict[str, str],
30
- exists: bool = True,
31
- ):
32
- self.cluster = cluster
33
- self.name = name
34
- self.current = current
35
- self.managed = managed
36
- self.desired = desired
37
- self.exists = exists
38
-
39
- def gql(self):
40
- """Get this namespace as an output of GQL"""
41
- d = {"name": self.name, "cluster": {"name": self.cluster}}
38
+ cluster: str
39
+ name: str
40
+ current: Optional[dict[str, str]]
41
+ managed: Optional[list[str]]
42
+ desired: dict[str, str]
43
+ exists: bool = True
44
+
45
+ def data(self):
46
+ """Get typed namespace data"""
47
+ namespace = load_namespace(f"{self.name}.yml")
48
+ namespace.name = self.name
49
+ namespace.cluster.name = self.cluster
42
50
  if self.desired is not None:
43
- d["labels"] = json.dumps(self.desired)
44
- return d
51
+ namespace.labels = self.desired
52
+ return namespace
45
53
 
46
54
  def oc_get_all(self):
47
55
  """Get this namespace as an output of oc get namespace"""
@@ -90,16 +98,16 @@ class TestOpenshiftNamespaceLabels(TestCase):
90
98
  This allows to code teh mock logic only once.
91
99
  """
92
100
 
93
- def _queries_get_namespaces(self):
94
- """Mock queries.get_namespaces() by returning our test data"""
95
- return [ns.gql() for ns in self.test_ns]
101
+ def _get_namespaces(self):
102
+ """Mock get_namespaces() by returning our test data"""
103
+ return [ns.data() for ns in self.test_ns]
96
104
 
97
105
  def _oc_map_clusters(self):
98
- """Mock OCM_Map.clusters() by listing clusters in our test data"""
106
+ """Mock OCMap.clusters() by listing clusters in our test data"""
99
107
  return list({ns.cluster for ns in self.test_ns if ns.exists})
100
108
 
101
109
  def _oc_map_get(self, cluster):
102
- """Mock OCM_Map.get() by getting namespaces from our test data"""
110
+ """Mock OCMap.get() by getting namespaces from our test data"""
103
111
  oc = self.oc_clients.setdefault(cluster, Mock(name=f"oc_{cluster}"))
104
112
  ns = [
105
113
  ns.oc_get_all()
@@ -142,17 +150,32 @@ class TestOpenshiftNamespaceLabels(TestCase):
142
150
 
143
151
  module = "reconcile.openshift_namespace_labels"
144
152
 
145
- # mock all {module}.queries functions
146
- # with a side_effect on get_namespaces only
147
153
  self.queries_patcher = patch(f"{module}.queries")
148
154
  self.queries = self.queries_patcher.start()
149
- self.queries.get_namespaces.side_effect = self._queries_get_namespaces
150
155
 
151
- self.oc_map_patcher = patch(f"{module}.OC_Map")
152
- self.oc_map = self.oc_map_patcher.start().return_value
156
+ self.namespaces_patcher = patch(f"{module}.get_namespaces")
157
+ self.namespaces = self.namespaces_patcher.start()
158
+ self.namespaces.side_effect = self._get_namespaces
159
+
160
+ vault_settings = AppInterfaceSettingsV1(vault=False)
161
+ self.get_vault_settings_patcher = patch(
162
+ f"{module}.get_app_interface_vault_settings"
163
+ )
164
+ self.get_vault_settings = self.get_vault_settings_patcher.start()
165
+ self.get_vault_settings.side_effect = [vault_settings]
166
+
167
+ self.create_secret_reader_patcher = patch(f"{module}.create_secret_reader")
168
+ self.create_secret_reader = self.create_secret_reader_patcher.start()
169
+ self.create_secret_reader.side_effect = [create_autospec(spec=SecretReaderBase)]
170
+
171
+ self.oc_map = create_autospec(spec=OCMap)
153
172
  self.oc_map.clusters.side_effect = self._oc_map_clusters
154
173
  self.oc_map.get.side_effect = self._oc_map_get
155
174
 
175
+ self.init_oc_map_patcher = patch(f"{module}.init_oc_map_from_namespaces")
176
+ self.init_oc_map = self.init_oc_map_patcher.start()
177
+ self.init_oc_map.side_effect = [self.oc_map]
178
+
156
179
  self.state_patcher = patch(f"{module}.State")
157
180
  self.state = self.state_patcher.start().return_value
158
181
  self.state.ls.side_effect = self._state_ls
@@ -161,14 +184,16 @@ class TestOpenshiftNamespaceLabels(TestCase):
161
184
 
162
185
  def tearDown(self) -> None:
163
186
  """cleanup patches created in self.setUp"""
164
- self.oc_map_patcher.stop()
187
+ self.init_oc_map_patcher.stop()
165
188
  self.queries_patcher.stop()
166
189
  self.state_patcher.stop()
190
+ self.create_secret_reader_patcher.stop()
191
+ self.namespaces_patcher.stop()
167
192
 
168
193
  def test_no_change(self):
169
194
  """No label change: nothing should be done"""
170
195
  self.test_ns = [
171
- NS(c1, "no-change", k1v1, k1, k1v1),
196
+ NS(c1, "namespace", k1v1, k1, k1v1),
172
197
  ]
173
198
  run_integration()
174
199
  self.state.add.assert_not_called()
@@ -178,59 +203,65 @@ class TestOpenshiftNamespaceLabels(TestCase):
178
203
  def test_update(self):
179
204
  """single label value change"""
180
205
  self.test_ns = [
181
- NS(c1, "update", k1v1, k1, k1v2),
206
+ NS(c1, "namespace", k1v1, k1, k1v2),
182
207
  ]
183
208
  run_integration()
184
209
  # no change in the managed key store: we have the same keys than before
185
210
  self.state.add.assert_not_called()
186
211
  oc = self.oc_clients[c1]
187
212
  oc.label.assert_called_once_with(
188
- None, "Namespace", "update", k1v2, overwrite=True
213
+ None, "Namespace", "namespace", k1v2, overwrite=True
189
214
  )
190
215
 
191
216
  def test_add(self):
192
217
  """addition of a label"""
193
218
  self.test_ns = [
194
- NS(c1, "add", k1v1, k1, k1v1_k2v2),
219
+ NS(c1, "namespace", k1v1, k1, k1v1_k2v2),
195
220
  ]
196
221
  run_integration()
197
- self.state.add.assert_called_once_with(state_key(c1, "add"), k1_k2, force=True)
222
+ self.state.add.assert_called_once_with(
223
+ state_key(c1, "namespace"), k1_k2, force=True
224
+ )
198
225
  oc = self.oc_clients[c1]
199
- oc.label.assert_called_once_with(None, "Namespace", "add", k2v2, overwrite=True)
226
+ oc.label.assert_called_once_with(
227
+ None, "Namespace", "namespace", k2v2, overwrite=True
228
+ )
200
229
 
201
230
  def test_add_from_none(self):
202
231
  """addition of a label from none on the current namespace"""
203
232
  self.test_ns = [
204
- NS(c1, "add-from-none", {}, None, k1v1),
233
+ NS(c1, "namespace", {}, None, k1v1),
205
234
  ]
206
235
  run_integration()
207
236
  self.state.add.assert_called_once_with(
208
- state_key(c1, "add-from-none"), k1, force=True
237
+ state_key(c1, "namespace"), k1, force=True
209
238
  )
210
239
  oc = self.oc_clients[c1]
211
240
  oc.label.assert_called_once_with(
212
- None, "Namespace", "add-from-none", k1v1, overwrite=True
241
+ None, "Namespace", "namespace", k1v1, overwrite=True
213
242
  )
214
243
 
215
244
  def test_remove_step1(self):
216
245
  """removal of a label step 1: remove label"""
217
246
  self.test_ns = [
218
- NS(c1, "remove", k1v1_k2v2, k1_k2, k1v1),
247
+ NS(c1, "namespace", k1v1_k2v2, k1_k2, k1v1),
219
248
  ]
220
249
  run_integration()
221
250
  self.state.add.assert_not_called()
222
251
  oc = self.oc_clients[c1]
223
252
  oc.label.assert_called_once_with(
224
- None, "Namespace", "remove", {"k2": None}, overwrite=True
253
+ None, "Namespace", "namespace", {"k2": None}, overwrite=True
225
254
  )
226
255
 
227
256
  def test_remove_step2(self):
228
257
  """removal of a label step 2: remove key from managed state"""
229
258
  self.test_ns = [
230
- NS(c1, "remove", k1v1, k1_k2, k1v1),
259
+ NS(c1, "namespace", k1v1, k1_k2, k1v1),
231
260
  ]
232
261
  run_integration()
233
- self.state.add.assert_called_once_with(state_key(c1, "remove"), k1, force=True)
262
+ self.state.add.assert_called_once_with(
263
+ state_key(c1, "namespace"), k1, force=True
264
+ )
234
265
  oc = self.oc_clients[c1]
235
266
  oc.label.assert_not_called()
236
267
 
@@ -238,27 +269,27 @@ class TestOpenshiftNamespaceLabels(TestCase):
238
269
  """Remove, add and modify labels all at once, step 1 (removals are in
239
270
  two steps)"""
240
271
  self.test_ns = [
241
- NS(c1, "all-in-one", k1v1_k2v2, k1_k2, k2v3_k3v3),
272
+ NS(c1, "namespace", k1v1_k2v2, k1_k2, k2v3_k3v3),
242
273
  ]
243
274
  run_integration()
244
275
  self.state.add.assert_called_once_with(
245
- state_key(c1, "all-in-one"), k1_k2_k3, force=True
276
+ state_key(c1, "namespace"), k1_k2_k3, force=True
246
277
  )
247
278
  oc = self.oc_clients[c1]
248
279
  labels = {"k1": None, "k2": "v3", "k3": "v3"}
249
280
  oc.label.assert_called_once_with(
250
- None, "Namespace", "all-in-one", labels, overwrite=True
281
+ None, "Namespace", "namespace", labels, overwrite=True
251
282
  )
252
283
 
253
284
  def test_remove_add_modify_step2(self):
254
285
  """Remove, add and modify labels all at once, step 2 (removals are in
255
286
  two steps)"""
256
287
  self.test_ns = [
257
- NS(c1, "all-in-one", k2v3_k3v3, k1_k2_k3, k2v3_k3v3),
288
+ NS(c1, "namespace", k2v3_k3v3, k1_k2_k3, k2v3_k3v3),
258
289
  ]
259
290
  run_integration()
260
291
  self.state.add.assert_called_once_with(
261
- state_key(c1, "all-in-one"), k2_k3, force=True
292
+ state_key(c1, "namespace"), k2_k3, force=True
262
293
  )
263
294
  oc = self.oc_clients[c1]
264
295
  oc.label.assert_not_called()
@@ -266,7 +297,7 @@ class TestOpenshiftNamespaceLabels(TestCase):
266
297
  def test_namespace_not_exists(self):
267
298
  """namespace does not exist (yet)"""
268
299
  self.test_ns = [
269
- NS(c1, "not-exists", None, None, k1v1, exists=False),
300
+ NS(c1, "namespace", None, None, k1v1, exists=False),
270
301
  ]
271
302
  run_integration()
272
303
  self.state.add.assert_not_called()
@@ -275,9 +306,9 @@ class TestOpenshiftNamespaceLabels(TestCase):
275
306
  def test_duplicate_namespace(self):
276
307
  """Namespace declared several times in a single cluster: ignored"""
277
308
  self.test_ns = [
278
- NS(c1, "no-change", k1v1, k1, k1v1),
279
- NS(c1, "no-change", k1v1, k1, k2v2),
280
- NS(c1, "no-change", k1v1, k1, k1v2),
309
+ NS(c1, "namespace", k1v1, k1, k1v1),
310
+ NS(c1, "namespace", k1v1, k1, k2v2),
311
+ NS(c1, "namespace", k1v1, k1, k1v2),
281
312
  ]
282
313
  run_integration()
283
314
  self.state.add.assert_not_called()
@@ -287,14 +318,14 @@ class TestOpenshiftNamespaceLabels(TestCase):
287
318
  def test_multi_cluster(self):
288
319
  """Namespace declared in several clusters. All get updated"""
289
320
  self.test_ns = [
290
- NS(c1, "multi-cluster", k1v1, k1, k1v1_k2v2),
291
- NS(c2, "multi-cluster", k1v1, k1, k1v1_k2v2),
321
+ NS(c1, "namespace", k1v1, k1, k1v1_k2v2),
322
+ NS(c2, "namespace", k1v1, k1, k1v1_k2v2),
292
323
  ]
293
324
  run_integration()
294
325
  self.assertEqual(self.state.add.call_count, 2)
295
326
  calls = [
296
- call(state_key(c1, "multi-cluster"), k1_k2, force=True),
297
- call(state_key(c2, "multi-cluster"), k1_k2, force=True),
327
+ call(state_key(c1, "namespace"), k1_k2, force=True),
328
+ call(state_key(c2, "namespace"), k1_k2, force=True),
298
329
  ]
299
330
  self.state.add.assert_has_calls(calls)
300
331
 
@@ -302,13 +333,13 @@ class TestOpenshiftNamespaceLabels(TestCase):
302
333
  self.assertIn(c2, self.oc_clients)
303
334
  for oc in self.oc_clients.values():
304
335
  oc.label.assert_called_once_with(
305
- None, "Namespace", "multi-cluster", k2v2, overwrite=True
336
+ None, "Namespace", "namespace", k2v2, overwrite=True
306
337
  )
307
338
 
308
339
  def test_dry_run(self):
309
340
  """Ensures nothing is done in dry_run mode"""
310
341
  self.test_ns = [
311
- NS(c1, "many-changes", k1v1_k2v2, k1_k2, k2v3_k3v3),
342
+ NS(c1, "namespace", k1v1_k2v2, k1_k2, k2v3_k3v3),
312
343
  ]
313
344
  run_integration(dry_run=True)
314
345
  self.state.add.assert_not_called()
@@ -0,0 +1,11 @@
1
+ from reconcile.gql_definitions.common.namespaces import (
2
+ NamespaceV1,
3
+ query,
4
+ )
5
+ from reconcile.utils import gql
6
+
7
+
8
+ def get_namespaces() -> list[NamespaceV1]:
9
+ gqlapi = gql.get_api()
10
+ data = query(gqlapi.query)
11
+ return list(data.namespaces or [])