qontract-reconcile 0.10.1rc923__py3-none-any.whl → 0.10.1rc925__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.1rc923.dist-info → qontract_reconcile-0.10.1rc925.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc923.dist-info → qontract_reconcile-0.10.1rc925.dist-info}/RECORD +11 -11
- reconcile/templating/lib/merge_request_manager.py +1 -1
- reconcile/templating/lib/model.py +19 -1
- reconcile/templating/renderer.py +32 -32
- reconcile/test/test_saasherder.py +61 -0
- reconcile/utils/promotion_state.py +5 -0
- reconcile/utils/saasherder/saasherder.py +15 -1
- {qontract_reconcile-0.10.1rc923.dist-info → qontract_reconcile-0.10.1rc925.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc923.dist-info → qontract_reconcile-0.10.1rc925.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc923.dist-info → qontract_reconcile-0.10.1rc925.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc923.dist-info → qontract_reconcile-0.10.1rc925.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.1rc925
|
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.1rc923.dist-info → qontract_reconcile-0.10.1rc925.dist-info}/RECORD
RENAMED
@@ -463,11 +463,11 @@ reconcile/templates/jira-checkpoint-missinginfo.j2,sha256=c_Vvg-lEENsB3tgxm9B6Y9
|
|
463
463
|
reconcile/templates/rosa-classic-cluster-creation.sh.j2,sha256=FVBmnR2FmVModhqOYNBInhF8zk0Qnj9og9KHK5-X9v0,2361
|
464
464
|
reconcile/templates/rosa-hcp-cluster-creation.sh.j2,sha256=2wTdv9qvapCvT8NSi_hq8sXhpFSaxRX-V6Cao1diCI8,2393
|
465
465
|
reconcile/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
466
|
-
reconcile/templating/renderer.py,sha256=
|
466
|
+
reconcile/templating/renderer.py,sha256=GjjYPDmiP0zvTDxOr_vAniqN9UHcSMiv-biPUvy8NzM,13091
|
467
467
|
reconcile/templating/validator.py,sha256=5f9f35PCHOOdjb7KZquL2YdabyuAUokPDa4xutSEHIQ,5360
|
468
468
|
reconcile/templating/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
469
|
-
reconcile/templating/lib/merge_request_manager.py,sha256=
|
470
|
-
reconcile/templating/lib/model.py,sha256=
|
469
|
+
reconcile/templating/lib/merge_request_manager.py,sha256=P4IbblzXbubjlaxAYoahHFADUNIXLepfqwzdnHPs8OY,5608
|
470
|
+
reconcile/templating/lib/model.py,sha256=Y1PeEP-koHKiH46JSwv4-PGRGQtcxZa9VmjTiuI6Lxs,889
|
471
471
|
reconcile/templating/lib/rendering.py,sha256=6kt8NCCwB4vLKYal7KtRmBDguIC1p_PIQCRr-vL7p5w,5504
|
472
472
|
reconcile/terraform_init/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
473
473
|
reconcile/terraform_init/integration.py,sha256=glQ9uy8Kj2aTQXCAupwSFeih7reX_xMX_UuWW_ywBMU,6100
|
@@ -537,7 +537,7 @@ reconcile/test/test_quay_repos.py,sha256=TdkcRF_a8PLp01Kti9eZZN-vGup2yPBT4Iba3k0
|
|
537
537
|
reconcile/test/test_queries.py,sha256=SpH3RmNpBjEr_ne3VjAMCgKK8RE1z1zo7bypkT5uoO4,1946
|
538
538
|
reconcile/test/test_repo_owners.py,sha256=uRYMLbMmh-9usF0TerabZTZV-Z1CS4I6ybT-LQqCLe8,1423
|
539
539
|
reconcile/test/test_requests_sender.py,sha256=7fd9C2kEFS0-CYtlsif66N1kO9c44pzuBPAJKR9igqU,5385
|
540
|
-
reconcile/test/test_saasherder.py,sha256=
|
540
|
+
reconcile/test/test_saasherder.py,sha256=jUn8d1DtdXq1pfDrThqIwx37Lgxa7mou3aZ-m3UrWdc,57129
|
541
541
|
reconcile/test/test_saasherder_allowed_secret_paths.py,sha256=5NHQwNJO66at6HiyMZ5sVRTQDwxdvlOQo0KmkBWCw5Q,4853
|
542
542
|
reconcile/test/test_secret_reader.py,sha256=kz7nzcPjvA08cytnvcA_PMA98AEyqJWsESkYeRn5xCk,4994
|
543
543
|
reconcile/test/test_slack_base.py,sha256=pTUGvJ2S2wF3PhJyGWmiNXG52QtXKy2cbu-G8Ymrv6I,5019
|
@@ -689,7 +689,7 @@ reconcile/utils/pagerduty_api.py,sha256=_24i9S_4X7nlvHb-7clXRE0p1BG4ODjOzKxWO-F9
|
|
689
689
|
reconcile/utils/parse_dhms_duration.py,sha256=TONpLnec5gHeF7k815YNJpQyDjXhkxZIcv9s8ffbTSY,1840
|
690
690
|
reconcile/utils/password_validator.py,sha256=XwuWg-8CPlcuG7dl_oQ1G1h2gSVSnfMym_VkuprpWVg,2183
|
691
691
|
reconcile/utils/prometheus.py,sha256=Ad0rwLbxRuuYjHwkwJloHEdK0bvy42h-p-HIT1DhDhs,3832
|
692
|
-
reconcile/utils/promotion_state.py,sha256=
|
692
|
+
reconcile/utils/promotion_state.py,sha256=drkR0PSzfsUVPB0pisARzvUfHgk8KPbeK4FUZdP099Y,3894
|
693
693
|
reconcile/utils/promtool.py,sha256=UmBfTHgW9Ys7fZ9BfhIVJEFGLkbge9y1AgL5PNHp7iA,2831
|
694
694
|
reconcile/utils/quay_api.py,sha256=zbwi3YjL7dTDYHGWcrZ0mbxyZQuEB8v3sV_Km2O-mIs,7906
|
695
695
|
reconcile/utils/raw_github_api.py,sha256=O6Q4vq7bi5ZWcfquPutc9rJ4Ef8_sFqd_RLgzpIoj0w,2920
|
@@ -793,7 +793,7 @@ reconcile/utils/runtime/sharding.py,sha256=r0ieUtNed7NvknSw6qQrCkKpVXE1shuHGnfFc
|
|
793
793
|
reconcile/utils/saasherder/__init__.py,sha256=J3MBZBFa5YmhqYm08QsjBXz8mFcVOCiOCkyIcw41t7E,343
|
794
794
|
reconcile/utils/saasherder/interfaces.py,sha256=C2wrw34OXypshVocAsPrVZsSHptgw4g9u7Haa2wulZQ,9087
|
795
795
|
reconcile/utils/saasherder/models.py,sha256=6MGie9SqsyP5ySjmk5bO5vPJ0-x53a0uzABxQO-WsB0,9746
|
796
|
-
reconcile/utils/saasherder/saasherder.py,sha256=
|
796
|
+
reconcile/utils/saasherder/saasherder.py,sha256=ZTDf6uIpke6y9jKZgESjdY8hDI6Jf2J2UGZp6LJXBWY,85249
|
797
797
|
reconcile/utils/terraform/__init__.py,sha256=zNbiyTWo35AT1sFTElL2j_AA0jJ_yWE_bfFn-nD2xik,250
|
798
798
|
reconcile/utils/terraform/config.py,sha256=5UVrd563TMcvi4ooa5JvWVDW1I3bIWg484u79evfV_8,164
|
799
799
|
reconcile/utils/terraform/config_client.py,sha256=3gUIIIEv52Vx7-VgQ2FZYfCCrfqUv_5gw_TQ3mbLcTs,4666
|
@@ -844,8 +844,8 @@ tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jr
|
|
844
844
|
tools/test/test_saas_promotion_state.py,sha256=dy4kkSSAQ7bC0Xp2CociETGN-2aABEfL6FU5D9Jl00Y,6056
|
845
845
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
846
846
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
847
|
-
qontract_reconcile-0.10.
|
848
|
-
qontract_reconcile-0.10.
|
849
|
-
qontract_reconcile-0.10.
|
850
|
-
qontract_reconcile-0.10.
|
851
|
-
qontract_reconcile-0.10.
|
847
|
+
qontract_reconcile-0.10.1rc925.dist-info/METADATA,sha256=JJstPmbQ_muKOcYIt2w0XSiI3q4MlNNDRsJ0enydTfk,2273
|
848
|
+
qontract_reconcile-0.10.1rc925.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
849
|
+
qontract_reconcile-0.10.1rc925.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
|
850
|
+
qontract_reconcile-0.10.1rc925.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
851
|
+
qontract_reconcile-0.10.1rc925.dist-info/RECORD,,
|
@@ -136,7 +136,7 @@ class MergeRequestManager(MergeRequestManagerBase[TemplateInfo]):
|
|
136
136
|
|
137
137
|
output = data.data
|
138
138
|
collections = {o.input.collection for o in output}
|
139
|
-
collection_hashes = {o.input.
|
139
|
+
collection_hashes = {o.input.calc_template_hash() for o in output}
|
140
140
|
additional_labels = {label for o in output for label in o.input.labels}
|
141
141
|
# From the way the code is written, we can assert that there is only one collection and one template hash
|
142
142
|
assert len(collections) == 1
|
@@ -1,12 +1,30 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from deepdiff import DeepHash
|
1
4
|
from pydantic import BaseModel
|
2
5
|
|
6
|
+
from reconcile.gql_definitions.templating.template_collection import (
|
7
|
+
TemplateV1,
|
8
|
+
)
|
9
|
+
|
3
10
|
|
4
11
|
class TemplateInput(BaseModel):
|
5
12
|
collection: str
|
6
|
-
|
13
|
+
templates: list[TemplateV1] = []
|
14
|
+
variables: list[dict[str, Any]] = []
|
15
|
+
collection_hash: str = ""
|
7
16
|
enable_auto_approval: bool = False
|
8
17
|
labels: list[str] = []
|
9
18
|
|
19
|
+
def calc_template_hash(self) -> str:
|
20
|
+
if not self.collection_hash:
|
21
|
+
hashable = {
|
22
|
+
"templates": sorted(self.templates, key=lambda x: x.name),
|
23
|
+
"variables": self.variables,
|
24
|
+
}
|
25
|
+
self.collection_hash = DeepHash(hashable)[hashable]
|
26
|
+
return self.collection_hash
|
27
|
+
|
10
28
|
|
11
29
|
class TemplateOutput(BaseModel):
|
12
30
|
input: TemplateInput
|
reconcile/templating/renderer.py
CHANGED
@@ -7,7 +7,6 @@ from collections.abc import Callable
|
|
7
7
|
from pathlib import Path
|
8
8
|
from typing import Any, Self
|
9
9
|
|
10
|
-
from deepdiff import DeepHash
|
11
10
|
from ruamel import yaml
|
12
11
|
|
13
12
|
from reconcile.gql_definitions.templating.template_collection import (
|
@@ -187,14 +186,6 @@ def unpack_dynamic_variables(
|
|
187
186
|
return dynamic
|
188
187
|
|
189
188
|
|
190
|
-
def calc_template_hash(c: TemplateCollectionV1, variables: dict[str, Any]) -> str:
|
191
|
-
hashable = {
|
192
|
-
"templates": sorted(c.templates, key=lambda x: x.name),
|
193
|
-
"variables": variables,
|
194
|
-
}
|
195
|
-
return DeepHash(hashable)[hashable]
|
196
|
-
|
197
|
-
|
198
189
|
class TemplateRendererIntegrationParams(PydanticRunParams):
|
199
190
|
clone_repo: bool = False
|
200
191
|
app_interface_data_path: str | None
|
@@ -276,11 +267,11 @@ class TemplateRendererIntegration(QontractReconcileIntegration):
|
|
276
267
|
self,
|
277
268
|
collection: TemplateCollectionV1,
|
278
269
|
gql_api: GqlApi,
|
279
|
-
dry_run: bool,
|
280
270
|
persistence: FilePersistence,
|
281
271
|
ruamel_instance: yaml.YAML,
|
272
|
+
input: TemplateInput,
|
282
273
|
each: dict[str, Any],
|
283
|
-
) ->
|
274
|
+
) -> list[TemplateOutput]:
|
284
275
|
variables = {}
|
285
276
|
if collection.variables:
|
286
277
|
variables = {
|
@@ -289,20 +280,17 @@ class TemplateRendererIntegration(QontractReconcileIntegration):
|
|
289
280
|
),
|
290
281
|
"static": unpack_static_variables(collection.variables, each),
|
291
282
|
}
|
283
|
+
input.variables.append(variables)
|
292
284
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
enable_auto_approval=collection.enable_auto_approval or False,
|
298
|
-
labels=collection.additional_mr_labels or [],
|
285
|
+
outputs: list[TemplateOutput] = []
|
286
|
+
for template in collection.templates:
|
287
|
+
output = self.process_template(
|
288
|
+
template, variables, persistence, ruamel_instance, input
|
299
289
|
)
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
if not dry_run and output:
|
305
|
-
p.write([output])
|
290
|
+
if output:
|
291
|
+
outputs.append(output)
|
292
|
+
|
293
|
+
return outputs
|
306
294
|
|
307
295
|
def reconcile(
|
308
296
|
self,
|
@@ -317,15 +305,27 @@ class TemplateRendererIntegration(QontractReconcileIntegration):
|
|
317
305
|
for_each_items: list[dict[str, Any]] = [{}]
|
318
306
|
if c.for_each and c.for_each.items:
|
319
307
|
for_each_items = c.for_each.items
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
308
|
+
input = TemplateInput(
|
309
|
+
collection=c.name,
|
310
|
+
templates=c.templates,
|
311
|
+
enable_auto_approval=c.enable_auto_approval or False,
|
312
|
+
labels=c.additional_mr_labels or [],
|
313
|
+
)
|
314
|
+
with PersistenceTransaction(persistence, dry_run) as p:
|
315
|
+
outputs: list[TemplateOutput] = []
|
316
|
+
for item in for_each_items:
|
317
|
+
outputs.extend(
|
318
|
+
self.reconcile_template_collection(
|
319
|
+
c,
|
320
|
+
gql_no_validation,
|
321
|
+
p,
|
322
|
+
ruamel_instance,
|
323
|
+
input,
|
324
|
+
item,
|
325
|
+
)
|
326
|
+
)
|
327
|
+
if not dry_run and outputs:
|
328
|
+
p.write(outputs)
|
329
329
|
|
330
330
|
@property
|
331
331
|
def name(self) -> str:
|
@@ -1085,6 +1085,7 @@ class TestConfigHashPromotionsValidation(TestCase):
|
|
1085
1085
|
"success": True,
|
1086
1086
|
"saas_file": self.saas_file.name,
|
1087
1087
|
"target_config_hash": "ed2af38cf21f268c",
|
1088
|
+
"has_succeeded_once": True,
|
1088
1089
|
}
|
1089
1090
|
self.state_mock.get.return_value = publisher_state
|
1090
1091
|
result = self.saasherder.validate_promotions()
|
@@ -1101,16 +1102,19 @@ class TestConfigHashPromotionsValidation(TestCase):
|
|
1101
1102
|
"success": True,
|
1102
1103
|
"saas_file": self.saas_file.name,
|
1103
1104
|
"target_config_hash": "ed2af38cf21f268c",
|
1105
|
+
"has_succeeded_once": True,
|
1104
1106
|
},
|
1105
1107
|
{
|
1106
1108
|
"success": True,
|
1107
1109
|
"saas_file": self.saas_file.name,
|
1108
1110
|
"target_config_hash": "ed2af38cf21f268c",
|
1111
|
+
"has_succeeded_once": True,
|
1109
1112
|
},
|
1110
1113
|
{
|
1111
1114
|
"success": True,
|
1112
1115
|
"saas_file": self.saas_file.name,
|
1113
1116
|
"target_config_hash": "will_not_match",
|
1117
|
+
"has_succeeded_once": True,
|
1114
1118
|
},
|
1115
1119
|
]
|
1116
1120
|
self.state_mock.get.side_effect = publisher_states
|
@@ -1137,6 +1141,7 @@ class TestConfigHashPromotionsValidation(TestCase):
|
|
1137
1141
|
"success": True,
|
1138
1142
|
"saas_file": self.saas_file.name,
|
1139
1143
|
"target_config_hash": "whatever",
|
1144
|
+
"has_succeeded_once": True,
|
1140
1145
|
}
|
1141
1146
|
|
1142
1147
|
self.assertEqual(len(self.saasherder.promotions), 4)
|
@@ -1148,6 +1153,62 @@ class TestConfigHashPromotionsValidation(TestCase):
|
|
1148
1153
|
result = self.saasherder.validate_promotions()
|
1149
1154
|
self.assertTrue(result)
|
1150
1155
|
|
1156
|
+
def test_promotion_state_re_deployment_failed(self) -> None:
|
1157
|
+
"""A promotion is valid if it has ever succeeded for that ref.
|
1158
|
+
Re-deployment results should be neglected for validation.
|
1159
|
+
"""
|
1160
|
+
publisher_state = {
|
1161
|
+
# Latest state is failed ...
|
1162
|
+
"success": False,
|
1163
|
+
"saas_file": self.saas_file.name,
|
1164
|
+
"target_config_hash": "ed2af38cf21f268c",
|
1165
|
+
# ... however, the deployment succeeded sometime before once.
|
1166
|
+
"has_succeeded_once": True,
|
1167
|
+
}
|
1168
|
+
self.state_mock.get.return_value = publisher_state
|
1169
|
+
result = self.saasherder.validate_promotions()
|
1170
|
+
self.assertTrue(result)
|
1171
|
+
|
1172
|
+
def test_promotion_state_never_successfully_deployed(self) -> None:
|
1173
|
+
"""A promotion is invalid, if it never succeeded before."""
|
1174
|
+
publisher_state = {
|
1175
|
+
# Latest state is failed ...
|
1176
|
+
"success": False,
|
1177
|
+
"saas_file": self.saas_file.name,
|
1178
|
+
"target_config_hash": "ed2af38cf21f268c",
|
1179
|
+
# ... and it never succeeded once before.
|
1180
|
+
"has_succeeded_once": False,
|
1181
|
+
}
|
1182
|
+
self.state_mock.get.return_value = publisher_state
|
1183
|
+
result = self.saasherder.validate_promotions()
|
1184
|
+
self.assertFalse(result)
|
1185
|
+
|
1186
|
+
def test_promotion_state_success_backwards_compatibility_success(self) -> None:
|
1187
|
+
"""Not all states have the has_succeeded_once attribute yet.
|
1188
|
+
If it doesnt exist, we should always fall back to latest success state.
|
1189
|
+
"""
|
1190
|
+
publisher_state = {
|
1191
|
+
"success": True,
|
1192
|
+
"saas_file": self.saas_file.name,
|
1193
|
+
"target_config_hash": "ed2af38cf21f268c",
|
1194
|
+
}
|
1195
|
+
self.state_mock.get.return_value = publisher_state
|
1196
|
+
result = self.saasherder.validate_promotions()
|
1197
|
+
self.assertTrue(result)
|
1198
|
+
|
1199
|
+
def test_promotion_state_success_backwards_compatibility_fail(self) -> None:
|
1200
|
+
"""Not all states have the has_succeeded_once attribute yet.
|
1201
|
+
If it doesnt exist, we should always fall back to latest success state.
|
1202
|
+
"""
|
1203
|
+
publisher_state = {
|
1204
|
+
"success": False,
|
1205
|
+
"saas_file": self.saas_file.name,
|
1206
|
+
"target_config_hash": "ed2af38cf21f268c",
|
1207
|
+
}
|
1208
|
+
self.state_mock.get.return_value = publisher_state
|
1209
|
+
result = self.saasherder.validate_promotions()
|
1210
|
+
self.assertFalse(result)
|
1211
|
+
|
1151
1212
|
|
1152
1213
|
@pytest.mark.usefixtures("inject_gql_class_factory")
|
1153
1214
|
class TestSoakDays(TestCase):
|
@@ -18,10 +18,15 @@ class PromotionData(BaseModel):
|
|
18
18
|
requirements.
|
19
19
|
"""
|
20
20
|
|
21
|
+
# The success is primarily used for SAPM auto-promotions
|
21
22
|
success: bool
|
22
23
|
target_config_hash: str | None
|
23
24
|
saas_file: str | None
|
24
25
|
check_in: str | None
|
26
|
+
# Whether this promotion has ever succeeded
|
27
|
+
# Note, this shouldnt be overridden on subsequent promotions of same ref
|
28
|
+
# This attribute is primarily used by saasherder validations
|
29
|
+
has_succeeded_once: bool | None
|
25
30
|
|
26
31
|
class Config:
|
27
32
|
smart_union = True
|
@@ -1846,7 +1846,9 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1846
1846
|
target_uid=target_uid,
|
1847
1847
|
pre_check_sha_exists=False,
|
1848
1848
|
)
|
1849
|
-
if not (
|
1849
|
+
if not (
|
1850
|
+
deployment and (deployment.success or deployment.has_succeeded_once)
|
1851
|
+
):
|
1850
1852
|
logging.error(
|
1851
1853
|
f"Commit {promotion.commit_sha} was not "
|
1852
1854
|
+ f"published with success to channel {channel.name}"
|
@@ -1943,6 +1945,17 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1943
1945
|
all_subscribed_saas_file_paths = set()
|
1944
1946
|
all_subscribed_target_paths = set()
|
1945
1947
|
for channel in promotion.publish:
|
1948
|
+
# make sure we keep some attributes on re-deployments of same ref
|
1949
|
+
has_succeeded_once = success
|
1950
|
+
current_state = self._promotion_state.get_promotion_data(
|
1951
|
+
sha=promotion.commit_sha,
|
1952
|
+
channel=channel,
|
1953
|
+
target_uid=promotion.saas_target_uid,
|
1954
|
+
use_cache=True,
|
1955
|
+
)
|
1956
|
+
if current_state and current_state.has_succeeded_once:
|
1957
|
+
has_succeeded_once = True
|
1958
|
+
|
1946
1959
|
# publish to state to pass promotion gate
|
1947
1960
|
self._promotion_state.publish_promotion_data(
|
1948
1961
|
sha=promotion.commit_sha,
|
@@ -1952,6 +1965,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1952
1965
|
saas_file=promotion.saas_file,
|
1953
1966
|
success=success,
|
1954
1967
|
target_config_hash=promotion.target_config_hash,
|
1968
|
+
has_succeeded_once=has_succeeded_once,
|
1955
1969
|
# TODO: do not override - check if timestamp already exists
|
1956
1970
|
check_in=str(now),
|
1957
1971
|
),
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc923.dist-info → qontract_reconcile-0.10.1rc925.dist-info}/top_level.txt
RENAMED
File without changes
|