qontract-reconcile 0.10.2.dev299__py3-none-any.whl → 0.10.2.dev303__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.dev299.dist-info → qontract_reconcile-0.10.2.dev303.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev303.dist-info}/RECORD +9 -9
- reconcile/gql_definitions/status_board/status_board.py +20 -0
- reconcile/status_board.py +133 -9
- reconcile/typed_queries/status_board.py +43 -8
- reconcile/utils/ocm/status_board.py +13 -0
- tools/qontract_cli.py +1 -1
- {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev303.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev303.dist-info}/entry_points.txt +0 -0
{qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev303.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.dev303
|
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.dev299.dist-info → qontract_reconcile-0.10.2.dev303.dist-info}/RECORD
RENAMED
@@ -102,7 +102,7 @@ reconcile/slack_base.py,sha256=I-msunWxfgu5bSwXYulGbtLjxUB_tRmTCAUCU-3nabI,3484
|
|
102
102
|
reconcile/slack_usergroups.py,sha256=3uQVZK0WeZfvE1g7xQwciKCcC3LifDa3NuE1ygQ0cRk,30174
|
103
103
|
reconcile/sql_query.py,sha256=FVwANLPWjkUHqN2OXJ-vnX5hqqcO6rTdyLEO4HkmAgM,26397
|
104
104
|
reconcile/status.py,sha256=cY4IJFXemhxptRJqR4qaaOWqei9e4jgLXuVSGajMsjg,544
|
105
|
-
reconcile/status_board.py,sha256=
|
105
|
+
reconcile/status_board.py,sha256=Wt8aqCmhTbKxRa9GUj5U_aoqif18z9XL5hRK3zy8too,20091
|
106
106
|
reconcile/terraform_aws_route53.py,sha256=CWp5bE3ddUrJGNNvG8YmkSPyNHCWtOc1GEDVLnbOY9Q,10043
|
107
107
|
reconcile/terraform_cloudflare_dns.py,sha256=0Eu46o_BBEEq-B-CCvKop9VTbwrvliCKGSS9gLBSJE4,13456
|
108
108
|
reconcile/terraform_cloudflare_resources.py,sha256=tK-BxQeNdZjf59deKd51Roz868e7UXe52XvcHsffJK0,14982
|
@@ -405,7 +405,7 @@ reconcile/gql_definitions/slack_usergroups/users.py,sha256=0KFLYHYXL_bGIKf5LAJSY
|
|
405
405
|
reconcile/gql_definitions/slo_documents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
406
406
|
reconcile/gql_definitions/slo_documents/slo_documents.py,sha256=pOrm9NXAonlo6Lxq6NkD3mHkZ53ZeBnZOZMkDvOEwds,3746
|
407
407
|
reconcile/gql_definitions/status_board/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
408
|
-
reconcile/gql_definitions/status_board/status_board.py,sha256=
|
408
|
+
reconcile/gql_definitions/status_board/status_board.py,sha256=qm7TkvvBvkMvf_9SQ50wz8ke8LEvwZYQjMkUDrN-BV0,5370
|
409
409
|
reconcile/gql_definitions/statuspage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
410
410
|
reconcile/gql_definitions/statuspage/statuspages.py,sha256=CTRzjiR9k41LqlkgyoNHwC2JERsoD_Run_aK7jw_Ono,5299
|
411
411
|
reconcile/gql_definitions/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -563,7 +563,7 @@ reconcile/typed_queries/saas_files.py,sha256=SOE36sWPBcuaRmEaNxXCQZMQdJiUZX8_A92
|
|
563
563
|
reconcile/typed_queries/slack.py,sha256=r30lspctHloyygPn8_DVybxPwUWwiBpvBRRXiTVcQYk,251
|
564
564
|
reconcile/typed_queries/slo_documents.py,sha256=YMdox_-lBRqrdxamPhdnUlRTY_Ro35ptsupq7OaynUQ,362
|
565
565
|
reconcile/typed_queries/smtp.py,sha256=aSLglYa5bHKmlGwKkxq2RZqyMWuAf0a4S_mOuhDa084,542
|
566
|
-
reconcile/typed_queries/status_board.py,sha256=
|
566
|
+
reconcile/typed_queries/status_board.py,sha256=ghu3jRJVzEPm5qHQUJWSCGLUdQzBcrtxU4mEeCCdXjo,3091
|
567
567
|
reconcile/typed_queries/tekton_pipeline_providers.py,sha256=LtoSnSRkuckYsXIU64L1Mf-o3iuUjaN-5O-ARzIROZA,515
|
568
568
|
reconcile/typed_queries/terraform_namespaces.py,sha256=4H9WE90jN_BVYBAt1DxJITS4vkL-vykbXZIS1H4EKNM,413
|
569
569
|
reconcile/typed_queries/unleash.py,sha256=7HDc4owF044xM9Thx4WsXV7DZgETxJjy4lbpwmqz1vU,282
|
@@ -733,7 +733,7 @@ reconcile/utils/ocm/products.py,sha256=UtWpkAvSMCxPOulEB7aV5ZY8ej_rmErlE_HVdm9Gn
|
|
733
733
|
reconcile/utils/ocm/search_filters.py,sha256=09p4Wq1d1HGrDiinf1dmLJ46VtFhkkRCOL4V-N-zwjY,14808
|
734
734
|
reconcile/utils/ocm/service_log.py,sha256=RG1f0MMn6joKaRCAm2xveSJCavdOPP1BVo9FXecDxaI,2018
|
735
735
|
reconcile/utils/ocm/sre_capability_labels.py,sha256=nqh0imrYczNeeeC7ZNX3pEwuAIVkKLTKZf0YHSPZYpE,1537
|
736
|
-
reconcile/utils/ocm/status_board.py,sha256=
|
736
|
+
reconcile/utils/ocm/status_board.py,sha256=1T9iD3DxWiVlY_8sK94twH01j_WrUbh-A2FC9dpXYPE,4548
|
737
737
|
reconcile/utils/ocm/subscriptions.py,sha256=hehKXsDXIhnhqvWOuiYvx6y2FGq3zt0APGYj7WiBIdI,2765
|
738
738
|
reconcile/utils/ocm/syncsets.py,sha256=9IQm1l5BodOVZa2OFbQmow3afmh4nXe5pn-CCJ5LxTI,1169
|
739
739
|
reconcile/utils/ocm/upgrades.py,sha256=W8-sLgETI_418ftY9vBlXswyjx_KdhJTJO9cwBL3hfY,4162
|
@@ -770,7 +770,7 @@ tools/app_sre_tekton_access_reporter.py,sha256=5qmkevJdlb2j_lpGC5Pu1Pmo0eomX5Zxz
|
|
770
770
|
tools/app_sre_tekton_access_revalidation.py,sha256=vwL1o_j7oSTOhrHNH1znpgjA2LHGzb8yc5iG3aaY4m0,2684
|
771
771
|
tools/glitchtip_access_reporter.py,sha256=wnaiDGW4MkYONV_erltnJ6nGkEj0kQrAiv04NNnOS0k,2859
|
772
772
|
tools/glitchtip_access_revalidation.py,sha256=jjeLO53LTbz_LfQw3G2Cs8lVLO_6xqU39BYyTH3cEPE,2764
|
773
|
-
tools/qontract_cli.py,sha256=
|
773
|
+
tools/qontract_cli.py,sha256=oOyvoY5-9UzLZbl7iUhENVs8DSH4bORbG90Dxww8GrQ,159925
|
774
774
|
tools/template_validation.py,sha256=Xn9X4sGFznx-rvBDnq9Kq16rfET8V3bqH1EwavsGBac,3335
|
775
775
|
tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
776
776
|
tools/cli_commands/container_images_report.py,sha256=8mAjCS6XR0yD7k0mfiVBlt6xbYU47q_ftdYNi5o5VKE,5566
|
@@ -796,7 +796,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
796
796
|
tools/saas_promotion_state/saas_promotion_state.py,sha256=uQv2QJAmUXP1g2GPIH30WTlvL9soY6m9lefpZEVDM5w,3965
|
797
797
|
tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
|
798
798
|
tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
|
799
|
-
qontract_reconcile-0.10.2.
|
800
|
-
qontract_reconcile-0.10.2.
|
801
|
-
qontract_reconcile-0.10.2.
|
802
|
-
qontract_reconcile-0.10.2.
|
799
|
+
qontract_reconcile-0.10.2.dev303.dist-info/METADATA,sha256=-71qdSLUInk-owzDEw5ShwjQ96x3W206KgjWZ2E4VxQ,24916
|
800
|
+
qontract_reconcile-0.10.2.dev303.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
801
|
+
qontract_reconcile-0.10.2.dev303.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
|
802
|
+
qontract_reconcile-0.10.2.dev303.dist-info/RECORD,,
|
@@ -51,11 +51,19 @@ query StatusBoard {
|
|
51
51
|
childrenApps {
|
52
52
|
name
|
53
53
|
onboardingStatus
|
54
|
+
saasFiles {
|
55
|
+
name
|
56
|
+
managedResourceTypes
|
57
|
+
}
|
54
58
|
}
|
55
59
|
parentApp {
|
56
60
|
name
|
57
61
|
onboardingStatus
|
58
62
|
}
|
63
|
+
saasFiles {
|
64
|
+
name
|
65
|
+
managedResourceTypes
|
66
|
+
}
|
59
67
|
}
|
60
68
|
}
|
61
69
|
}
|
@@ -96,9 +104,15 @@ class ProductV1(ConfiguredBaseModel):
|
|
96
104
|
name: str = Field(..., alias="name")
|
97
105
|
|
98
106
|
|
107
|
+
class SaasFileV2(ConfiguredBaseModel):
|
108
|
+
name: str = Field(..., alias="name")
|
109
|
+
managed_resource_types: list[str] = Field(..., alias="managedResourceTypes")
|
110
|
+
|
111
|
+
|
99
112
|
class AppV1_AppV1(ConfiguredBaseModel):
|
100
113
|
name: str = Field(..., alias="name")
|
101
114
|
onboarding_status: str = Field(..., alias="onboardingStatus")
|
115
|
+
saas_files: Optional[list[SaasFileV2]] = Field(..., alias="saasFiles")
|
102
116
|
|
103
117
|
|
104
118
|
class NamespaceV1_AppV1_AppV1(ConfiguredBaseModel):
|
@@ -106,11 +120,17 @@ class NamespaceV1_AppV1_AppV1(ConfiguredBaseModel):
|
|
106
120
|
onboarding_status: str = Field(..., alias="onboardingStatus")
|
107
121
|
|
108
122
|
|
123
|
+
class AppV1_SaasFileV2(ConfiguredBaseModel):
|
124
|
+
name: str = Field(..., alias="name")
|
125
|
+
managed_resource_types: list[str] = Field(..., alias="managedResourceTypes")
|
126
|
+
|
127
|
+
|
109
128
|
class AppV1(ConfiguredBaseModel):
|
110
129
|
name: str = Field(..., alias="name")
|
111
130
|
onboarding_status: str = Field(..., alias="onboardingStatus")
|
112
131
|
children_apps: Optional[list[AppV1_AppV1]] = Field(..., alias="childrenApps")
|
113
132
|
parent_app: Optional[NamespaceV1_AppV1_AppV1] = Field(..., alias="parentApp")
|
133
|
+
saas_files: Optional[list[AppV1_SaasFileV2]] = Field(..., alias="saasFiles")
|
114
134
|
|
115
135
|
|
116
136
|
class NamespaceV1(ConfiguredBaseModel):
|
reconcile/status_board.py
CHANGED
@@ -6,6 +6,9 @@ from abc import (
|
|
6
6
|
from collections.abc import Iterable, Mapping
|
7
7
|
from enum import Enum
|
8
8
|
from itertools import chain
|
9
|
+
from typing import (
|
10
|
+
Any,
|
11
|
+
)
|
9
12
|
|
10
13
|
from pydantic import BaseModel
|
11
14
|
|
@@ -13,7 +16,7 @@ from reconcile.gql_definitions.slo_documents.slo_documents import SLODocumentV1
|
|
13
16
|
from reconcile.gql_definitions.status_board.status_board import StatusBoardV1
|
14
17
|
from reconcile.typed_queries.slo_documents import get_slo_documents
|
15
18
|
from reconcile.typed_queries.status_board import (
|
16
|
-
|
19
|
+
get_selected_app_data,
|
17
20
|
get_status_board,
|
18
21
|
)
|
19
22
|
from reconcile.utils.differ import diff_mappings
|
@@ -31,6 +34,7 @@ from reconcile.utils.ocm.status_board import (
|
|
31
34
|
get_application_services,
|
32
35
|
get_managed_products,
|
33
36
|
get_product_applications,
|
37
|
+
update_application,
|
34
38
|
update_service,
|
35
39
|
)
|
36
40
|
from reconcile.utils.ocm_base_client import (
|
@@ -55,6 +59,7 @@ class AbstractStatusBoard(ABC, BaseModel):
|
|
55
59
|
id: str | None
|
56
60
|
name: str
|
57
61
|
fullname: str
|
62
|
+
metadata: dict[str, Any] | ServiceMetadataSpec | None
|
58
63
|
|
59
64
|
@abstractmethod
|
60
65
|
def create(self, ocm: OCMBaseClient) -> None:
|
@@ -122,6 +127,7 @@ class Product(AbstractStatusBoard):
|
|
122
127
|
class Application(AbstractStatusBoard):
|
123
128
|
product: Product
|
124
129
|
services: list["Service"] | None
|
130
|
+
metadata: dict[str, Any]
|
125
131
|
|
126
132
|
def create(self, ocm: OCMBaseClient) -> None:
|
127
133
|
if self.product.id:
|
@@ -131,9 +137,11 @@ class Application(AbstractStatusBoard):
|
|
131
137
|
logging.warning("Missing product id for application")
|
132
138
|
|
133
139
|
def update(self, ocm: OCMBaseClient) -> None:
|
134
|
-
|
135
|
-
|
136
|
-
|
140
|
+
if not self.id:
|
141
|
+
logging.error(f'Trying to update Application "{self.name}" without id')
|
142
|
+
return
|
143
|
+
spec = self.to_ocm_spec()
|
144
|
+
update_application(ocm, self.id, spec)
|
137
145
|
|
138
146
|
def delete(self, ocm: OCMBaseClient) -> None:
|
139
147
|
if not self.id:
|
@@ -149,6 +157,7 @@ class Application(AbstractStatusBoard):
|
|
149
157
|
return {
|
150
158
|
"name": self.name,
|
151
159
|
"fullname": self.fullname,
|
160
|
+
"metadata": self.metadata,
|
152
161
|
"product": {"id": product_id},
|
153
162
|
}
|
154
163
|
|
@@ -248,12 +257,14 @@ class StatusBoardExporterIntegration(QontractReconcileIntegration):
|
|
248
257
|
return QONTRACT_INTEGRATION
|
249
258
|
|
250
259
|
@staticmethod
|
251
|
-
def get_product_apps(
|
260
|
+
def get_product_apps(
|
261
|
+
sb: StatusBoardV1,
|
262
|
+
) -> dict[str, dict[str, dict[str, dict[str, set[str]]]]]:
|
252
263
|
global_selectors = (
|
253
264
|
sb.global_app_selectors.exclude or [] if sb.global_app_selectors else []
|
254
265
|
)
|
255
266
|
return {
|
256
|
-
p.product_environment.product.name:
|
267
|
+
p.product_environment.product.name: get_selected_app_data(
|
257
268
|
global_selectors, p
|
258
269
|
)
|
259
270
|
for p in sb.products
|
@@ -287,7 +298,7 @@ class StatusBoardExporterIntegration(QontractReconcileIntegration):
|
|
287
298
|
|
288
299
|
@staticmethod
|
289
300
|
def desired_abstract_status_board_map(
|
290
|
-
desired_product_apps: Mapping[str, set[str]],
|
301
|
+
desired_product_apps: Mapping[str, dict[str, dict[str, dict[str, set[str]]]]],
|
291
302
|
slodocs: list[SLODocumentV1],
|
292
303
|
) -> dict[str, AbstractStatusBoard]:
|
293
304
|
"""
|
@@ -299,7 +310,11 @@ class StatusBoardExporterIntegration(QontractReconcileIntegration):
|
|
299
310
|
desired_abstract_status_board_map: dict[str, AbstractStatusBoard] = {}
|
300
311
|
for product_name, apps in desired_product_apps.items():
|
301
312
|
product = Product(
|
302
|
-
id=None,
|
313
|
+
id=None,
|
314
|
+
name=product_name,
|
315
|
+
fullname=product_name,
|
316
|
+
applications=[],
|
317
|
+
metadata={},
|
303
318
|
)
|
304
319
|
desired_abstract_status_board_map[product_name] = product
|
305
320
|
for a in apps:
|
@@ -310,6 +325,7 @@ class StatusBoardExporterIntegration(QontractReconcileIntegration):
|
|
310
325
|
fullname=key,
|
311
326
|
services=[],
|
312
327
|
product=product,
|
328
|
+
metadata=apps[a]["metadata"],
|
313
329
|
)
|
314
330
|
for slodoc in slodocs:
|
315
331
|
products = [
|
@@ -373,6 +389,99 @@ class StatusBoardExporterIntegration(QontractReconcileIntegration):
|
|
373
389
|
|
374
390
|
return return_value
|
375
391
|
|
392
|
+
@staticmethod
|
393
|
+
def _compare_metadata(
|
394
|
+
current_metadata: dict[str, Any] | ServiceMetadataSpec,
|
395
|
+
desired_metadata: dict[str, Any] | ServiceMetadataSpec,
|
396
|
+
) -> bool:
|
397
|
+
"""
|
398
|
+
Compare metadata dictionaries with deep equality checking for nested structures.
|
399
|
+
|
400
|
+
:param current_metadata: The current metadata dictionary
|
401
|
+
:param desired_metadata: The desired metadata dictionary
|
402
|
+
:return: True if metadata are equal, False otherwise
|
403
|
+
"""
|
404
|
+
# Convert TypedDict to regular dict to allow variable key access
|
405
|
+
current_dict = dict(current_metadata)
|
406
|
+
desired_dict = dict(desired_metadata)
|
407
|
+
|
408
|
+
if current_dict.keys() != desired_dict.keys():
|
409
|
+
return False
|
410
|
+
|
411
|
+
for key, current_value in current_dict.items():
|
412
|
+
desired_value = desired_dict[key]
|
413
|
+
|
414
|
+
# Handle None values
|
415
|
+
if current_value is None or desired_value is None:
|
416
|
+
if current_value is not desired_value:
|
417
|
+
return False
|
418
|
+
continue
|
419
|
+
|
420
|
+
# Handle sets
|
421
|
+
if isinstance(current_value, set) and isinstance(desired_value, set):
|
422
|
+
if current_value != desired_value:
|
423
|
+
return False
|
424
|
+
# Handle lists and tuples
|
425
|
+
elif isinstance(current_value, (list, tuple)) and isinstance(
|
426
|
+
desired_value, (list, tuple)
|
427
|
+
):
|
428
|
+
if len(current_value) != len(desired_value):
|
429
|
+
return False
|
430
|
+
|
431
|
+
try:
|
432
|
+
sorted_current = sorted(current_value, key=repr)
|
433
|
+
sorted_desired = sorted(desired_value, key=repr)
|
434
|
+
except Exception:
|
435
|
+
# Fallback: compare without sorting
|
436
|
+
sorted_current = list(current_value)
|
437
|
+
sorted_desired = list(desired_value)
|
438
|
+
|
439
|
+
for c, d in zip(sorted_current, sorted_desired, strict=True):
|
440
|
+
if isinstance(c, dict) and isinstance(d, dict):
|
441
|
+
if not StatusBoardExporterIntegration._compare_metadata(c, d):
|
442
|
+
return False
|
443
|
+
elif isinstance(c, (list, tuple)) and isinstance(d, (list, tuple)):
|
444
|
+
if not StatusBoardExporterIntegration._compare_metadata(
|
445
|
+
{"x": c}, {"x": d}
|
446
|
+
):
|
447
|
+
return False
|
448
|
+
elif c != d:
|
449
|
+
return False
|
450
|
+
# Handle nested dictionaries
|
451
|
+
elif isinstance(current_value, dict) and isinstance(desired_value, dict):
|
452
|
+
if not StatusBoardExporterIntegration._compare_metadata(
|
453
|
+
current_value, desired_value
|
454
|
+
):
|
455
|
+
return False
|
456
|
+
# Handle primitive types
|
457
|
+
elif current_value != desired_value:
|
458
|
+
return False
|
459
|
+
|
460
|
+
return True
|
461
|
+
|
462
|
+
@staticmethod
|
463
|
+
def _status_board_objects_equal(
|
464
|
+
current: AbstractStatusBoard, desired: AbstractStatusBoard
|
465
|
+
) -> bool:
|
466
|
+
"""
|
467
|
+
Check if two AbstractStatusBoard objects are equal, including metadata comparison.
|
468
|
+
|
469
|
+
:param current: The current AbstractStatusBoard object
|
470
|
+
:param desired: The desired AbstractStatusBoard object
|
471
|
+
:return: True if objects are equal, False otherwise
|
472
|
+
"""
|
473
|
+
# Check basic equality first (name, fullname)
|
474
|
+
if current.name != desired.name or current.fullname != desired.fullname:
|
475
|
+
return False
|
476
|
+
|
477
|
+
# Compare metadata with deep equality
|
478
|
+
if current.metadata and desired.metadata:
|
479
|
+
return StatusBoardExporterIntegration._compare_metadata(
|
480
|
+
current.metadata, desired.metadata
|
481
|
+
)
|
482
|
+
else:
|
483
|
+
return True
|
484
|
+
|
376
485
|
@staticmethod
|
377
486
|
def get_diff(
|
378
487
|
desired_abstract_status_board_map: Mapping[str, AbstractStatusBoard],
|
@@ -383,6 +492,7 @@ class StatusBoardExporterIntegration(QontractReconcileIntegration):
|
|
383
492
|
diff_result = diff_mappings(
|
384
493
|
current_abstract_status_board_map,
|
385
494
|
desired_abstract_status_board_map,
|
495
|
+
equal=StatusBoardExporterIntegration._status_board_objects_equal,
|
386
496
|
)
|
387
497
|
|
388
498
|
for pair in chain(diff_result.identical.values(), diff_result.change.values()):
|
@@ -398,6 +508,18 @@ class StatusBoardExporterIntegration(QontractReconcileIntegration):
|
|
398
508
|
for o in diff_result.delete.values()
|
399
509
|
)
|
400
510
|
|
511
|
+
# Handle Applications that need updates (metadata changes)
|
512
|
+
applications_to_update = [
|
513
|
+
a.desired
|
514
|
+
for _, a in diff_result.change.items()
|
515
|
+
if isinstance(a.desired, Application)
|
516
|
+
]
|
517
|
+
|
518
|
+
return_list.extend(
|
519
|
+
StatusBoardHandler(action=Action.update, status_board_object=a)
|
520
|
+
for a in applications_to_update
|
521
|
+
)
|
522
|
+
|
401
523
|
services_to_update = [
|
402
524
|
s.desired
|
403
525
|
for _, s in diff_result.change.items()
|
@@ -445,7 +567,9 @@ class StatusBoardExporterIntegration(QontractReconcileIntegration):
|
|
445
567
|
ocm_api = init_ocm_base_client(sb.ocm, self.secret_reader)
|
446
568
|
|
447
569
|
# Desired state
|
448
|
-
desired_product_apps: dict[
|
570
|
+
desired_product_apps: dict[
|
571
|
+
str, dict[str, dict[str, dict[str, set[str]]]]
|
572
|
+
] = self.get_product_apps(sb)
|
449
573
|
desired_abstract_status_board_map = self.desired_abstract_status_board_map(
|
450
574
|
desired_product_apps, slodocs
|
451
575
|
)
|
@@ -9,6 +9,10 @@ from reconcile.gql_definitions.status_board.status_board import (
|
|
9
9
|
query,
|
10
10
|
)
|
11
11
|
from reconcile.utils import gql
|
12
|
+
from reconcile.utils.ocm.status_board import (
|
13
|
+
METADATA_MANAGED_BY_KEY,
|
14
|
+
METADATA_MANAGED_BY_VALUE,
|
15
|
+
)
|
12
16
|
|
13
17
|
|
14
18
|
def get_status_board(
|
@@ -19,11 +23,11 @@ def get_status_board(
|
|
19
23
|
return query(query_func).status_board_v1 or []
|
20
24
|
|
21
25
|
|
22
|
-
def
|
26
|
+
def get_selected_app_data(
|
23
27
|
global_selectors: Iterable[str],
|
24
28
|
product: StatusBoardProductV1,
|
25
|
-
) -> set[str]:
|
26
|
-
|
29
|
+
) -> dict[str, dict[str, dict[str, set[str]]]]:
|
30
|
+
selected_app_data: dict[str, dict[str, dict[str, Any]]] = {}
|
27
31
|
|
28
32
|
apps: dict[str, Any] = {"apps": []}
|
29
33
|
for namespace in product.product_environment.namespaces or []:
|
@@ -31,15 +35,45 @@ def get_selected_app_names(
|
|
31
35
|
if namespace.app.parent_app:
|
32
36
|
prefix = f"{namespace.app.parent_app.name}-"
|
33
37
|
name = f"{prefix}{namespace.app.name}"
|
34
|
-
|
38
|
+
|
39
|
+
deployment_saas_files = set()
|
40
|
+
if namespace.app.saas_files:
|
41
|
+
deployment_saas_files = {
|
42
|
+
saas_file.name
|
43
|
+
for saas_file in namespace.app.saas_files
|
44
|
+
if "Deployment" in saas_file.managed_resource_types
|
45
|
+
or "ClowdApp" in saas_file.managed_resource_types
|
46
|
+
}
|
47
|
+
|
48
|
+
selected_app_data[name] = {
|
49
|
+
"metadata": {
|
50
|
+
METADATA_MANAGED_BY_KEY: METADATA_MANAGED_BY_VALUE,
|
51
|
+
"deploymentSaasFiles": set(deployment_saas_files),
|
52
|
+
},
|
53
|
+
}
|
54
|
+
|
35
55
|
app = namespace.app.dict(by_alias=True)
|
36
56
|
app["name"] = name
|
37
57
|
apps["apps"].append(app)
|
38
58
|
|
39
59
|
for child in namespace.app.children_apps or []:
|
40
60
|
name = f"{namespace.app.name}-{child.name}"
|
41
|
-
if name not in
|
42
|
-
|
61
|
+
if name not in selected_app_data:
|
62
|
+
deployment_saas_files = set()
|
63
|
+
if child.saas_files:
|
64
|
+
deployment_saas_files = {
|
65
|
+
saas_file.name
|
66
|
+
for saas_file in child.saas_files
|
67
|
+
if "Deployment" in saas_file.managed_resource_types
|
68
|
+
}
|
69
|
+
|
70
|
+
selected_app_data[name] = {
|
71
|
+
"metadata": {
|
72
|
+
METADATA_MANAGED_BY_KEY: METADATA_MANAGED_BY_VALUE,
|
73
|
+
"deploymentSaasFiles": set(deployment_saas_files),
|
74
|
+
},
|
75
|
+
}
|
76
|
+
|
43
77
|
child_dict = child.dict(by_alias=True)
|
44
78
|
child_dict["name"] = name
|
45
79
|
apps["apps"].append(child_dict)
|
@@ -52,6 +86,7 @@ def get_selected_app_names(
|
|
52
86
|
apps_to_remove: set[str] = set()
|
53
87
|
results = parser.parse(selector).find(apps)
|
54
88
|
apps_to_remove.update(match.value["name"] for match in results)
|
55
|
-
|
89
|
+
for app_name in apps_to_remove:
|
90
|
+
selected_app_data.pop(app_name, None)
|
56
91
|
|
57
|
-
return
|
92
|
+
return selected_app_data
|
@@ -21,6 +21,7 @@ class IDSpec(TypedDict):
|
|
21
21
|
|
22
22
|
|
23
23
|
class ApplicationOCMSpec(BaseOCMSpec):
|
24
|
+
metadata: dict[str, Any]
|
24
25
|
product: IDSpec
|
25
26
|
|
26
27
|
|
@@ -117,6 +118,18 @@ def create_service(ocm_api: OCMBaseClient, spec: ServiceOCMSpec) -> str:
|
|
117
118
|
return resp["id"]
|
118
119
|
|
119
120
|
|
121
|
+
def update_application(
|
122
|
+
ocm_api: OCMBaseClient,
|
123
|
+
application_id: str,
|
124
|
+
spec: ApplicationOCMSpec,
|
125
|
+
) -> None:
|
126
|
+
data = spec | {
|
127
|
+
"metadata": spec.get("metadata", {})
|
128
|
+
| {METADATA_MANAGED_BY_KEY: METADATA_MANAGED_BY_VALUE}
|
129
|
+
}
|
130
|
+
ocm_api.patch(f"/api/status-board/v1/applications/{application_id}", data=data)
|
131
|
+
|
132
|
+
|
120
133
|
def update_service(
|
121
134
|
ocm_api: OCMBaseClient,
|
122
135
|
service_id: str,
|
tools/qontract_cli.py
CHANGED
@@ -2772,7 +2772,7 @@ def slo_document_services(ctx: click.Context, status_board_instance: str) -> Non
|
|
2772
2772
|
print(f"Status-board instance '{status_board_instance}' not found.")
|
2773
2773
|
sys.exit(1)
|
2774
2774
|
|
2775
|
-
desired_product_apps: dict[str, set[str]] = (
|
2775
|
+
desired_product_apps: dict[str, dict[str, dict[str, dict[str, set[str]]]]] = (
|
2776
2776
|
StatusBoardExporterIntegration.get_product_apps(sb)
|
2777
2777
|
)
|
2778
2778
|
|
{qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev303.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|