qontract-reconcile 0.10.2.dev18__py3-none-any.whl → 0.10.2.dev20__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.dev18
3
+ Version: 0.10.2.dev20
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
@@ -185,7 +185,7 @@ reconcile/cna/assets/asset_factory.py,sha256=7T7X_J6xIsoGETqBRI45_EyIKEdQcnRPt_G
185
185
  reconcile/cna/assets/null.py,sha256=85mVh97atCoC0aLuX47poTZiyOthmziJeBsUw0c924w,1658
186
186
  reconcile/dynatrace_token_provider/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
187
187
  reconcile/dynatrace_token_provider/dependencies.py,sha256=FuRUnK18EyJIIgFwQBZSskIG08mN2VQAcAcaJFTf8zc,2812
188
- reconcile/dynatrace_token_provider/integration.py,sha256=mfcfCKFTMehiXaClMXJHXe_GFsa9nCfgjnAl_T4a3P8,26267
188
+ reconcile/dynatrace_token_provider/integration.py,sha256=usNTif2yrzWp7V24QHmMI1bd4QxwObYbxsqfJAYREXc,26576
189
189
  reconcile/dynatrace_token_provider/metrics.py,sha256=oP-6NTZENFdvWiS0krnmX6tq3xyOzQ8e6vS0CZWYUuw,1496
190
190
  reconcile/dynatrace_token_provider/model.py,sha256=gkpqo5rRRueBXnIMjp4EEHqBUBuU65TRI8zpdb8GJ0A,241
191
191
  reconcile/dynatrace_token_provider/ocm.py,sha256=MwYCZIxW4f-1jzFTxxN__sity6S8O7bbKUdyTFEVO7U,4325
@@ -197,12 +197,12 @@ reconcile/endpoints_discovery/merge_request_manager.py,sha256=wUMsumxv8RnWaRatta
197
197
  reconcile/external_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
198
198
  reconcile/external_resources/aws.py,sha256=NSaOeHqFEcMaMxNjJwuQZosolgsJ8XRVvwkEEBj9vrw,7730
199
199
  reconcile/external_resources/factories.py,sha256=TyJMaijDfPIFYks9i6dhKN7nSR1BoCkoBs1iPExKpcE,5493
200
- reconcile/external_resources/integration.py,sha256=gBVO5dE8JyZ3xYcYik-MTIp_18oU7_hpYc_oztyfElQ,6753
200
+ reconcile/external_resources/integration.py,sha256=JF38M7R0Z4ADUTx57TZqSZH9k_xpPlbAxQAcGyIISuM,6925
201
201
  reconcile/external_resources/integration_secrets_sync.py,sha256=dX09O3r6KURziUYYfiki10orNjOGVma-XojhVqd0ww4,1667
202
- reconcile/external_resources/manager.py,sha256=eVaaGCaKDkc897xt5cA5-B4yYuS9VWR-Z7Uom0uSsG0,15971
202
+ reconcile/external_resources/manager.py,sha256=q7Ezp-g3MhpH_t8zpNMH5wCzHvh-nERpHzvBPA1G1ng,17031
203
203
  reconcile/external_resources/meta.py,sha256=noaytFzmShpzLA_ebGh7wuP45mOfHIOnnoUxivjDa1I,672
204
204
  reconcile/external_resources/metrics.py,sha256=KiBjMUaN_z0cSkF_7Ar_a8RiuiwVqjyMcVdISlxhzXE,3898
205
- reconcile/external_resources/model.py,sha256=YJylbAhetN9szpLUFd9jFqxCRMvSWXVxSC9OMQNV-wg,11316
205
+ reconcile/external_resources/model.py,sha256=KdB3eYlopWOLQmvL1aICjm-kAPIlY2N_zSikcCFBQpk,11751
206
206
  reconcile/external_resources/reconciler.py,sha256=K9QvbQCIOCuOHnPIxQE_P_jFtrkF3dGo8d_cCCh08Ys,8973
207
207
  reconcile/external_resources/secrets_sync.py,sha256=50fK4fzgSz-K8uy5_DQQWA_ju_rTDYAC2HRymgfY7TA,16344
208
208
  reconcile/external_resources/state.py,sha256=ye8yjMoCtTHSRhDH7skFLDIHIuYTjisWYCTJrwnmbEw,9565
