qontract-reconcile 0.10.1rc1198__py3-none-any.whl → 0.10.1rc1200__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.10.1rc1198
3
+ Version: 0.10.1rc1200
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
@@ -192,16 +192,16 @@ reconcile/endpoints_discovery/integration.py,sha256=znfnlm8bZesfcNbQnaR2aaVM-DTB
192
192
  reconcile/endpoints_discovery/merge_request.py,sha256=_yLb4tnvoZMCko8rta2C_CvOInJa9pa3HzSmHNtjgGU,2978
193
193
  reconcile/endpoints_discovery/merge_request_manager.py,sha256=wUMsumxv8RnWaRattax4HfoRlhtVzmgro3GiJJ1C4Vc,6392
194
194
  reconcile/external_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
195
- reconcile/external_resources/aws.py,sha256=EN2Vz6IRp6TNWX5vwGAGESrD09_8sTFzKgZjdDR6cmg,7072
196
- reconcile/external_resources/factories.py,sha256=KrJDh52_8PeCEVjwfeVr1jwAJDdhMXRQ_XcBETfnKY4,4988
195
+ reconcile/external_resources/aws.py,sha256=NSaOeHqFEcMaMxNjJwuQZosolgsJ8XRVvwkEEBj9vrw,7730
196
+ reconcile/external_resources/factories.py,sha256=TyJMaijDfPIFYks9i6dhKN7nSR1BoCkoBs1iPExKpcE,5493
197
197
  reconcile/external_resources/integration.py,sha256=gBVO5dE8JyZ3xYcYik-MTIp_18oU7_hpYc_oztyfElQ,6753
198
198
  reconcile/external_resources/integration_secrets_sync.py,sha256=dX09O3r6KURziUYYfiki10orNjOGVma-XojhVqd0ww4,1667
199
- reconcile/external_resources/manager.py,sha256=M_duB1JjQ3xRghvErbmAAbb3mWCo4bFf_2LHqAJVK7E,14991
199
+ reconcile/external_resources/manager.py,sha256=HATI3oWDyxIt7k_SynXqk4CYQzCJHGPmig1XEJoNOA0,15832
200
200
  reconcile/external_resources/meta.py,sha256=noaytFzmShpzLA_ebGh7wuP45mOfHIOnnoUxivjDa1I,672
201
201
  reconcile/external_resources/metrics.py,sha256=8MZgNtNZzIRSYTX97KEUIUTETZBhitULzWxbShGyMO8,3193
202
- reconcile/external_resources/model.py,sha256=HVbt2dUJSoIj4MTNlAJeweKp4L0Af0XaNZXoCER7nVw,10571
202
+ reconcile/external_resources/model.py,sha256=YJylbAhetN9szpLUFd9jFqxCRMvSWXVxSC9OMQNV-wg,11316
203
203
  reconcile/external_resources/reconciler.py,sha256=K9QvbQCIOCuOHnPIxQE_P_jFtrkF3dGo8d_cCCh08Ys,8973
204
- reconcile/external_resources/secrets_sync.py,sha256=fxzrNreggWJvASonIPUr3CB5M637M1ljZLZr6dct9xU,16329
204
+ reconcile/external_resources/secrets_sync.py,sha256=H8JfI3JW1XEau1jqv15AhYg49mCZeHwZmqMzMv_6tFc,16344
205
205
  reconcile/external_resources/state.py,sha256=7DBzVhIvYqeZWrWapmU_bXXftTQa_m-EOwJFVlIFnDw,9583
206
206
  reconcile/glitchtip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
207
207
  reconcile/glitchtip/integration.py,sha256=XtewM9nfTPLnPSpYebP50GrveYOnhTvKNq3seSvL6u8,8343
@@ -882,8 +882,8 @@ tools/test/test_qontract_cli.py,sha256=iuzKbQ6ahinvjoQmQLBrG4shey0z-1rB6qCgS8T6d
882
882
  tools/test/test_saas_promotion_state.py,sha256=dy4kkSSAQ7bC0Xp2CociETGN-2aABEfL6FU5D9Jl00Y,6056
