qontract-reconcile 0.10.2.dev85__py3-none-any.whl → 0.10.2.dev87__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.10.2.dev85.dist-info → qontract_reconcile-0.10.2.dev87.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev85.dist-info → qontract_reconcile-0.10.2.dev87.dist-info}/RECORD +9 -9
- reconcile/external_resources/manager.py +48 -20
- reconcile/external_resources/model.py +17 -12
- reconcile/fleet_labeler/dependencies.py +3 -0
- reconcile/fleet_labeler/integration.py +27 -4
- reconcile/fleet_labeler/vcs.py +16 -3
- {qontract_reconcile-0.10.2.dev85.dist-info → qontract_reconcile-0.10.2.dev87.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev85.dist-info → qontract_reconcile-0.10.2.dev87.dist-info}/entry_points.txt +0 -0
{qontract_reconcile-0.10.2.dev85.dist-info → qontract_reconcile-0.10.2.dev87.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.2.
|
3
|
+
Version: 0.10.2.dev87
|
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
|
{qontract_reconcile-0.10.2.dev85.dist-info → qontract_reconcile-0.10.2.dev87.dist-info}/RECORD
RENAMED
@@ -199,22 +199,22 @@ reconcile/external_resources/aws.py,sha256=wzN3GHxyqVa4Lqqg5HdogqNW2RM532t0ZiKaQ
|
|
199
199
|
reconcile/external_resources/factories.py,sha256=C0QHT0soEv6z99-ELAAE19S5MaMHhV0t1fSiQn0Coc4,5970
|
200
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=
|
202
|
+
reconcile/external_resources/manager.py,sha256=mvFfcXPzvNqDWDgKTK8eiSe6C_FUvBtben3bSqrqSoc,18246
|
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=
|
205
|
+
reconcile/external_resources/model.py,sha256=HrSkmAjOqw0aT3CsZKXKMLyocM__8XO5qHb-dXcoagw,13747
|
206
206
|
reconcile/external_resources/reconciler.py,sha256=-0trp1K-iUgOQn3mm1ZUSmfaReRrUT0eHzPkUhNPolQ,9583
|
207
207
|
reconcile/external_resources/secrets_sync.py,sha256=ZDxzGZ6wC4zxLhA7-L39xDRH6rzUM285gytuzmRQdlw,16208
|
208
208
|
reconcile/external_resources/state.py,sha256=gF3ACdl7YiUlbQ4uEGrD6i_Txxqr6mT9f8IFlTQ-8dY,13176
|
209
209
|
reconcile/fleet_labeler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
210
|
-
reconcile/fleet_labeler/dependencies.py,sha256=
|
211
|
-
reconcile/fleet_labeler/integration.py,sha256=
|
210
|
+
reconcile/fleet_labeler/dependencies.py,sha256=ZOpmtwgxEPBWU2yHRc6rhPwlvqhwYSsNMJQ_jVq1dLI,2993
|
211
|
+
reconcile/fleet_labeler/integration.py,sha256=jm2wvkxsxqGLsX_vE874crgce_YsDWyggL3Q3olMNJM,8753
|
212
212
|
reconcile/fleet_labeler/merge_request.py,sha256=VA_XSIob4LWYb02dRUr9coXjIa8_0prObI1v5Gqi_mY,1513
|
213
213
|
reconcile/fleet_labeler/meta.py,sha256=DF7O4T9wvQ7-xTWXvuNw1OG_F0SBmRrjFBtVy9wWh9U,146
|
214
214
|
reconcile/fleet_labeler/metrics.py,sha256=wx9BmXLsN67m-aSsf81iB7Ehj5SzUsS2WB75isUReZg,662
|
215
215
|
reconcile/fleet_labeler/ocm.py,sha256=GGsz-bq1g8BJVVMCfI2kSwZCyngbQoZ3i3k8fO608KA,2506
|
216
216
|
reconcile/fleet_labeler/validate.py,sha256=gzc2tt7h9F60h7dcyJfEmsnjnfuux5Jtc_WzrIqr-5k,2541
|
217
|
-
reconcile/fleet_labeler/vcs.py,sha256=
|
217
|
+
reconcile/fleet_labeler/vcs.py,sha256=6UHUQ08AGAHXF7629I6X-T_E1pvx96LxjS66EeOzve4,1108
|
218
218
|
reconcile/glitchtip/README.md,sha256=rfXT6jNP9khJW65jL7I2PgoxvxgcGGuJF8NpbzufEQ4,4335
|
219
219
|
reconcile/glitchtip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
220
220
|
reconcile/glitchtip/integration.py,sha256=vCyg8W4ZUGxjU8tB1Gkre_auSpzo83n05mmO8_-7al0,8263
|
@@ -786,7 +786,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
786
786
|
tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
|
787
787
|
tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
|
788
788
|
tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
|
789
|
-
qontract_reconcile-0.10.2.
|
790
|
-
qontract_reconcile-0.10.2.
|
791
|
-
qontract_reconcile-0.10.2.
|
792
|
-
qontract_reconcile-0.10.2.
|
789
|
+
qontract_reconcile-0.10.2.dev87.dist-info/METADATA,sha256=nVRpZDkDyzcxrgE5yIHcfaAKhAQBbpzHtq_p3vStPr8,24565
|
790
|
+
qontract_reconcile-0.10.2.dev87.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
791
|
+
qontract_reconcile-0.10.2.dev87.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
|
792
|
+
qontract_reconcile-0.10.2.dev87.dist-info/RECORD,,
|
@@ -133,6 +133,40 @@ class ExternalResourcesManager:
|
|
133
133
|
self.thread_pool_size = thread_pool_size
|
134
134
|
self.dry_runs_validator = dry_runs_validator
|
135
135
|
|
136
|
+
def _resource_spec_changed(
|
137
|
+
self, reconciliation: Reconciliation, state: ExternalResourceState
|
138
|
+
) -> bool:
|
139
|
+
return reconciliation.resource_hash != state.reconciliation.resource_hash
|
140
|
+
|
141
|
+
def _resource_drift_detection_ttl_expired(
|
142
|
+
self, reconciliation: Reconciliation, state: ExternalResourceState
|
143
|
+
) -> bool:
|
144
|
+
return (datetime.now(state.ts.tzinfo) - state.ts).total_seconds() > (
|
145
|
+
reconciliation.module_configuration.reconcile_drift_interval_minutes * 60
|
146
|
+
)
|
147
|
+
|
148
|
+
def _reconciliation_module_config_overridden(
|
149
|
+
self, reconciliation: Reconciliation, state: ExternalResourceState
|
150
|
+
) -> bool:
|
151
|
+
return reconciliation.module_configuration.overridden and (
|
152
|
+
reconciliation.module_configuration.image_version
|
153
|
+
!= state.reconciliation.module_configuration.image_version
|
154
|
+
)
|
155
|
+
|
156
|
+
def _reconciliation_needs_dry_run_run(
|
157
|
+
self, reconciliation: Reconciliation, state: ExternalResourceState
|
158
|
+
) -> bool:
|
159
|
+
return (
|
160
|
+
reconciliation.action == Action.APPLY
|
161
|
+
and (
|
162
|
+
self._resource_spec_changed(reconciliation, state)
|
163
|
+
or self._reconciliation_module_config_overridden(reconciliation, state)
|
164
|
+
)
|
165
|
+
) or (
|
166
|
+
reconciliation.action == Action.DESTROY
|
167
|
+
and not state.resource_status.is_in_progress
|
168
|
+
)
|
169
|
+
|
136
170
|
def _get_reconcile_action(
|
137
171
|
self, reconciliation: Reconciliation, state: ExternalResourceState
|
138
172
|
) -> ReconcileAction:
|
@@ -145,17 +179,16 @@ class ExternalResourcesManager:
|
|
145
179
|
case ResourceStatus.ERROR:
|
146
180
|
return ReconcileAction.APPLY_ERROR
|
147
181
|
case ResourceStatus.CREATED | ResourceStatus.PENDING_SECRET_SYNC:
|
148
|
-
if (
|
149
|
-
reconciliation.resource_hash
|
150
|
-
!= state.reconciliation.resource_hash
|
151
|
-
):
|
182
|
+
if self._resource_spec_changed(reconciliation, state):
|
152
183
|
return ReconcileAction.APPLY_SPEC_CHANGED
|
153
|
-
elif (
|
154
|
-
|
155
|
-
> reconciliation.module_configuration.reconcile_drift_interval_minutes
|
156
|
-
* 60
|
184
|
+
elif self._resource_drift_detection_ttl_expired(
|
185
|
+
reconciliation, state
|
157
186
|
):
|
158
187
|
return ReconcileAction.APPLY_DRIFT_DETECTION
|
188
|
+
elif self._reconciliation_module_config_overridden(
|
189
|
+
reconciliation, state
|
190
|
+
):
|
191
|
+
return ReconcileAction.APPLY_MODULE_CONFIG_OVERRIDDEN
|
159
192
|
elif reconciliation.action == Action.DESTROY:
|
160
193
|
match state.resource_status:
|
161
194
|
case ResourceStatus.CREATED:
|
@@ -411,18 +444,13 @@ class ExternalResourcesManager:
|
|
411
444
|
self.dry_runs_validator.validate()
|
412
445
|
desired_r = self._get_desired_objects_reconciliations()
|
413
446
|
deleted_r = self._get_deleted_objects_reconciliations()
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
and state.reconciliation.resource_hash != r.resource_hash
|
422
|
-
) or (
|
423
|
-
r.action == Action.DESTROY and not state.resource_status.is_in_progress
|
424
|
-
):
|
425
|
-
triggered.add(r)
|
447
|
+
triggered = {
|
448
|
+
r
|
449
|
+
for r in desired_r.union(deleted_r)
|
450
|
+
if self._reconciliation_needs_dry_run_run(
|
451
|
+
r, self.state_mgr.get_external_resource_state(key=r.key)
|
452
|
+
)
|
453
|
+
}
|
426
454
|
|
427
455
|
threaded.run(
|
428
456
|
self.reconciler.reconcile_resource,
|
@@ -7,7 +7,7 @@ from collections.abc import ItemsView, Iterable, Iterator, MutableMapping
|
|
7
7
|
from enum import StrEnum
|
8
8
|
from typing import Any
|
9
9
|
|
10
|
-
from pydantic import BaseModel
|
10
|
+
from pydantic import BaseModel, Field
|
11
11
|
|
12
12
|
from reconcile.external_resources.meta import (
|
13
13
|
FLAG_DELETE_RESOURCE,
|
@@ -263,6 +263,7 @@ class ExternalResourceModuleConfiguration(BaseModel, frozen=True):
|
|
263
263
|
outputs_secret_image: str = ""
|
264
264
|
outputs_secret_version: str = ""
|
265
265
|
resources: Resources = Resources()
|
266
|
+
overridden: bool = Field(default=False, exclude=True)
|
266
267
|
|
267
268
|
@property
|
268
269
|
def image_version(self) -> str:
|
@@ -278,17 +279,19 @@ class ExternalResourceModuleConfiguration(BaseModel, frozen=True):
|
|
278
279
|
spec: ExternalResourceSpec,
|
279
280
|
settings: ExternalResourcesSettingsV1,
|
280
281
|
) -> "ExternalResourceModuleConfiguration":
|
281
|
-
module_overrides = spec.metadata.get(
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
282
|
+
module_overrides = spec.metadata.get("module_overrides")
|
283
|
+
overridden = module_overrides is not None
|
284
|
+
|
285
|
+
if module_overrides is None:
|
286
|
+
module_overrides = ExternalResourcesModuleOverrides(
|
287
|
+
module_type=None,
|
288
|
+
image=None,
|
289
|
+
version=None,
|
290
|
+
reconcile_timeout_minutes=None,
|
291
|
+
outputs_secret_image=None,
|
292
|
+
outputs_secret_version=None,
|
293
|
+
resources=None,
|
294
|
+
)
|
292
295
|
|
293
296
|
return ExternalResourceModuleConfiguration(
|
294
297
|
image=module_overrides.image or module.image,
|
@@ -307,6 +310,7 @@ class ExternalResourceModuleConfiguration(BaseModel, frozen=True):
|
|
307
310
|
or module.resources
|
308
311
|
or settings.module_default_resources
|
309
312
|
),
|
313
|
+
overridden=overridden,
|
310
314
|
)
|
311
315
|
|
312
316
|
|
@@ -358,6 +362,7 @@ class ReconcileAction(StrEnum):
|
|
358
362
|
APPLY_SPEC_CHANGED = "Resource spec has changed"
|
359
363
|
APPLY_DRIFT_DETECTION = "Resource drift detection run"
|
360
364
|
APPLY_USER_REQUESTED = "Resource reconciliation requested"
|
365
|
+
APPLY_MODULE_CONFIG_OVERRIDDEN = "Module configuration overridden"
|
361
366
|
DESTROY_CREATED = "Resource no longer exists in the configuration"
|
362
367
|
DESTROY_ERROR = "Resource status in ERROR state"
|
363
368
|
|
@@ -27,10 +27,12 @@ class Dependencies:
|
|
27
27
|
label_specs_by_name: Mapping[str, FleetLabelsSpecV1],
|
28
28
|
ocm_clients_by_label_spec_name: Mapping[str, OCMClient],
|
29
29
|
vcs: VCS,
|
30
|
+
dry_run: bool,
|
30
31
|
):
|
31
32
|
self.label_specs_by_name = label_specs_by_name
|
32
33
|
self.ocm_clients_by_label_spec_name = ocm_clients_by_label_spec_name
|
33
34
|
self.vcs = vcs
|
35
|
+
self.dry_run = dry_run
|
34
36
|
|
35
37
|
@classmethod
|
36
38
|
def create(
|
@@ -42,6 +44,7 @@ class Dependencies:
|
|
42
44
|
label_specs_by_name=_label_specs(),
|
43
45
|
ocm_clients_by_label_spec_name=_ocm_clients(secret_reader=secret_reader),
|
44
46
|
vcs=_vcs(secret_reader=secret_reader, dry_run=dry_run),
|
47
|
+
dry_run=dry_run,
|
45
48
|
)
|
46
49
|
|
47
50
|
|
@@ -16,7 +16,7 @@ from reconcile.fleet_labeler.meta import (
|
|
16
16
|
from reconcile.fleet_labeler.metrics import FleetLabelerDuplicateClusterMatchesGauge
|
17
17
|
from reconcile.fleet_labeler.ocm import OCMClient
|
18
18
|
from reconcile.fleet_labeler.validate import validate_label_specs
|
19
|
-
from reconcile.fleet_labeler.vcs import VCS
|
19
|
+
from reconcile.fleet_labeler.vcs import VCS, Gitlab404Error
|
20
20
|
from reconcile.gql_definitions.fleet_labeler.fleet_labels import (
|
21
21
|
FleetLabelDefaultV1,
|
22
22
|
FleetLabelsSpecV1,
|
@@ -60,7 +60,10 @@ class FleetLabelerIntegration(QontractReconcileIntegration[NoParams]):
|
|
60
60
|
validate_label_specs(specs=dependencies.label_specs_by_name)
|
61
61
|
for spec_name, ocm in dependencies.ocm_clients_by_label_spec_name.items():
|
62
62
|
self._sync_cluster_inventory(
|
63
|
-
ocm,
|
63
|
+
ocm=ocm,
|
64
|
+
spec=dependencies.label_specs_by_name[spec_name],
|
65
|
+
vcs=dependencies.vcs,
|
66
|
+
dry_run=dependencies.dry_run,
|
64
67
|
)
|
65
68
|
|
66
69
|
def _render_default_labels(
|
@@ -112,7 +115,11 @@ class FleetLabelerIntegration(QontractReconcileIntegration[NoParams]):
|
|
112
115
|
return stream.getvalue()
|
113
116
|
|
114
117
|
def _sync_cluster_inventory(
|
115
|
-
self,
|
118
|
+
self,
|
119
|
+
ocm: OCMClient,
|
120
|
+
spec: FleetLabelsSpecV1,
|
121
|
+
vcs: VCS,
|
122
|
+
dry_run: bool,
|
116
123
|
) -> None:
|
117
124
|
class ClusterData(BaseModel):
|
118
125
|
"""
|
@@ -189,7 +196,23 @@ class FleetLabelerIntegration(QontractReconcileIntegration[NoParams]):
|
|
189
196
|
f"[{spec.name}] Deleting cluster {cluster_id=} from inventory."
|
190
197
|
)
|
191
198
|
|
192
|
-
|
199
|
+
# When adding a new label spec file, then we dont have any existing content in main yet.
|
200
|
+
# This is a chicken-egg problem, but if we are in dry-run we can skip these steps on 404s.
|
201
|
+
# Note, that the diff is already printed above, so we can make a good decision if desired
|
202
|
+
# content fits.
|
203
|
+
# If the content exists in main, then it doesnt harm to also run the rendering procedure.
|
204
|
+
try:
|
205
|
+
current_content = vcs.get_file_content_from_main(path=spec.path)
|
206
|
+
except Gitlab404Error:
|
207
|
+
if dry_run:
|
208
|
+
logging.info(
|
209
|
+
f"The file data{spec.path} does not exist in main branch yet. This is likely because it is being created with this MR. We are skipping rendering steps."
|
210
|
+
)
|
211
|
+
return
|
212
|
+
# 404 must never happen on non-dry-run, as the file must have already passed
|
213
|
+
# MR check and must have been merged to main
|
214
|
+
raise
|
215
|
+
|
193
216
|
# Lets make sure we are deterministic when adding new clusters
|
194
217
|
# The overhead is neglectable and it makes testing easier
|
195
218
|
sorted_clusters_to_add = sorted(clusters_to_add, key=lambda c: c.name)
|
reconcile/fleet_labeler/vcs.py
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
+
from gitlab.exceptions import GitlabGetError
|
2
|
+
|
1
3
|
from reconcile.fleet_labeler.merge_request import FleetLabelerUpdates
|
2
4
|
from reconcile.utils.vcs import VCS as VCSBase
|
3
5
|
|
4
6
|
|
7
|
+
class Gitlab404Error(Exception):
|
8
|
+
pass
|
9
|
+
|
10
|
+
|
5
11
|
class VCS:
|
6
12
|
"""
|
7
13
|
Thin abstractions of reconcile.utils.vcs module to reduce coupling and simplify tests.
|
@@ -11,9 +17,16 @@ class VCS:
|
|
11
17
|
self._vcs = vcs
|
12
18
|
|
13
19
|
def get_file_content_from_main(self, path: str) -> str:
|
14
|
-
|
15
|
-
|
16
|
-
|
20
|
+
try:
|
21
|
+
return self._vcs.get_file_content_from_app_interface_ref(
|
22
|
+
file_path=path, ref="main"
|
23
|
+
)
|
24
|
+
except GitlabGetError as e:
|
25
|
+
if e.response_code != 404:
|
26
|
+
raise
|
27
|
+
raise Gitlab404Error(
|
28
|
+
f"File at ${path} does not exist yet in main branch. Maybe it is just being created with this MR?"
|
29
|
+
) from e
|
17
30
|
|
18
31
|
def open_merge_request(self, path: str, content: str) -> None:
|
19
32
|
mr = FleetLabelerUpdates(path=path, content=content)
|
{qontract_reconcile-0.10.2.dev85.dist-info → qontract_reconcile-0.10.2.dev87.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|