@@ -766,7 +766,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
766
766
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
767
767
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
768
768
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
769
- qontract_reconcile-0.10.2.dev18.dist-info/METADATA,sha256=vxQFz-cgRhQ5es2GJgtvVjOXk3OWWfS7nMbp4DAWsi0,24665
770
- qontract_reconcile-0.10.2.dev18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
771
- qontract_reconcile-0.10.2.dev18.dist-info/entry_points.txt,sha256=JniHZPadNOILPyfSl0LF2YSp3Db7K2_W2CN7i9f3Gos,540
772
- qontract_reconcile-0.10.2.dev18.dist-info/RECORD,,
769
+ qontract_reconcile-0.10.2.dev20.dist-info/METADATA,sha256=dMYY6LBhL21XhtZxss8dwyRBMyK2ce1Yh7UvoUBK2bM,24665
770
+ qontract_reconcile-0.10.2.dev20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
771
+ qontract_reconcile-0.10.2.dev20.dist-info/entry_points.txt,sha256=JniHZPadNOILPyfSl0LF2YSp3Db7K2_W2CN7i9f3Gos,540
772
+ qontract_reconcile-0.10.2.dev20.dist-info/RECORD,,
@@ -37,12 +37,17 @@ from reconcile.utils.ocm.base import (
37
37
  OCMServiceLogSeverity,
38
38
  )
39
39
  from reconcile.utils.ocm.labels import subscription_label_filter
40
- from reconcile.utils.openshift_resource import QONTRACT_ANNOTATION_INTEGRATION
40
+ from reconcile.utils.openshift_resource import (
41
+ QONTRACT_ANNOTATION_INTEGRATION,
42
+ QONTRACT_ANNOTATION_INTEGRATION_VERSION,
43
+ )
41
44
  from reconcile.utils.runtime.integration import (
42
45
  NoParams,
43
46
  QontractReconcileIntegration,
44
47
  )
48
+ from reconcile.utils.semver_helper import make_semver
45
49
 
50
+ QONTRACT_INTEGRATION_VERSION = make_semver(2, 0, 1)
46
51
  QONTRACT_INTEGRATION = "dynatrace-token-provider"
47
52
  SYNCSET_AND_MANIFEST_ID = "ext-dynatrace-tokens-dtp"
48
53
 
@@ -65,10 +70,11 @@ class DynatraceTokenProviderIntegration(QontractReconcileIntegration[NoParams]):
65
70
  def get_early_exit_desired_state(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
66
71
  """Return the desired state for early exit."""
67
72
  return {
73
+ "version": QONTRACT_INTEGRATION_VERSION,
68
74
  "specs": {
69
75
  spec.name: spec.dict()
70
76
  for spec in get_dynatrace_token_provider_token_specs()
71
- }
77
+ },
72
78
  }
73
79
 
74
80
  @property
@@ -577,6 +583,7 @@ class DynatraceTokenProviderIntegration(QontractReconcileIntegration[NoParams]):
577
583
  "namespace": secret.namespace_name,
578
584
  "annotations": {
579
585
  QONTRACT_ANNOTATION_INTEGRATION: QONTRACT_INTEGRATION,
586
+ QONTRACT_ANNOTATION_INTEGRATION_VERSION: QONTRACT_INTEGRATION_VERSION,
580
587
  },
581
588
  },
582
589
  "data": data,
@@ -3,6 +3,7 @@ from collections.abc import Callable
3
3
  from typing import Any
4
4
 
