qontract-reconcile 0.10.1rc876__py3-none-any.whl → 0.10.1rc878__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.1rc876.dist-info → qontract_reconcile-0.10.1rc878.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc876.dist-info → qontract_reconcile-0.10.1rc878.dist-info}/RECORD +14 -14
- reconcile/gql_definitions/common/saas_files.py +14 -0
- reconcile/gql_definitions/common/saas_target_namespaces.py +4 -0
- reconcile/gql_definitions/fragments/saas_target_namespace.py +6 -0
- reconcile/gql_definitions/templating/template_collection.py +8 -0
- reconcile/templating/renderer.py +55 -23
- reconcile/test/test_saasherder.py +59 -0
- reconcile/utils/saasherder/interfaces.py +8 -0
- reconcile/utils/saasherder/models.py +1 -0
- reconcile/utils/saasherder/saasherder.py +14 -0
- {qontract_reconcile-0.10.1rc876.dist-info → qontract_reconcile-0.10.1rc878.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc876.dist-info → qontract_reconcile-0.10.1rc878.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc876.dist-info → qontract_reconcile-0.10.1rc878.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc876.dist-info → qontract_reconcile-0.10.1rc878.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.1rc878
|
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
|
{qontract_reconcile-0.10.1rc876.dist-info → qontract_reconcile-0.10.1rc878.dist-info}/RECORD
RENAMED
@@ -258,8 +258,8 @@ reconcile/gql_definitions/common/pgp_reencryption_settings.py,sha256=NPLmO6J-zSu
|
|
258
258
|
reconcile/gql_definitions/common/pipeline_providers.py,sha256=JJgmmghqLIwjKOdcWYHPnf4PDgAq4GF7046i0ozrqgI,9127
|
259
259
|
reconcile/gql_definitions/common/quay_instances.py,sha256=toBkdYYVTmEafezAHZKgaW-mQ29xEW6jeronzsAlNyI,1786
|
260
260
|
reconcile/gql_definitions/common/reserved_networks.py,sha256=yP9qSQCaSQcva-ZgTnZp09qH27ur5_qK080ToIs04MY,2560
|
261
|
-
reconcile/gql_definitions/common/saas_files.py,sha256=
|
262
|
-
reconcile/gql_definitions/common/saas_target_namespaces.py,sha256=
|
261
|
+
reconcile/gql_definitions/common/saas_files.py,sha256=rt-ugGWWO148cE_8-cbRkNPJ4peYDQoAnSZ3c5qT_dk,16390
|
262
|
+
reconcile/gql_definitions/common/saas_target_namespaces.py,sha256=ZmX9SYoLQnjTc9zU6ehFNUs_GgOedim3v73A-83Gnsc,2813
|
263
263
|
reconcile/gql_definitions/common/saasherder_settings.py,sha256=nqQLcMwYxLseqq0BEcVvmrpIj2eQq0h8XDSpLN6GGCw,1793
|
264
264
|
reconcile/gql_definitions/common/slack_workspaces.py,sha256=2o0kgi4QiaRuNmZJnc_By4F6NsKIdRaXkrufRQw7Nok,1753
|
265
265
|
reconcile/gql_definitions/common/smtp_client_settings.py,sha256=JU6t6D-Qj-z1gLlgUiHKe0W7AxWQdty9jlv-ig_43tM,2248
|
@@ -299,7 +299,7 @@ reconcile/gql_definitions/fragments/prometheus_instance.py,sha256=12ltnV9kdEw6Ln
|
|
299
299
|
reconcile/gql_definitions/fragments/resource_limits_requirements.py,sha256=ucskQ_a8RxvFl5-IWxz5kk3g4-5Pvh_W4N3nLmuKxi0,744
|
300
300
|
reconcile/gql_definitions/fragments/resource_requests_requirements.py,sha256=TFKO4YALFPanSvZvIJFz0dCioBU7i73Q6hkDtGMvs9I,736
|
301
301
|
reconcile/gql_definitions/fragments/resource_values.py,sha256=-N2lNRhWp8PgocmIeX3U9f3l90Q97N2lXoq1pXdb_LE,742
|
302
|
-
reconcile/gql_definitions/fragments/saas_target_namespace.py,sha256=
|
302
|
+
reconcile/gql_definitions/fragments/saas_target_namespace.py,sha256=Ei39iL5J5TzeYtoBqbUF6zog3ctXTK-ZQyvbIVcUI20,3921
|
303
303
|
reconcile/gql_definitions/fragments/terraform_state.py,sha256=S5QuTR9YlvUObiU7hevS9ybxZEssWoRGqCR9YtGwePs,1024
|
304
304
|
reconcile/gql_definitions/fragments/upgrade_policy.py,sha256=cVza8zfra1E3yBsHiS-hKbys17fvv572GFnKshJjluE,1246
|
305
305
|
reconcile/gql_definitions/fragments/user.py,sha256=84RGYYSYnZmyrwHlCX89-EgAu7UaLFOTMQXobmHCfz8,939
|
@@ -364,7 +364,7 @@ reconcile/gql_definitions/status_board/status_board.py,sha256=vHEzncabujkqbjJ-ib
|
|
364
364
|
reconcile/gql_definitions/statuspage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
365
365
|
reconcile/gql_definitions/statuspage/statuspages.py,sha256=CTRzjiR9k41LqlkgyoNHwC2JERsoD_Run_aK7jw_Ono,5299
|
366
366
|
reconcile/gql_definitions/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
367
|
-
reconcile/gql_definitions/templating/template_collection.py,sha256=
|
367
|
+
reconcile/gql_definitions/templating/template_collection.py,sha256=Y1xA4ODphlsSMjGkAH-nm-dF5FzaQlpfs8hVMBczH5o,3542
|
368
368
|
reconcile/gql_definitions/templating/templates.py,sha256=ejAvQ13zfNMQTz3FWtRUic6dSvio3aAgBKEqt600hbk,2821
|
369
369
|
reconcile/gql_definitions/terraform_cloudflare_dns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
370
370
|
reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py,sha256=eyGX9HcTF6MZbOYZ6Kl6Mg3k6nJTUtwqs9gDxBP_8Dk,1920
|
@@ -461,7 +461,7 @@ reconcile/templates/jira-checkpoint-missinginfo.j2,sha256=c_Vvg-lEENsB3tgxm9B6Y9
|
|
461
461
|
reconcile/templates/rosa-classic-cluster-creation.sh.j2,sha256=0UHfYtXRVJqP07VJQx456cRI6EbZNBgamtP_8nb4WPY,2353
|
462
462
|
reconcile/templates/rosa-hcp-cluster-creation.sh.j2,sha256=O7Bf3WQIJhsZoEqaYA0wRktUO4yXXCb4BQkuvvp-C80,2385
|
463
463
|
reconcile/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
464
|
-
reconcile/templating/renderer.py,sha256=
|
464
|
+
reconcile/templating/renderer.py,sha256=cRgoT5aMTbo3UYR9RkHIajd5lQ4wjN1Bge-DTrhY6Ac,11831
|
465
465
|
reconcile/templating/validator.py,sha256=pvDEc6veznEZzjypkoRJUGMMFLWosU-zd7i3j7JeNjE,4670
|
466
466
|
reconcile/templating/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
467
467
|
reconcile/templating/lib/merge_request_manager.py,sha256=JUkfF3smaQ8onzKF5F7UpmA7MWaQpftANy6dDo1FCug,5464
|
@@ -535,7 +535,7 @@ reconcile/test/test_quay_repos.py,sha256=TdkcRF_a8PLp01Kti9eZZN-vGup2yPBT4Iba3k0
|
|
535
535
|
reconcile/test/test_queries.py,sha256=SpH3RmNpBjEr_ne3VjAMCgKK8RE1z1zo7bypkT5uoO4,1946
|
536
536
|
reconcile/test/test_repo_owners.py,sha256=uRYMLbMmh-9usF0TerabZTZV-Z1CS4I6ybT-LQqCLe8,1423
|
537
537
|
reconcile/test/test_requests_sender.py,sha256=7fd9C2kEFS0-CYtlsif66N1kO9c44pzuBPAJKR9igqU,5385
|
538
|
-
reconcile/test/test_saasherder.py,sha256=
|
538
|
+
reconcile/test/test_saasherder.py,sha256=fdjcygNFPz1YyU2uB-8IU3yQZr6MlD8np2_fYUsL2b8,54440
|
539
539
|
reconcile/test/test_saasherder_allowed_secret_paths.py,sha256=5NHQwNJO66at6HiyMZ5sVRTQDwxdvlOQo0KmkBWCw5Q,4853
|
540
540
|
reconcile/test/test_secret_reader.py,sha256=kz7nzcPjvA08cytnvcA_PMA98AEyqJWsESkYeRn5xCk,4994
|
541
541
|
reconcile/test/test_slack_base.py,sha256=gpbWOLNxMMX6fyAbs1JakhLTnwfedb3f7WpUae4tQZE,5060
|
@@ -787,9 +787,9 @@ reconcile/utils/runtime/meta.py,sha256=X44HzyXIBprf3zcsGr2XLCgoeFkz6r3U2nlFXM1H7
|
|
787
787
|
reconcile/utils/runtime/runner.py,sha256=72cc-I6yXyPov8UCLHpyERRy1eiMLpGite2roO0yUlo,7979
|
788
788
|
reconcile/utils/runtime/sharding.py,sha256=roCdbnBklhTK_g34zbgQYqzpKPaNQ8J6Xd9XLO9-t6Q,16258
|
789
789
|
reconcile/utils/saasherder/__init__.py,sha256=J3MBZBFa5YmhqYm08QsjBXz8mFcVOCiOCkyIcw41t7E,343
|
790
|
-
reconcile/utils/saasherder/interfaces.py,sha256=
|
791
|
-
reconcile/utils/saasherder/models.py,sha256=
|
792
|
-
reconcile/utils/saasherder/saasherder.py,sha256=
|
790
|
+
reconcile/utils/saasherder/interfaces.py,sha256=q7Xhs95RMwOCCqlfjelqWJOiUqhPYkpL20XaOMRxEZs,9321
|
791
|
+
reconcile/utils/saasherder/models.py,sha256=iLQZGXMx4FYnK62MJKkVp1T32BpsZ88NOcU7RW0WyAk,5579
|
792
|
+
reconcile/utils/saasherder/saasherder.py,sha256=cQhyuBGUbtFL8_1lMDRwnrSaYC88C_ApgcaBruZ7BZ4,87523
|
793
793
|
reconcile/utils/terraform/__init__.py,sha256=zNbiyTWo35AT1sFTElL2j_AA0jJ_yWE_bfFn-nD2xik,250
|
794
794
|
reconcile/utils/terraform/config.py,sha256=5UVrd563TMcvi4ooa5JvWVDW1I3bIWg484u79evfV_8,164
|
795
795
|
reconcile/utils/terraform/config_client.py,sha256=py-Ree-QUYD6Hvng6bM40VgSuttteehIKNgwOSoJO1o,4706
|
@@ -837,8 +837,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
|
|
837
837
|
tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jrss,4941
|
838
838
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
839
839
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
840
|
-
qontract_reconcile-0.10.
|
841
|
-
qontract_reconcile-0.10.
|
842
|
-
qontract_reconcile-0.10.
|
843
|
-
qontract_reconcile-0.10.
|
844
|
-
qontract_reconcile-0.10.
|
840
|
+
qontract_reconcile-0.10.1rc878.dist-info/METADATA,sha256=yc6zH-3Cqe1m2HcHSAx5gtwBQGqkbkGv3wpeOBz8b2M,2273
|
841
|
+
qontract_reconcile-0.10.1rc878.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
842
|
+
qontract_reconcile-0.10.1rc878.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
|
843
|
+
qontract_reconcile-0.10.1rc878.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
844
|
+
qontract_reconcile-0.10.1rc878.dist-info/RECORD,,
|
@@ -82,6 +82,10 @@ fragment SaasTargetNamespace on Namespace_v1 {
|
|
82
82
|
name
|
83
83
|
email
|
84
84
|
}
|
85
|
+
codeComponents {
|
86
|
+
url
|
87
|
+
hotfixVersions
|
88
|
+
}
|
85
89
|
}
|
86
90
|
cluster {
|
87
91
|
name
|
@@ -137,6 +141,10 @@ query SaasFiles {
|
|
137
141
|
name
|
138
142
|
email
|
139
143
|
}
|
144
|
+
codeComponents {
|
145
|
+
url
|
146
|
+
hotfixVersions
|
147
|
+
}
|
140
148
|
}
|
141
149
|
pipelinesProvider {
|
142
150
|
name
|
@@ -320,11 +328,17 @@ class OwnerV1(ConfiguredBaseModel):
|
|
320
328
|
email: str = Field(..., alias="email")
|
321
329
|
|
322
330
|
|
331
|
+
class AppCodeComponentsV1(ConfiguredBaseModel):
|
332
|
+
url: str = Field(..., alias="url")
|
333
|
+
hotfix_versions: Optional[list[str]] = Field(..., alias="hotfixVersions")
|
334
|
+
|
335
|
+
|
323
336
|
class AppV1(ConfiguredBaseModel):
|
324
337
|
name: str = Field(..., alias="name")
|
325
338
|
parent_app: Optional[AppV1_AppV1] = Field(..., alias="parentApp")
|
326
339
|
self_service_roles: Optional[list[RoleV1]] = Field(..., alias="selfServiceRoles")
|
327
340
|
service_owners: Optional[list[OwnerV1]] = Field(..., alias="serviceOwners")
|
341
|
+
code_components: Optional[list[AppCodeComponentsV1]] = Field(..., alias="codeComponents")
|
328
342
|
|
329
343
|
|
330
344
|
class PipelinesProviderV1(ConfiguredBaseModel):
|
@@ -52,12 +52,18 @@ class OwnerV1(ConfiguredBaseModel):
|
|
52
52
|
email: str = Field(..., alias="email")
|
53
53
|
|
54
54
|
|
55
|
+
class AppCodeComponentsV1(ConfiguredBaseModel):
|
56
|
+
url: str = Field(..., alias="url")
|
57
|
+
hotfix_versions: Optional[list[str]] = Field(..., alias="hotfixVersions")
|
58
|
+
|
59
|
+
|
55
60
|
class AppV1(ConfiguredBaseModel):
|
56
61
|
name: str = Field(..., alias="name")
|
57
62
|
parent_app: Optional[AppV1_AppV1] = Field(..., alias="parentApp")
|
58
63
|
labels: Optional[Json] = Field(..., alias="labels")
|
59
64
|
self_service_roles: Optional[list[RoleV1]] = Field(..., alias="selfServiceRoles")
|
60
65
|
service_owners: Optional[list[OwnerV1]] = Field(..., alias="serviceOwners")
|
66
|
+
code_components: Optional[list[AppCodeComponentsV1]] = Field(..., alias="codeComponents")
|
61
67
|
|
62
68
|
|
63
69
|
class DisableClusterAutomationsV1(ConfiguredBaseModel):
|
@@ -25,6 +25,9 @@ query TemplateCollection_v1 {
|
|
25
25
|
additionalMrLabels
|
26
26
|
description
|
27
27
|
enableAutoApproval
|
28
|
+
forEach {
|
29
|
+
items
|
30
|
+
}
|
28
31
|
variables {
|
29
32
|
static
|
30
33
|
dynamic {
|
@@ -54,6 +57,10 @@ class ConfiguredBaseModel(BaseModel):
|
|
54
57
|
extra=Extra.forbid
|
55
58
|
|
56
59
|
|
60
|
+
class TemplateCollectionForEachV1(ConfiguredBaseModel):
|
61
|
+
items: Optional[list[Json]] = Field(..., alias="items")
|
62
|
+
|
63
|
+
|
57
64
|
class TemplateCollectionVariablesQueriesV1(ConfiguredBaseModel):
|
58
65
|
name: str = Field(..., alias="name")
|
59
66
|
query: str = Field(..., alias="query")
|
@@ -83,6 +90,7 @@ class TemplateCollectionV1(ConfiguredBaseModel):
|
|
83
90
|
additional_mr_labels: Optional[list[str]] = Field(..., alias="additionalMrLabels")
|
84
91
|
description: str = Field(..., alias="description")
|
85
92
|
enable_auto_approval: Optional[bool] = Field(..., alias="enableAutoApproval")
|
93
|
+
for_each: Optional[TemplateCollectionForEachV1] = Field(..., alias="forEach")
|
86
94
|
variables: Optional[TemplateCollectionVariablesV1] = Field(..., alias="variables")
|
87
95
|
templates: list[TemplateV1] = Field(..., alias="templates")
|
88
96
|
|
reconcile/templating/renderer.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import json
|
1
2
|
import logging
|
2
3
|
import os
|
3
4
|
import tempfile
|
@@ -30,7 +31,7 @@ from reconcile.typed_queries.github_orgs import get_github_orgs
|
|
30
31
|
from reconcile.typed_queries.gitlab_instances import get_gitlab_instances
|
31
32
|
from reconcile.utils import gql
|
32
33
|
from reconcile.utils.git import clone
|
33
|
-
from reconcile.utils.gql import init_from_config
|
34
|
+
from reconcile.utils.gql import GqlApi, init_from_config
|
34
35
|
from reconcile.utils.jinja2.utils import process_jinja2_template
|
35
36
|
from reconcile.utils.ruamel import create_ruamel_instance
|
36
37
|
from reconcile.utils.runtime.integration import (
|
@@ -158,18 +159,25 @@ class ClonedRepoGitlabPersistence(FilePersistence):
|
|
158
159
|
|
159
160
|
def unpack_static_variables(
|
160
161
|
collection_variables: TemplateCollectionVariablesV1,
|
162
|
+
each: dict[str, Any],
|
161
163
|
) -> dict:
|
162
|
-
return
|
164
|
+
return {
|
165
|
+
k: json.loads(process_jinja2_template(body=json.dumps(v), vars={"each": each}))
|
166
|
+
for k, v in (collection_variables.static or {}).items()
|
167
|
+
}
|
163
168
|
|
164
169
|
|
165
170
|
def unpack_dynamic_variables(
|
166
|
-
collection_variables: TemplateCollectionVariablesV1,
|
171
|
+
collection_variables: TemplateCollectionVariablesV1,
|
172
|
+
each: dict[str, Any],
|
173
|
+
gql: gql.GqlApi,
|
167
174
|
) -> dict[str, dict[str, Any]]:
|
168
175
|
static = collection_variables.static or {}
|
169
176
|
dynamic: dict[str, dict[str, Any]] = {}
|
170
177
|
for dv in collection_variables.dynamic or []:
|
171
178
|
query = process_jinja2_template(
|
172
|
-
body=dv.query,
|
179
|
+
body=dv.query,
|
180
|
+
vars={"static": static, "dynamic": dynamic, "each": each},
|
173
181
|
)
|
174
182
|
dynamic[dv.name] = gql.query(query) or {}
|
175
183
|
return dynamic
|
@@ -238,6 +246,38 @@ class TemplateRendererIntegration(QontractReconcileIntegration):
|
|
238
246
|
)
|
239
247
|
return None
|
240
248
|
|
249
|
+
def reconcile_template_collection(
|
250
|
+
self,
|
251
|
+
collection: TemplateCollectionV1,
|
252
|
+
gql_api: GqlApi,
|
253
|
+
dry_run: bool,
|
254
|
+
persistence: FilePersistence,
|
255
|
+
ruamel_instance: yaml.YAML,
|
256
|
+
each: dict[str, Any],
|
257
|
+
) -> None:
|
258
|
+
variables = {}
|
259
|
+
if collection.variables:
|
260
|
+
variables = {
|
261
|
+
"dynamic": unpack_dynamic_variables(
|
262
|
+
collection.variables, each, gql_api
|
263
|
+
),
|
264
|
+
"static": unpack_static_variables(collection.variables, each),
|
265
|
+
}
|
266
|
+
|
267
|
+
with PersistenceTransaction(persistence, dry_run) as p:
|
268
|
+
input = TemplateInput(
|
269
|
+
collection=collection.name,
|
270
|
+
collection_hash=calc_template_hash(collection, variables),
|
271
|
+
enable_auto_approval=collection.enable_auto_approval or False,
|
272
|
+
labels=collection.additional_mr_labels or [],
|
273
|
+
)
|
274
|
+
for template in collection.templates:
|
275
|
+
output = self.process_template(
|
276
|
+
template, variables, p, ruamel_instance, input
|
277
|
+
)
|
278
|
+
if not dry_run and output:
|
279
|
+
p.write([output])
|
280
|
+
|
241
281
|
def reconcile(
|
242
282
|
self,
|
243
283
|
dry_run: bool,
|
@@ -247,26 +287,18 @@ class TemplateRendererIntegration(QontractReconcileIntegration):
|
|
247
287
|
gql_no_validation = init_from_config(validate_schemas=False)
|
248
288
|
|
249
289
|
for c in get_template_collections():
|
250
|
-
|
251
|
-
if c.
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
enable_auto_approval=c.enable_auto_approval or False,
|
262
|
-
labels=c.additional_mr_labels or [],
|
290
|
+
for_each_items: list[dict[str, Any]] = [{}]
|
291
|
+
if c.for_each and c.for_each.items:
|
292
|
+
for_each_items = c.for_each.items
|
293
|
+
for item in for_each_items:
|
294
|
+
self.reconcile_template_collection(
|
295
|
+
c,
|
296
|
+
gql_no_validation,
|
297
|
+
dry_run,
|
298
|
+
persistence,
|
299
|
+
ruamel_instance,
|
300
|
+
item,
|
263
301
|
)
|
264
|
-
for template in c.templates:
|
265
|
-
output = self.process_template(
|
266
|
-
template, variables, p, ruamel_instance, input
|
267
|
-
)
|
268
|
-
if not dry_run and output:
|
269
|
-
p.write([output])
|
270
302
|
|
271
303
|
@property
|
272
304
|
def name(self) -> str:
|
@@ -33,9 +33,12 @@ from reconcile.typed_queries.saas_files import (
|
|
33
33
|
)
|
34
34
|
from reconcile.utils.jjb_client import JJB
|
35
35
|
from reconcile.utils.openshift_resource import ResourceInventory
|
36
|
+
from reconcile.utils.promotion_state import PromotionData
|
36
37
|
from reconcile.utils.saasherder import SaasHerder
|
37
38
|
from reconcile.utils.saasherder.interfaces import SaasFile as SaasFileInterface
|
38
39
|
from reconcile.utils.saasherder.models import (
|
40
|
+
Channel,
|
41
|
+
Promotion,
|
39
42
|
TriggerSpecContainerImage,
|
40
43
|
TriggerSpecMovingCommit,
|
41
44
|
TriggerSpecUpstreamJob,
|
@@ -1350,6 +1353,62 @@ class TestRemoveNoneAttributes(TestCase):
|
|
1350
1353
|
self.assertEqual(res, expected)
|
1351
1354
|
|
1352
1355
|
|
1356
|
+
@pytest.mark.usefixtures("inject_gql_class_factory")
|
1357
|
+
class TestPromotionHoxfixVersions(TestCase):
|
1358
|
+
def setUp(self) -> None:
|
1359
|
+
self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
|
1360
|
+
SaasFile,
|
1361
|
+
Fixtures("saasherder").get_anymarkup("saas.gql.yml"),
|
1362
|
+
)
|
1363
|
+
self.state_patcher = patch("reconcile.utils.state.State", autospec=True)
|
1364
|
+
self.state_mock = self.state_patcher.start().return_value
|
1365
|
+
self.saasherder = SaasHerder(
|
1366
|
+
[self.saas_file],
|
1367
|
+
secret_reader=MockSecretReader(),
|
1368
|
+
thread_pool_size=1,
|
1369
|
+
state=self.state_mock,
|
1370
|
+
integration="",
|
1371
|
+
integration_version="",
|
1372
|
+
hash_length=7,
|
1373
|
+
repo_url="https://repo-url.com",
|
1374
|
+
)
|
1375
|
+
self.promotion_state_patcher = patch(
|
1376
|
+
"reconcile.utils.promotion_state.PromotionState", autospec=True
|
1377
|
+
)
|
1378
|
+
self.promotion_state_mock = self.promotion_state_patcher.start().return_value
|
1379
|
+
self.saasherder._promotion_state = self.promotion_state_mock
|
1380
|
+
|
1381
|
+
def tearDown(self) -> None:
|
1382
|
+
self.state_patcher.stop()
|
1383
|
+
self.promotion_state_patcher.stop()
|
1384
|
+
|
1385
|
+
def test_hotfix_version_valid_promotion(self) -> None:
|
1386
|
+
code_component_url = "https://github.com/app-sre/test-saas-deployments"
|
1387
|
+
hotfix_version = "1234567890123456789012345678901234567890"
|
1388
|
+
# code_component = self.saas_file.app.code_components[0]
|
1389
|
+
channel = Channel(
|
1390
|
+
name="",
|
1391
|
+
publisher_uids=[""],
|
1392
|
+
)
|
1393
|
+
promotion = Promotion(
|
1394
|
+
url=code_component_url,
|
1395
|
+
commit_sha=hotfix_version,
|
1396
|
+
saas_file=self.saas_file.name,
|
1397
|
+
target_config_hash="",
|
1398
|
+
saas_target_uid="",
|
1399
|
+
soak_days=0,
|
1400
|
+
subscribe=[channel],
|
1401
|
+
)
|
1402
|
+
self.saasherder.promotions = [promotion]
|
1403
|
+
self.promotion_state_mock.get_promotion_data.return_value = PromotionData(
|
1404
|
+
success=False
|
1405
|
+
)
|
1406
|
+
self.assertFalse(self.saasherder.validate_promotions())
|
1407
|
+
|
1408
|
+
self.saasherder.hotfix_versions[code_component_url] = {hotfix_version}
|
1409
|
+
self.assertTrue(self.saasherder.validate_promotions())
|
1410
|
+
|
1411
|
+
|
1353
1412
|
def test_render_templated_parameters(
|
1354
1413
|
gql_class_factory: Callable[..., SaasFileInterface],
|
1355
1414
|
) -> None:
|
@@ -63,6 +63,9 @@ class SaasApp(Protocol):
|
|
63
63
|
@property
|
64
64
|
def service_owners(self) -> Optional[Sequence[SaasServiceOwner]]: ...
|
65
65
|
|
66
|
+
@property
|
67
|
+
def code_components(self) -> Optional[Sequence[AppCodeComponent]]: ...
|
68
|
+
|
66
69
|
|
67
70
|
class SaasPipelinesProvider(Protocol):
|
68
71
|
name: str
|
@@ -356,6 +359,11 @@ class SaasServiceOwner(Protocol):
|
|
356
359
|
email: str
|
357
360
|
|
358
361
|
|
362
|
+
class AppCodeComponent(Protocol):
|
363
|
+
url: str
|
364
|
+
hotfix_versions: Optional[list[str]]
|
365
|
+
|
366
|
+
|
359
367
|
SaasPipelinesProviders = Union[SaasPipelinesProviderTekton, SaasPipelinesProvider]
|
360
368
|
|
361
369
|
|
@@ -150,6 +150,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
150
150
|
self._promotion_state = PromotionState(state=state) if state else None
|
151
151
|
self._channel_map = self._assemble_channels(saas_files=all_saas_files)
|
152
152
|
self.images: set[str] = set()
|
153
|
+
self.hotfix_versions = self._collect_hotfix_versions()
|
153
154
|
|
154
155
|
# each namespace is in fact a target,
|
155
156
|
# so we can use it to calculate.
|
@@ -1057,6 +1058,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1057
1058
|
self._channel_map[sub] for sub in target.promotion.subscribe or []
|
1058
1059
|
]
|
1059
1060
|
target_promotion = Promotion(
|
1061
|
+
url=url,
|
1060
1062
|
auto=target.promotion.auto,
|
1061
1063
|
publish=target.promotion.publish,
|
1062
1064
|
subscribe=channels,
|
@@ -1098,6 +1100,14 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1098
1100
|
channel_map[publish].publisher_uids.append(publisher_uid)
|
1099
1101
|
return channel_map
|
1100
1102
|
|
1103
|
+
def _collect_hotfix_versions(self) -> dict[str, set[str]]:
|
1104
|
+
hotfix_versions: dict[str, set[str]] = {}
|
1105
|
+
for saas_file in self.saas_files:
|
1106
|
+
for cc in saas_file.app.code_components or []:
|
1107
|
+
for v in cc.hotfix_versions or []:
|
1108
|
+
hotfix_versions.setdefault(cc.url, set()).add(v)
|
1109
|
+
return hotfix_versions
|
1110
|
+
|
1101
1111
|
@staticmethod
|
1102
1112
|
def _collect_images(resource: Resource) -> set[str]:
|
1103
1113
|
images = set()
|
@@ -1889,6 +1899,10 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1889
1899
|
if not promotion.subscribe:
|
1890
1900
|
return True
|
1891
1901
|
|
1902
|
+
# hotfix must run before further gates are evaluated to override them
|
1903
|
+
if promotion.commit_sha in self.hotfix_versions.get(promotion.url, set()):
|
1904
|
+
return True
|
1905
|
+
|
1892
1906
|
now = datetime.now(timezone.utc)
|
1893
1907
|
passed_soak_days = timedelta(days=0)
|
1894
1908
|
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc876.dist-info → qontract_reconcile-0.10.1rc878.dist-info}/top_level.txt
RENAMED
File without changes
|