qontract-reconcile 0.10.1rc1188__py3-none-any.whl → 0.10.1rc1189__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.1rc1188
3
+ Version: 0.10.1rc1189
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
@@ -196,13 +196,13 @@ reconcile/external_resources/aws.py,sha256=EN2Vz6IRp6TNWX5vwGAGESrD09_8sTFzKgZjd
196
196
  reconcile/external_resources/factories.py,sha256=KrJDh52_8PeCEVjwfeVr1jwAJDdhMXRQ_XcBETfnKY4,4988
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=fXUm09w-9FRWYfJAwtV4_td1UHNLzdoGhWYpw5VR9jg,17757
199
+ reconcile/external_resources/manager.py,sha256=M_duB1JjQ3xRghvErbmAAbb3mWCo4bFf_2LHqAJVK7E,14991
200
200
  reconcile/external_resources/meta.py,sha256=noaytFzmShpzLA_ebGh7wuP45mOfHIOnnoUxivjDa1I,672
201
- reconcile/external_resources/metrics.py,sha256=nMbyonGZEJDD1lYzpQY2eR9TNwvxYC4ZCcpi6wrExcM,1037
202
- reconcile/external_resources/model.py,sha256=H3elpiqehg_jACy28fGV5_77n8gKclVO77-7cfbaMNA,9178
201
+ reconcile/external_resources/metrics.py,sha256=8MZgNtNZzIRSYTX97KEUIUTETZBhitULzWxbShGyMO8,3193
202
+ reconcile/external_resources/model.py,sha256=HVbt2dUJSoIj4MTNlAJeweKp4L0Af0XaNZXoCER7nVw,10571
203
203
  reconcile/external_resources/reconciler.py,sha256=K9QvbQCIOCuOHnPIxQE_P_jFtrkF3dGo8d_cCCh08Ys,8973
204
204
  reconcile/external_resources/secrets_sync.py,sha256=fxzrNreggWJvASonIPUr3CB5M637M1ljZLZr6dct9xU,16329
205
- reconcile/external_resources/state.py,sha256=z086bnIUTOkzFmQvS9rSAhFsM3Aw_9PLKHBACJ-0tQc,9690
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
208
208
  reconcile/glitchtip/reconciler.py,sha256=nUvDv7qG1ly0cA16MmlL6NV71yl1mJYLT2mui7lmi0Y,12402
@@ -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.1rc1188.dist-info/METADATA,sha256=GT_x8-c5VlD7dIrPKFWAaFfVJawtfVTFoiH22w4G4Cw,2213
886
- qontract_reconcile-0.10.1rc1188.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
887
- qontract_reconcile-0.10.1rc1188.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
888
- qontract_reconcile-0.10.1rc1188.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
889
- qontract_reconcile-0.10.1rc1188.dist-info/RECORD,,
885
+ qontract_reconcile-0.10.1rc1189.dist-info/METADATA,sha256=NVuBKGEqAXmlVCYwyO-M5Cnu8hcOq0-pJJ9g7a9JCro,2213
886
+ qontract_reconcile-0.10.1rc1189.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
887
+ qontract_reconcile-0.10.1rc1189.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
888
+ qontract_reconcile-0.10.1rc1189.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
889
+ qontract_reconcile-0.10.1rc1189.dist-info/RECORD,,
@@ -1,9 +1,7 @@
1
1
  import logging
2
2
  from collections.abc import Iterable
3
3
  from datetime import UTC, datetime
4
- from enum import StrEnum
5
4
 
6
- from pydantic import BaseModel
7
5
  from sretoolbox.utils import threaded
8
6
 