5
5
  from reconcile.external_resources.manager import (
6
+ ExternalResourceDryRunsValidator,
6
7
  ExternalResourcesInventory,
7
8
  ExternalResourcesManager,
8
9
  setup_factories,
@@ -89,6 +90,10 @@ def create_er_manager(
89
90
  m_inventory = load_module_inventory(get_modules())
90
91
  namespaces = [ns for ns in get_namespaces() if ns.external_resources]
91
92
  er_inventory = ExternalResourcesInventory(namespaces)
93
+ state_manager = ExternalResourcesStateDynamoDB(
94
+ aws_api=aws_api,
95
+ table_name=er_settings.state_dynamodb_table,
96
+ )
92
97
 
93
98
  if not workers_cluster:
94
99
  workers_cluster = er_settings.workers_cluster.name
@@ -104,10 +109,7 @@ def create_er_manager(
104
109
  ),
105
110
  er_inventory=er_inventory,
106
111
  module_inventory=m_inventory,
107
- state_manager=ExternalResourcesStateDynamoDB(
108
- aws_api=aws_api,
109
- table_name=er_settings.state_dynamodb_table,
110
- ),
112
+ state_manager=state_manager,
111
113
  reconciler=K8sExternalResourcesReconciler(
112
114
  controller=build_job_controller(
113
115
  integration=QONTRACT_INTEGRATION,
@@ -128,6 +130,9 @@ def create_er_manager(
128
130
  thread_pool_size=thread_pool_size,
129
131
  dry_run=dry_run,
130
132
  ),
133
+ dry_runs_validator=ExternalResourceDryRunsValidator(
134
+ state_manager, er_inventory
135
+ ),
131
136
  )
132
137
 
133
138
 
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from collections import Counter
2
3
  from collections.abc import Iterable
3
4
  from datetime import UTC, datetime
4
5
 
@@ -19,6 +20,7 @@ from reconcile.external_resources.model import (
19
20
  ExternalResourceKey,
20
21
  ExternalResourceModuleConfiguration,
21
22
  ExternalResourceOrphanedResourcesError,
23
+ ExternalResourceOutputResourceNameDuplications,
22
24
  ExternalResourcesInventory,
23
25
  ExternalResourceValidationError,
24
26
  ModuleInventory,
@@ -73,6 +75,41 @@ def setup_factories(
73
75
  return of
74
76
 
75
77
 
78
+ class ExternalResourceDryRunsValidator:
79
+ def __init__(
80
+ self,
81
+ state_manager: ExternalResourcesStateDynamoDB,
82
+ er_inventory: ExternalResourcesInventory,
83
+ ):
84
+ self.state_mgr = state_manager
85
+ self.er_inventory = er_inventory
86
+
87
+ def _check_output_resource_name_duplications(
88
+ self,
89
+ ) -> None:
90
+ specs = Counter(
91
+ (
92
+ spec.cluster_name,
93
+ spec.namespace_name,
94
+ spec.output_resource_name,
95
+ )
96
+ for spec in self.er_inventory.values()
97
+ )
98
+ if duplicates := [key for key, count in specs.items() if count > 1]:
99
+ raise ExternalResourceOutputResourceNameDuplications(duplicates)
100
+
101
+ def _check_orphaned_objects(self) -> None:
102
+ state_keys = self.state_mgr.get_all_resource_keys()
103
+ inventory_keys = set(self.er_inventory.keys())
104
+ orphans = state_keys - inventory_keys
105
+ if len(orphans) > 0:
106
+ raise ExternalResourceOrphanedResourcesError(orphans)
107
+
108
+ def validate(self) -> None:
109
+ self._check_orphaned_objects()
110
+ self._check_output_resource_name_duplications()
111
+
112
+
76
113
  class ExternalResourcesManager:
77
114
  def __init__(
78
115
  self,
@@ -84,6 +121,7 @@ class ExternalResourcesManager:
84
121
  er_inventory: ExternalResourcesInventory,
85
122
  factories: ObjectFactory[ExternalResourceFactory],
86
123
  secrets_reconciler: InClusterSecretsReconciler,
124
+ dry_runs_validator: ExternalResourceDryRunsValidator,
87
125
  thread_pool_size: int,
88
126
  ) -> None:
89
127
  self.state_mgr = state_manager
@@ -96,6 +134,7 @@ class ExternalResourcesManager:
96
134
  self.secrets_reconciler = secrets_reconciler
97
135
  self.errors: dict[ExternalResourceKey, ExternalResourceValidationError] = {}
98
136
  self.thread_pool_size = thread_pool_size
137
+ self.dry_runs_validator = dry_runs_validator
99
138
 
100
139
  def _get_reconcile_action(
101
140
  self, reconciliation: Reconciliation, state: ExternalResourceState
@@ -199,13 +238,6 @@ class ExternalResourcesManager:
199
238
  to_reconcile.add(r)
200
239
  return to_reconcile
201
240
 
202
- def _check_orphaned_objects(self) -> None:
203
- state_keys = self.state_mgr.get_all_resource_keys()
204
- inventory_keys = set(self.er_inventory.keys())
205
- orphans = state_keys - inventory_keys
206
- if len(orphans) > 0:
207
- raise ExternalResourceOrphanedResourcesError(orphans)
208
-
209
241
  def _get_reconciliation_status(
210
242
  self,
211
243
  r: Reconciliation,
@@ -374,7 +406,7 @@ class ExternalResourcesManager:
374
406
  self._sync_secrets(to_sync_keys=to_sync_keys | pending_sync_keys)
375
407
 
376
408
  def handle_dry_run_resources(self) -> None:
377
- self._check_orphaned_objects()
409
+ self.dry_runs_validator.validate()
378
410
  desired_r = self._get_desired_objects_reconciliations()
379
411
  deleted_r = self._get_deleted_objects_reconciliations()
380
412
  reconciliations = desired_r.union(deleted_r)
@@ -45,6 +45,17 @@ class ExternalResourceOrphanedResourcesError(Exception):
45
45
  super().__init__("".join(msg))
46
46
 
47
47
 
48
+ class ExternalResourceOutputResourceNameDuplications(Exception):
49
+ def __init__(self, duplicates: Iterable[tuple[str, str, str]]) -> None:
50
+ msg = [
51
+ "There are output_resource_name attribute duplications. ",
52
+ "output_resource_name must be unique within a cluster/namespace.\n"
53
+ "Duplications:\n",
54
+ "\n".join(map(str, duplicates)),
55
+ ]
56
+ super().__init__("".join(msg))
57
+
58
+
48
59
  class ExternalResourceValidationError(Exception):
49
60
  errors: list[str] = []
50
61