883
883
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
884
884
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
885
- qontract_reconcile-0.10.1rc1198.dist-info/METADATA,sha256=Cm6U5Z2jCGWiZmnTJGDeU-yaw7dCIkonvABx3Wf_6KA,2213
886
- qontract_reconcile-0.10.1rc1198.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
887
- qontract_reconcile-0.10.1rc1198.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
888
- qontract_reconcile-0.10.1rc1198.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
889
- qontract_reconcile-0.10.1rc1198.dist-info/RECORD,,
885
+ qontract_reconcile-0.10.1rc1200.dist-info/METADATA,sha256=TsJMZagQbxyhlVsDImDJTdadVxpB3agV14BYdeZkSo4,2213
886
+ qontract_reconcile-0.10.1rc1200.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
887
+ qontract_reconcile-0.10.1rc1200.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
888
+ qontract_reconcile-0.10.1rc1200.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
889
+ qontract_reconcile-0.10.1rc1200.dist-info/RECORD,,
@@ -3,6 +3,7 @@ from typing import Any
3
3
 
4
4
  from reconcile.external_resources.model import (
5
5
  ExternalResource,
6
+ ExternalResourceKey,
6
7
  ExternalResourcesInventory,
7
8
  )
8
9
  from reconcile.utils.external_resource_spec import (
@@ -25,6 +26,13 @@ class AWSResourceFactory(ABC):
25
26
  @abstractmethod
26
27
  def validate(self, resource: ExternalResource) -> None: ...
27
28
 
29
+ def find_linked_resources(
30
+ self, spec: ExternalResourceSpec
31
+ ) -> set[ExternalResourceKey]:
32
+ """Method to find dependant resources. Resources in this list
33
+ will be reconciled every time the parent resource finishes its reconciliation."""
34
+ return set()
35
+
28
36
 
29
37
  class AWSDefaultResourceFactory(AWSResourceFactory):
30
38
  def resolve(self, spec: ExternalResourceSpec) -> dict[str, Any]:
@@ -136,6 +144,17 @@ class AWSRdsFactory(AWSDefaultResourceFactory):
136
144
 
137
145
  def validate(self, resource: ExternalResource) -> None: ...
138
146
 
147
+ def find_linked_resources(
148
+ self, spec: ExternalResourceSpec
149
+ ) -> set[ExternalResourceKey]:
150
+ return {
151
+ k
152
+ for k, s in self.er_inventory.items()
153
+ if s.provision_provider == "aws"
154
+ and s.provider == "rds"
155
+ and s.resource["replica_source"] == spec.identifier
156
+ }
157
+
139
158
 
140
159
  class AWSMskFactory(AWSDefaultResourceFactory):
141
160
  def _get_source_db_spec(
@@ -60,6 +60,13 @@ class ExternalResourceFactory(ABC):
60
60
  def validate_external_resource(self, resource: ExternalResource) -> None:
61
61
  pass
62
62
 
63
+ def find_linked_resources(
64
+ self, spec: ExternalResourceSpec
65
+ ) -> set[ExternalResourceKey]:
66
+ """Method to find dependant resources. Resources in this list
67
+ will be reconciled every time the parent resource finishes its reconciliation."""
68
+ return set()
69
+
63
70
 
64
71
  class ModuleProvisionDataFactory(ABC):
65
72
  @abstractmethod
@@ -148,3 +155,9 @@ class AWSExternalResourceFactory(ExternalResourceFactory):
148
155
  def validate_external_resource(self, resource: ExternalResource) -> None:
149
156
  f = self.resource_factories.get_factory(resource.provision.provider)
150
157
  f.validate(resource)
158
+
159
+ def find_linked_resources(
160
+ self, spec: ExternalResourceSpec
161
+ ) -> set[ExternalResourceKey]:
162
+ f = self.resource_factories.get_factory(spec.provider)
163
+ return f.find_linked_resources(spec)
@@ -18,6 +18,7 @@ from reconcile.external_resources.model import (
18
18
  ExternalResource,
19
19
  ExternalResourceKey,
20
20
  ExternalResourceModuleConfiguration,
21
+ ExternalResourceOrphanedResourcesError,
21
22
  ExternalResourcesInventory,
22
23
  ExternalResourceValidationError,
23
24
  ModuleInventory,
@@ -161,7 +162,7 @@ class ExternalResourcesManager:
161
162
  continue
162
163
  module = self.module_inventory.get_from_spec(spec)
163
164
  try:
164
- resource = self._build_external_resource(spec, self.er_inventory)
165
+ resource = self._build_external_resource(spec)
165
166
  except ExternalResourceValidationError as e:
166
167
  self.errors[key] = e
167
168
  continue
@@ -169,11 +170,12 @@ class ExternalResourcesManager:
169
170
  reconciliation = Reconciliation(
170
171
  key=key,
171
172
  resource_hash=resource.hash(),
172
- input=self._serialize_resource_input(resource),
173
+ input=resource.json(),
173
174
  action=Action.APPLY,
174
175
  module_configuration=ExternalResourceModuleConfiguration.resolve_configuration(
175
176
  module, spec, self.settings
176
177
  ),
178
+ linked_resources=self._find_linked_resources(spec),
177
179
  )
178
180
  r.add(reconciliation)
179
181
  return r
@@ -197,6 +199,13 @@ class ExternalResourcesManager:
197
199
  to_reconcile.add(r)
198
200
  return to_reconcile
199
201
 
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
+
200
209
  def _get_reconciliation_status(
201
210
  self,
202
211
  r: Reconciliation,
@@ -273,6 +282,13 @@ class ExternalResourcesManager:
273
282
  state.update_resource_status(reconciliation_status)
274
283
  self.state_mgr.set_external_resource_state(state)
275
284
 
285
+ if r.linked_resources:
286
+ for lr in r.linked_resources:
287
+ lrs = self.state_mgr.get_external_resource_state(lr)
288
+ if not lrs.resource_status.is_in_progress:
289
+ lrs.resource_status = ResourceStatus.RECONCILIATION_REQUESTED
290
+ self.state_mgr.set_external_resource_state(lrs)
291
+
276
292
  def _set_resource_reconciliation_in_progress(
277
293
  self, r: Reconciliation, state: ExternalResourceState
278
294
  ) -> None:
@@ -317,16 +333,17 @@ class ExternalResourcesManager:
317
333
  )
318
334
  self.state_mgr.update_resource_status(key, ResourceStatus.CREATED)
319
335
 
320
- def _build_external_resource(
321
- self, spec: ExternalResourceSpec, er_inventory: ExternalResourcesInventory
322
- ) -> ExternalResource:
336
+ def _build_external_resource(self, spec: ExternalResourceSpec) -> ExternalResource:
323
337
  f = self.factories.get_factory(spec.provision_provider)
324
338
  resource = f.create_external_resource(spec)
325
339
  f.validate_external_resource(resource)
326
340
  return resource
327
341
 
328
- def _serialize_resource_input(self, resource: ExternalResource) -> str:
329
- return resource.json()
342
+ def _find_linked_resources(
343
+ self, spec: ExternalResourceSpec
344
+ ) -> set[ExternalResourceKey]:
345
+ f = self.factories.get_factory(spec.provision_provider)
346
+ return f.find_linked_resources(spec)
330
347
 
331
348
  def handle_resources(self) -> None:
332
349
  desired_r = self._get_desired_objects_reconciliations()
@@ -355,6 +372,7 @@ class ExternalResourcesManager:
355
372
  self._sync_secrets(to_sync_keys=to_sync_keys | pending_sync_keys)
356
373
 
357
374
  def handle_dry_run_resources(self) -> None:
375
+ self._check_orphaned_objects()
358
376
  desired_r = self._get_desired_objects_reconciliations()
359
377
  deleted_r = self._get_deleted_objects_reconciliations()
360
378
  reconciliations = desired_r.union(deleted_r)
@@ -3,11 +3,7 @@ import json
3
3
  from abc import (
4
4
  ABC,
5
5
  )
6
- from collections.abc import (
7
- Iterable,
8
- Iterator,
9
- MutableMapping,
10
- )
6
+ from collections.abc import ItemsView, Iterable, Iterator, MutableMapping
11
7
  from enum import StrEnum
12
8
  from typing import Any
13
9
 
@@ -38,6 +34,17 @@ from reconcile.utils.external_resource_spec import (
38
34
  )
39
35
 
40
36
 
37
+ class ExternalResourceOrphanedResourcesError(Exception):
38
+ def __init__(self, orphans: Iterable["ExternalResourceKey"]) -> None:
39
+ msg = [
40
+ "There are orphaned resources in the configuration. ",
41
+ "To delete ERv2 managed external resources, set the 'delete: true' attribute.\n",
42
+ "Orphans:\n",
43
+ "\n".join(map(str, orphans)),
44
+ ]
45
+ super().__init__("".join(msg))
46
+
47
+
41
48
  class ExternalResourceValidationError(Exception):
42
49
  errors: list[str] = []
43
50
 
@@ -82,17 +89,17 @@ class ExternalResourcesInventory(MutableMapping):
82
89
  def __init__(self, namespaces: Iterable[NamespaceV1]) -> None:
83
90
  self._inventory: dict[ExternalResourceKey, ExternalResourceSpec] = {}
84
91
 
85
- desired_providers = [
86
- (p, ns)
92
+ resource_providers = [
93
+ (rp, ns)
87
94
  for ns in namespaces
88
- for p in ns.external_resources or []
89
- if isinstance(p, SUPPORTED_RESOURCE_PROVIDERS) and p.resources
95
+ for rp in ns.external_resources or []
96
+ if isinstance(rp, SUPPORTED_RESOURCE_PROVIDERS) and rp.resources
90
97
  ]
91
98
 
92
99
  desired_specs = [
93
- self._build_external_resource_spec(ns, p, r)
94
- for (p, ns) in desired_providers
95
- for r in p.resources
100
+ self._build_external_resource_spec(ns, rp, r)
101
+ for (rp, ns) in resource_providers
102
+ for r in rp.resources
96
103
  if isinstance(r, SUPPORTED_RESOURCE_TYPES) and r.managed_by_erv2
97
104
  ]
98
105
 
@@ -136,6 +143,9 @@ class ExternalResourcesInventory(MutableMapping):
136
143
  def __len__(self) -> int:
137
144
  return len(self._inventory)
138
145
 
146
+ def items(self) -> ItemsView[ExternalResourceKey, ExternalResourceSpec]:
147
+ return self._inventory.items()
148
+
139
149
  def get_inventory_spec(
140
150
  self, provision_provider: str, provisioner: str, provider: str, identifier: str
141
151
  ) -> ExternalResourceSpec:
@@ -281,6 +291,9 @@ class Reconciliation(BaseModel, frozen=True):
281
291
  module_configuration: ExternalResourceModuleConfiguration = (
282
292
  ExternalResourceModuleConfiguration()
283
293
  )
294
+ # linked_resources store dependants resources. They will get reconciled
295
+ # every time the parent resource reconciliation finishes.
296
+ linked_resources: frozenset[ExternalResourceKey] | None
284
297
 
285
298
 
286
299
  class ReconcileAction(StrEnum):
@@ -20,7 +20,9 @@ from reconcile.external_resources.meta import (
20
20
  SECRET_UPDATED_AT,
21
21
  SECRET_UPDATED_AT_TIMEFORMAT,
22
22
  )
23
- from reconcile.external_resources.model import ExternalResourceKey
23
+ from reconcile.external_resources.model import (
24
+ ExternalResourceKey,
25
+ )
24
26
  from reconcile.openshift_base import ApplyOptions, apply_action
25
27
  from reconcile.typed_queries.clusters_minimal import get_clusters_minimal
26
28
  from reconcile.utils.differ import diff_mappings
@@ -207,7 +209,7 @@ class SecretsReconciler:
207
209
  If current is newer; don't apply.
208
210
  If other changes; apply and Recycle Pods
209
211
  Desired can not be newer than current.
210
- External reosurce to Cluster (last reconciliation):
212
+ External resource to Cluster (last reconciliation):
211
213
  If updated_at annotation is the only change; Don't update
212
214
  If other changes; Update Secret and Recycle Pods
213
215
  Current can not be newer then Desired
@@ -233,7 +235,7 @@ class SecretsReconciler:
233
235
 
234
236
  if self.ri.has_error_registered():
235
237
  # Return all specs as error if there are errors.
236
- # There is no a clear way to kwno which specs failed.
238
+ # There is not a clear way to know which specs have failed.
237
239
  return list(specs)
238
240
  else:
239
241
  return []