9
7
  from reconcile.external_resources.factories import (
@@ -14,11 +12,7 @@ from reconcile.external_resources.factories import (
14
12
  TerraformModuleProvisionDataFactory,
15
13
  setup_aws_resource_factories,
16
14
  )
17
- from reconcile.external_resources.metrics import (
18
- ExternalResourcesReconcileErrorsCounter,
19
- ExternalResourcesReconcileTimeGauge,
20
- ExternalResourcesResourceStatus,
21
- )
15
+ from reconcile.external_resources.metrics import publish_metrics
22
16
  from reconcile.external_resources.model import (
23
17
  Action,
24
18
  ExternalResource,
@@ -27,11 +21,12 @@ from reconcile.external_resources.model import (
27
21
  ExternalResourcesInventory,
28
22
  ExternalResourceValidationError,
29
23
  ModuleInventory,
24
+ ReconcileAction,
30
25
  Reconciliation,
26
+ ReconciliationStatus,
31
27
  )
32
28
  from reconcile.external_resources.reconciler import (
33
29
  ExternalResourcesReconciler,
34
- ReconciliationK8sJob,
35
30
  )
36
31
  from reconcile.external_resources.secrets_sync import InClusterSecretsReconciler
37
32
  from reconcile.external_resources.state import (
@@ -43,7 +38,6 @@ from reconcile.external_resources.state import (
43
38
  from reconcile.gql_definitions.external_resources.external_resources_settings import (
44
39
  ExternalResourcesSettingsV1,
45
40
  )
46
- from reconcile.utils import metrics
47
41
  from reconcile.utils.external_resource_spec import (
48
42
  ExternalResourceSpec,
49
43
  )
@@ -78,67 +72,6 @@ def setup_factories(
78
72
  return of
79
73
 
80
74
 
81
- class ReconcileAction(StrEnum):
82
- NOOP = "NOOP"
83
- APPLY_NOT_EXISTS = "Resource does not exist"
84
- APPLY_ERROR = "Resource status in ERROR state"
85
- APPLY_SPEC_CHANGED = "Resource spec has changed"
86
- APPLY_DRIFT_DETECTION = "Resource drift detection run"
87
- APPLY_USER_REQUESTED = "Resource reconciliation requested"
88
- DESTROY_CREATED = "Resource no longer exists in the configuration"
89
- DESTROY_ERROR = "Resource status in ERROR state"
90
-
91
-
92
- class ReconciliationStatus(BaseModel):
93
- reconcile_time: int = 0
94
- resource_status: ResourceStatus
95
-
96
- def publish_metrics(self, r: Reconciliation, spec: ExternalResourceSpec) -> None:
97
- job_name = ReconciliationK8sJob(reconciliation=r).name()
98
-
99
- # Use transactional_metrics to remove old status, we just want to expose the latest status
100
- with metrics.transactional_metrics(scope=job_name) as metrics_container:
101
- metrics_container.set_gauge(
102
- ExternalResourcesResourceStatus(
103
- app=spec.namespace["app"]["name"],
104
- environment=spec.namespace["environment"]["name"],
105
- provision_provider=r.key.provision_provider,
106
- provisioner_name=r.key.provisioner_name,
107
- provider=r.key.provider,
108
- identifier=r.key.identifier,
109
- job_name=job_name,
110
- status=self.resource_status,
111
- ),
112
- 1,
113
- )
114
-
115
- metrics.set_gauge(
116
- ExternalResourcesReconcileTimeGauge(
117
- app=spec.namespace["app"]["name"],
118
- environment=spec.namespace["environment"]["name"],
119
- provision_provider=r.key.provision_provider,
120
- provisioner_name=r.key.provisioner_name,
121
- provider=r.key.provider,
122
- identifier=r.key.identifier,
123
- job_name=job_name,
124
- ),
125
- self.reconcile_time,
126
- )
127
-
128
- if self.resource_status.has_errors:
129
- metrics.inc_counter(
130
- ExternalResourcesReconcileErrorsCounter(
131
- app=spec.namespace["app"]["name"],
132
- environment=spec.namespace["environment"]["name"],
133
- provision_provider=r.key.provision_provider,
134
- provisioner_name=r.key.provisioner_name,
135
- provider=r.key.provider,
136
- identifier=r.key.identifier,
137
- job_name=job_name,
138
- )
139
- )
140
-
141
-
142
75
  class ExternalResourcesManager:
143
76
  def __init__(
144
77
  self,
@@ -276,9 +209,12 @@ class ExternalResourcesManager:
276
209
  :return: ReconciliationStatus
277
210
  """
278
211
 
279
- status = ReconciliationStatus(resource_status=state.resource_status)
212
+ reconciliation_status = ReconciliationStatus(
213
+ resource_status=state.resource_status
214
+ )
215
+
280
216
  if not state.resource_status.is_in_progress:
281
- return status
217
+ return reconciliation_status
282
218
 
283
219
  logging.info(
284
220
  "Reconciliation In progress. Action: %s, Key:%s",
@@ -286,10 +222,6 @@ class ExternalResourcesManager:
286
222
  state.reconciliation.key,
287
223
  )
288
224
 
289
- # Need to check the reconciliation set in the state, not the desired one
290
- # as the reconciliation object might be from a previous desired state
291
- status.resource_status = state.resource_status
292
-
293
225
  match self.reconciler.get_resource_reconcile_status(state.reconciliation):
294
226
  case ReconcileStatus.SUCCESS:
295
227
  logging.info(
@@ -298,10 +230,12 @@ class ExternalResourcesManager:
298
230
  r.key,
299
231
  )
300
232
  if r.action == Action.APPLY:
301
- status.resource_status = ResourceStatus.PENDING_SECRET_SYNC
233
+ reconciliation_status.resource_status = (
234
+ ResourceStatus.PENDING_SECRET_SYNC
235
+ )
302
236
  elif r.action == Action.DESTROY:
303
- status.resource_status = ResourceStatus.DELETED
304
- status.reconcile_time = (
237
+ reconciliation_status.resource_status = ResourceStatus.DELETED
238
+ reconciliation_status.reconcile_time = (
305
239
  self.reconciler.get_resource_reconcile_duration(r) or 0
306
240
  )
307
241
  case ReconcileStatus.ERROR:
@@ -310,16 +244,18 @@ class ExternalResourcesManager:
310
244
  r.action.value,
311
245
  r.key,
312
246
  )
313
- status.resource_status = ResourceStatus.ERROR
247
+ reconciliation_status.resource_status = ResourceStatus.ERROR
314
248
  case ReconcileStatus.NOT_EXISTS:
315
249
  logging.info(
316
250
  "Reconciliation should exist but it doesn't. Marking as ERROR to retrigger: Action:%s, Key:%s",
317
251
  r.action.value,
318
252
  r.key,
319
253
  )
320
- status.resource_status = ResourceStatus.ERROR
254
+ reconciliation_status.resource_status = ResourceStatus.ERROR
255
+ case ReconcileStatus.IN_PROGRESS:
256
+ logging.debug("Reconciliation still in progress ...")
321
257
 
322
- return status
258
+ return reconciliation_status
323
259
 
324
260
  def _update_resource_state(
325
261
  self,
@@ -327,19 +263,14 @@ class ExternalResourcesManager:
327
263
  state: ExternalResourceState,
328
264
  reconciliation_status: ReconciliationStatus,
329
265
  ) -> None:
330
- if (
331
- state.resource_status.is_in_progress
332
- and reconciliation_status.resource_status.is_in_progress
333
- ):
334
- logging.debug(
335
- "Reconciliation is still in progress. There is no need to update the state"
336
- )
266
+ if not state.reconciliation_needs_state_update(reconciliation_status):
267
+ logging.debug("Reconciliation does not need a state update.")
337
268
  return
338
- state.ts = datetime.now(UTC)
269
+
339
270
  if reconciliation_status.resource_status == ResourceStatus.DELETED:
340
271
  self.state_mgr.del_external_resource_state(r.key)
341
272
  else:
342
- state.resource_status = reconciliation_status.resource_status
273
+ state.update_resource_status(reconciliation_status)
343
274
  self.state_mgr.set_external_resource_state(state)
344
275
 
345
276
  def _set_resource_reconciliation_in_progress(
@@ -414,7 +345,7 @@ class ExternalResourcesManager:
414
345
  self._set_resource_reconciliation_in_progress(r, state)
415
346
 
416
347
  if spec := self.er_inventory.get(r.key):
417
- reconciliation_status.publish_metrics(r, spec)
348
+ publish_metrics(r, spec, reconciliation_status)
418
349
 
419
350
  pending_sync_keys = self.state_mgr.get_keys_by_status(
420
351
  ResourceStatus.PENDING_SECRET_SYNC
@@ -1,6 +1,10 @@
1
1
  from pydantic import BaseModel
2
2
 
3
3
  from reconcile.external_resources.meta import QONTRACT_INTEGRATION
4
+ from reconcile.external_resources.model import Reconciliation, ReconciliationStatus
5
+ from reconcile.external_resources.reconciler import ReconciliationK8sJob
6
+ from reconcile.utils import metrics
7
+ from reconcile.utils.external_resources import ExternalResourceSpec
4
8
  from reconcile.utils.metrics import (
5
9
  CounterMetric,
6
10
  GaugeMetric,
@@ -39,3 +43,53 @@ class ExternalResourcesResourceStatus(ExternalResourcesBaseMetric, GaugeMetric):
39
43
  @classmethod
40
44
  def name(cls) -> str:
41
45
  return "external_resources_resource_status"
46
+
47
+
48
+ def publish_metrics(
49
+ r: Reconciliation,
50
+ spec: ExternalResourceSpec,
51
+ reconciliation_status: ReconciliationStatus,
52
+ ) -> None:
53
+ job_name = ReconciliationK8sJob(reconciliation=r).name()
54
+
55
+ # Use transactional_metrics to remove old status, we just want to expose the latest status
56
+ with metrics.transactional_metrics(scope=job_name) as metrics_container:
57
+ metrics_container.set_gauge(
58
+ ExternalResourcesResourceStatus(
59
+ app=spec.namespace["app"]["name"],
60
+ environment=spec.namespace["environment"]["name"],
61
+ provision_provider=r.key.provision_provider,
62
+ provisioner_name=r.key.provisioner_name,
63
+ provider=r.key.provider,
64
+ identifier=r.key.identifier,
65
+ job_name=job_name,
66
+ status=reconciliation_status.resource_status,
67
+ ),
68
+ 1,
69
+ )
70
+
71
+ metrics.set_gauge(
72
+ ExternalResourcesReconcileTimeGauge(
73
+ app=spec.namespace["app"]["name"],
74
+ environment=spec.namespace["environment"]["name"],
75
+ provision_provider=r.key.provision_provider,
76
+ provisioner_name=r.key.provisioner_name,
77
+ provider=r.key.provider,
78
+ identifier=r.key.identifier,
79
+ job_name=job_name,
80
+ ),
81
+ reconciliation_status.reconcile_time,
82
+ )
83
+
84
+ if reconciliation_status.resource_status.has_errors:
85
+ metrics.inc_counter(
86
+ ExternalResourcesReconcileErrorsCounter(
87
+ app=spec.namespace["app"]["name"],
88
+ environment=spec.namespace["environment"]["name"],
89
+ provision_provider=r.key.provision_provider,
90
+ provisioner_name=r.key.provisioner_name,
91
+ provider=r.key.provider,
92
+ identifier=r.key.identifier,
93
+ job_name=job_name,
94
+ )
95
+ )
@@ -245,6 +245,34 @@ class ExternalResourceModuleConfiguration(BaseModel, frozen=True):
245
245
  )
246
246
 
247
247
 
248
+ class ResourceStatus(StrEnum):
249
+ CREATED: str = "CREATED"
250
+ DELETED: str = "DELETED"
251
+ ABANDONED: str = "ABANDONED"
252
+ NOT_EXISTS: str = "NOT_EXISTS"
253
+ IN_PROGRESS: str = "IN_PROGRESS"
254
+ DELETE_IN_PROGRESS: str = "DELETE_IN_PROGRESS"
255
+ ERROR: str = "ERROR"
256
+ PENDING_SECRET_SYNC: str = "PENDING_SECRET_SYNC"
257
+ RECONCILIATION_REQUESTED: str = "RECONCILIATION_REQUESTED"
258
+
259
+ @property
260
+ def does_not_exist(self) -> bool:
261
+ return self == ResourceStatus.NOT_EXISTS
262
+
263
+ @property
264
+ def is_in_progress(self) -> bool:
265
+ return self in {ResourceStatus.IN_PROGRESS, ResourceStatus.DELETE_IN_PROGRESS}
266
+
267
+ @property
268
+ def needs_secret_sync(self) -> bool:
269
+ return self == ResourceStatus.PENDING_SECRET_SYNC
270
+
271
+ @property
272
+ def has_errors(self) -> bool:
273
+ return self == ResourceStatus.ERROR
274
+
275
+
248
276
  class Reconciliation(BaseModel, frozen=True):
249
277
  key: ExternalResourceKey
250
278
  resource_hash: str = ""
@@ -255,6 +283,22 @@ class Reconciliation(BaseModel, frozen=True):
255
283
  )
256
284
 
257
285
 
286
+ class ReconcileAction(StrEnum):
287
+ NOOP = "NOOP"
288
+ APPLY_NOT_EXISTS = "Resource does not exist"
289
+ APPLY_ERROR = "Resource status in ERROR state"
290
+ APPLY_SPEC_CHANGED = "Resource spec has changed"
291
+ APPLY_DRIFT_DETECTION = "Resource drift detection run"
292
+ APPLY_USER_REQUESTED = "Resource reconciliation requested"
293
+ DESTROY_CREATED = "Resource no longer exists in the configuration"
294
+ DESTROY_ERROR = "Resource status in ERROR state"
295
+
296
+
297
+ class ReconciliationStatus(BaseModel):
298
+ reconcile_time: int = 0
299
+ resource_status: ResourceStatus
300
+
301
+
258
302
  class ModuleProvisionData(ABC, BaseModel):
259
303
  pass
260
304
 
@@ -10,6 +10,8 @@ from reconcile.external_resources.model import (
10
10
  ExternalResourceKey,
11
11
  ExternalResourceModuleConfiguration,
12
12
  Reconciliation,
13
+ ReconciliationStatus,
14
+ ResourceStatus,
13
15
  )
14
16
  from reconcile.utils.aws_api_typed.api import AWSApi
15
17
 
@@ -27,36 +29,26 @@ class ReconcileStatus(StrEnum):
27
29
  NOT_EXISTS: str = "NOT_EXISTS"
28
30
 
29
31
 
30
- class ResourceStatus(StrEnum):
31
- CREATED: str = "CREATED"
32
- DELETED: str = "DELETED"
33
- ABANDONED: str = "ABANDONED"
34
- NOT_EXISTS: str = "NOT_EXISTS"
35
- IN_PROGRESS: str = "IN_PROGRESS"
36
- DELETE_IN_PROGRESS: str = "DELETE_IN_PROGRESS"
37
- ERROR: str = "ERROR"
38
- PENDING_SECRET_SYNC: str = "PENDING_SECRET_SYNC"
39
- RECONCILIATION_REQUESTED: str = "RECONCILIATION_REQUESTED"
40
-
41
- @property
42
- def is_in_progress(self) -> bool:
43
- return self in {ResourceStatus.IN_PROGRESS, ResourceStatus.DELETE_IN_PROGRESS}
44
-
45
- @property
46
- def needs_secret_sync(self) -> bool:
47
- return self == ResourceStatus.PENDING_SECRET_SYNC
48
-
49
- @property
50
- def has_errors(self) -> bool:
51
- return self == ResourceStatus.ERROR
52
-
53
-
54
32
  class ExternalResourceState(BaseModel):
55
33
  key: ExternalResourceKey
56
34
  ts: datetime
57
35
  resource_status: ResourceStatus
58
36
  reconciliation: Reconciliation
59
37
 
38
+ def update_resource_status(
39
+ self, reconciliation_status: ReconciliationStatus
40
+ ) -> None:
41
+ if self.reconciliation_needs_state_update(reconciliation_status):
42
+ self.ts = datetime.now()
43
+ self.resource_status = reconciliation_status.resource_status
44
+
45
+ def reconciliation_needs_state_update(
46
+ self, reconciliation_status: ReconciliationStatus
47
+ ) -> bool:
48
+ return (
49
+ self.resource_status.is_in_progress or self.resource_status.does_not_exist
50
+ ) and not reconciliation_status.resource_status.is_in_progress
51
+
60
52
 
61
53
  class DynamoDBStateAdapter:
62
54
  # Table PK