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.
- {qontract_reconcile-0.9.1rc94.dist-info → qontract_reconcile-0.9.1rc95.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.9.1rc94.dist-info → qontract_reconcile-0.9.1rc95.dist-info}/RECORD +8 -7
- reconcile/openshift_namespace_labels.py +70 -60
- reconcile/test/test_openshift_namespace_labels.py +93 -62
- reconcile/typed_queries/namespaces.py +11 -0
- {qontract_reconcile-0.9.1rc94.dist-info → qontract_reconcile-0.9.1rc95.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.9.1rc94.dist-info → qontract_reconcile-0.9.1rc95.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.9.1rc94.dist-info → qontract_reconcile-0.9.1rc95.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.9.
|
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=
|
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=
|
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.
|
471
|
-
qontract_reconcile-0.9.
|
472
|
-
qontract_reconcile-0.9.
|
473
|
-
qontract_reconcile-0.9.
|
474
|
-
qontract_reconcile-0.9.
|
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
|
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:
|
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
|
207
|
+
return namespace.cluster.name, namespace.name
|
197
208
|
|
198
209
|
|
199
|
-
def get_gql_namespaces_in_shard() -> list[
|
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 =
|
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
|
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:
|
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
|
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(
|
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(
|
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
|
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:
|
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(
|
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:
|
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
|
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:
|
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
|
-
|
424
|
-
|
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,
|
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
|
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
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
44
|
-
return
|
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
|
94
|
-
"""Mock
|
95
|
-
return [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
|
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
|
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.
|
152
|
-
self.
|
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.
|
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, "
|
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, "
|
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", "
|
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, "
|
219
|
+
NS(c1, "namespace", k1v1, k1, k1v1_k2v2),
|
195
220
|
]
|
196
221
|
run_integration()
|
197
|
-
self.state.add.assert_called_once_with(
|
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(
|
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, "
|
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, "
|
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", "
|
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, "
|
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", "
|
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, "
|
259
|
+
NS(c1, "namespace", k1v1, k1_k2, k1v1),
|
231
260
|
]
|
232
261
|
run_integration()
|
233
|
-
self.state.add.assert_called_once_with(
|
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, "
|
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, "
|
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", "
|
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, "
|
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, "
|
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, "
|
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, "
|
279
|
-
NS(c1, "
|
280
|
-
NS(c1, "
|
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, "
|
291
|
-
NS(c2, "
|
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, "
|
297
|
-
call(state_key(c2, "
|
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", "
|
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, "
|
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 [])
|
File without changes
|
{qontract_reconcile-0.9.1rc94.dist-info → qontract_reconcile-0.9.1rc95.dist-info}/entry_points.txt
RENAMED
File without changes
|
{qontract_reconcile-0.9.1rc94.dist-info → qontract_reconcile-0.9.1rc95.dist-info}/top_level.txt
RENAMED
File without changes
|