qontract-reconcile 0.10.1rc850__py3-none-any.whl → 0.10.1rc852__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.1rc850.dist-info → qontract_reconcile-0.10.1rc852.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc850.dist-info → qontract_reconcile-0.10.1rc852.dist-info}/RECORD +10 -10
- reconcile/saas_auto_promotions_manager/subscriber.py +2 -52
- reconcile/test/test_saasherder.py +140 -10
- reconcile/utils/saasherder/interfaces.py +2 -0
- reconcile/utils/saasherder/models.py +1 -0
- reconcile/utils/saasherder/saasherder.py +85 -68
- {qontract_reconcile-0.10.1rc850.dist-info → qontract_reconcile-0.10.1rc852.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc850.dist-info → qontract_reconcile-0.10.1rc852.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc850.dist-info → qontract_reconcile-0.10.1rc852.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc850.dist-info → qontract_reconcile-0.10.1rc852.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.1rc852
|
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.1rc850.dist-info → qontract_reconcile-0.10.1rc852.dist-info}/RECORD
RENAMED
@@ -426,7 +426,7 @@ reconcile/saas_auto_promotions_manager/integration.py,sha256=8IXLEvpblgZRr2UPfTs
|
|
426
426
|
reconcile/saas_auto_promotions_manager/meta.py,sha256=76Jp50r6Y_KyJoXFfSjrt5YrCtXyg_A4FXXxHYiS3TE,161
|
427
427
|
reconcile/saas_auto_promotions_manager/publisher.py,sha256=am8b0uLZIhWjSAgUHPtSOu2fRpa5wXaMl_WnVe4gv2k,3009
|
428
428
|
reconcile/saas_auto_promotions_manager/s3_exporter.py,sha256=IKlVWZmiPnvl7sKeF6JgAlhXZe5CovKTxQc0SNkNSx4,2583
|
429
|
-
reconcile/saas_auto_promotions_manager/subscriber.py,sha256=
|
429
|
+
reconcile/saas_auto_promotions_manager/subscriber.py,sha256=zAqifs7LxssQurO9UwWNrUnOqomRLuD_Z7nTXhU490o,8519
|
430
430
|
reconcile/saas_auto_promotions_manager/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
431
431
|
reconcile/saas_auto_promotions_manager/merge_request_manager/batcher.py,sha256=lHIULPE8nmPN9pJVfZFdD0l48EB20o4cAy4owahJenw,7848
|
432
432
|
reconcile/saas_auto_promotions_manager/merge_request_manager/desired_state.py,sha256=isY8frVsL3PlcdZmdZ4O0qyp76oczl4DUMX9uMArs5Y,1222
|
@@ -532,7 +532,7 @@ reconcile/test/test_quay_repos.py,sha256=TdkcRF_a8PLp01Kti9eZZN-vGup2yPBT4Iba3k0
|
|
532
532
|
reconcile/test/test_queries.py,sha256=SpH3RmNpBjEr_ne3VjAMCgKK8RE1z1zo7bypkT5uoO4,1946
|
533
533
|
reconcile/test/test_repo_owners.py,sha256=uRYMLbMmh-9usF0TerabZTZV-Z1CS4I6ybT-LQqCLe8,1423
|
534
534
|
reconcile/test/test_requests_sender.py,sha256=7fd9C2kEFS0-CYtlsif66N1kO9c44pzuBPAJKR9igqU,5385
|
535
|
-
reconcile/test/test_saasherder.py,sha256=
|
535
|
+
reconcile/test/test_saasherder.py,sha256=QFX6JrPCpB9jS-K_VleIjTf6NaF8NDa4UwddXXo1yPk,52127
|
536
536
|
reconcile/test/test_saasherder_allowed_secret_paths.py,sha256=5NHQwNJO66at6HiyMZ5sVRTQDwxdvlOQo0KmkBWCw5Q,4853
|
537
537
|
reconcile/test/test_secret_reader.py,sha256=kz7nzcPjvA08cytnvcA_PMA98AEyqJWsESkYeRn5xCk,4994
|
538
538
|
reconcile/test/test_slack_base.py,sha256=gpbWOLNxMMX6fyAbs1JakhLTnwfedb3f7WpUae4tQZE,5060
|
@@ -784,9 +784,9 @@ reconcile/utils/runtime/meta.py,sha256=X44HzyXIBprf3zcsGr2XLCgoeFkz6r3U2nlFXM1H7
|
|
784
784
|
reconcile/utils/runtime/runner.py,sha256=72cc-I6yXyPov8UCLHpyERRy1eiMLpGite2roO0yUlo,7979
|
785
785
|
reconcile/utils/runtime/sharding.py,sha256=roCdbnBklhTK_g34zbgQYqzpKPaNQ8J6Xd9XLO9-t6Q,16258
|
786
786
|
reconcile/utils/saasherder/__init__.py,sha256=J3MBZBFa5YmhqYm08QsjBXz8mFcVOCiOCkyIcw41t7E,343
|
787
|
-
reconcile/utils/saasherder/interfaces.py,sha256=
|
788
|
-
reconcile/utils/saasherder/models.py,sha256=
|
789
|
-
reconcile/utils/saasherder/saasherder.py,sha256=
|
787
|
+
reconcile/utils/saasherder/interfaces.py,sha256=Tte-BAJ71FZF1J_ADay1UVIxLCJZcbefq4SRua4mn5w,9141
|
788
|
+
reconcile/utils/saasherder/models.py,sha256=XiAb9pSmTxaNFa3XqNNfe1JxlGgTqsmd1nLi17iIV_g,5566
|
789
|
+
reconcile/utils/saasherder/saasherder.py,sha256=4S3Q4tb11hmBI3rux5ajk-BUs-6Lfpg47uHZJj2uNKk,86787
|
790
790
|
reconcile/utils/terraform/__init__.py,sha256=zNbiyTWo35AT1sFTElL2j_AA0jJ_yWE_bfFn-nD2xik,250
|
791
791
|
reconcile/utils/terraform/config.py,sha256=5UVrd563TMcvi4ooa5JvWVDW1I3bIWg484u79evfV_8,164
|
792
792
|
reconcile/utils/terraform/config_client.py,sha256=py-Ree-QUYD6Hvng6bM40VgSuttteehIKNgwOSoJO1o,4706
|
@@ -834,8 +834,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
|
|
834
834
|
tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jrss,4941
|
835
835
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
836
836
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
837
|
-
qontract_reconcile-0.10.
|
838
|
-
qontract_reconcile-0.10.
|
839
|
-
qontract_reconcile-0.10.
|
840
|
-
qontract_reconcile-0.10.
|
841
|
-
qontract_reconcile-0.10.
|
837
|
+
qontract_reconcile-0.10.1rc852.dist-info/METADATA,sha256=iKUIKevX3oHc70SMvq6rkwA29a-RH-fO9SncYOwTdQ4,2273
|
838
|
+
qontract_reconcile-0.10.1rc852.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
839
|
+
qontract_reconcile-0.10.1rc852.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
|
840
|
+
qontract_reconcile-0.10.1rc852.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
841
|
+
qontract_reconcile-0.10.1rc852.dist-info/RECORD,,
|
@@ -1,9 +1,9 @@
|
|
1
1
|
import hashlib
|
2
2
|
import logging
|
3
|
-
from collections.abc import Iterable
|
3
|
+
from collections.abc import Iterable
|
4
4
|
from dataclasses import dataclass
|
5
5
|
from datetime import datetime, timedelta, timezone
|
6
|
-
from typing import
|
6
|
+
from typing import Optional
|
7
7
|
|
8
8
|
from reconcile.gql_definitions.fragments.saas_target_namespace import (
|
9
9
|
SaasTargetNamespace,
|
@@ -78,56 +78,6 @@ class Subscriber:
|
|
78
78
|
self._compute_desired_ref()
|
79
79
|
self._compute_desired_config_hashes()
|
80
80
|
|
81
|
-
@staticmethod
|
82
|
-
def from_exported_dict(data: Mapping[str, Any]) -> "Subscriber":
|
83
|
-
subscriber = Subscriber(
|
84
|
-
saas_name=data["1"],
|
85
|
-
template_name=data["2"],
|
86
|
-
ref=data["3"],
|
87
|
-
target_file_path=data["4"],
|
88
|
-
use_target_config_hash=data["5"],
|
89
|
-
target_namespace=SaasTargetNamespace(**data["6"]),
|
90
|
-
uid=data["7"],
|
91
|
-
soak_days=data["8"],
|
92
|
-
)
|
93
|
-
subscriber.desired_hashes = data["9"]
|
94
|
-
subscriber.desired_ref = data["10"]
|
95
|
-
return subscriber
|
96
|
-
|
97
|
-
def to_exportable_dict(self) -> dict[str, Any]:
|
98
|
-
"""
|
99
|
-
We will later persist subscriber data as json in MRs. We keep key size small to use less space.
|
100
|
-
Note, the data will be encoded and encrypted in another component.
|
101
|
-
"""
|
102
|
-
data: dict[str, Any] = {}
|
103
|
-
data["1"] = self.saas_name
|
104
|
-
data["2"] = self.template_name
|
105
|
-
data["3"] = self.ref
|
106
|
-
data["4"] = self.target_file_path
|
107
|
-
data["5"] = self._use_target_config_hash
|
108
|
-
data["6"] = self.target_namespace.dict(by_alias=True)
|
109
|
-
data["7"] = self.uid
|
110
|
-
data["8"] = self.soak_days
|
111
|
-
data["9"] = self.desired_hashes
|
112
|
-
data["10"] = self.desired_ref
|
113
|
-
return data
|
114
|
-
|
115
|
-
def __eq__(self, other: object) -> bool:
|
116
|
-
if not isinstance(other, Subscriber):
|
117
|
-
# don't attempt to compare against unrelated types
|
118
|
-
return False
|
119
|
-
return (
|
120
|
-
self.saas_name == other.saas_name
|
121
|
-
and self.template_name == other.template_name
|
122
|
-
and self.ref == other.ref
|
123
|
-
and self.target_file_path == other.target_file_path
|
124
|
-
and self._use_target_config_hash == other._use_target_config_hash
|
125
|
-
and self.desired_ref == other.desired_ref
|
126
|
-
and self.desired_hashes == other.desired_hashes
|
127
|
-
and self.target_namespace == other.target_namespace
|
128
|
-
and self.uid == other.uid
|
129
|
-
)
|
130
|
-
|
131
81
|
def _validate_deployment(
|
132
82
|
self, publisher: Publisher, channel: Channel
|
133
83
|
) -> Optional[DeploymentInfo]:
|
@@ -3,6 +3,7 @@ from collections.abc import (
|
|
3
3
|
Iterable,
|
4
4
|
MutableMapping,
|
5
5
|
)
|
6
|
+
from datetime import datetime, timedelta, timezone
|
6
7
|
from typing import (
|
7
8
|
Any,
|
8
9
|
Optional,
|
@@ -1026,7 +1027,7 @@ class TestConfigHashPromotionsValidation(TestCase):
|
|
1026
1027
|
|
1027
1028
|
def setUp(self) -> None:
|
1028
1029
|
self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
|
1029
|
-
SaasFile, Fixtures("saasherder").get_anymarkup("saas.gql.yml")
|
1030
|
+
SaasFile, Fixtures("saasherder").get_anymarkup("saas-multi-channel.gql.yml")
|
1030
1031
|
)
|
1031
1032
|
self.state_patcher = patch("reconcile.utils.state.State", autospec=True)
|
1032
1033
|
self.state_mock = self.state_patcher.start().return_value
|
@@ -1095,12 +1096,24 @@ class TestConfigHashPromotionsValidation(TestCase):
|
|
1095
1096
|
promotion_data. This could happen if the parent target has run again
|
1096
1097
|
with the same ref before the subscriber target promotion MR is merged.
|
1097
1098
|
"""
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1099
|
+
publisher_states = [
|
1100
|
+
{
|
1101
|
+
"success": True,
|
1102
|
+
"saas_file": self.saas_file.name,
|
1103
|
+
"target_config_hash": "ed2af38cf21f268c",
|
1104
|
+
},
|
1105
|
+
{
|
1106
|
+
"success": True,
|
1107
|
+
"saas_file": self.saas_file.name,
|
1108
|
+
"target_config_hash": "ed2af38cf21f268c",
|
1109
|
+
},
|
1110
|
+
{
|
1111
|
+
"success": True,
|
1112
|
+
"saas_file": self.saas_file.name,
|
1113
|
+
"target_config_hash": "will_not_match",
|
1114
|
+
},
|
1115
|
+
]
|
1116
|
+
self.state_mock.get.side_effect = publisher_states
|
1104
1117
|
result = self.saasherder.validate_promotions()
|
1105
1118
|
self.assertFalse(result)
|
1106
1119
|
|
@@ -1126,16 +1139,133 @@ class TestConfigHashPromotionsValidation(TestCase):
|
|
1126
1139
|
"target_config_hash": "whatever",
|
1127
1140
|
}
|
1128
1141
|
|
1129
|
-
self.assertEqual(len(self.saasherder.promotions),
|
1130
|
-
self.assertIsNotNone(self.saasherder.promotions[
|
1142
|
+
self.assertEqual(len(self.saasherder.promotions), 4)
|
1143
|
+
self.assertIsNotNone(self.saasherder.promotions[3])
|
1131
1144
|
# Remove promotion_data on the promoted target
|
1132
|
-
self.saasherder.promotions[
|
1145
|
+
self.saasherder.promotions[3].promotion_data = None # type: ignore
|
1133
1146
|
|
1134
1147
|
self.state_mock.get.return_value = publisher_state
|
1135
1148
|
result = self.saasherder.validate_promotions()
|
1136
1149
|
self.assertTrue(result)
|
1137
1150
|
|
1138
1151
|
|
1152
|
+
@pytest.mark.usefixtures("inject_gql_class_factory")
|
1153
|
+
class TestSoakDays(TestCase):
|
1154
|
+
"""TestCase to test SaasHerder soakDays gate. SaasHerder is
|
1155
|
+
initialized with ResourceInventory population. Like is done in
|
1156
|
+
openshift-saas-deploy"""
|
1157
|
+
|
1158
|
+
cluster: str
|
1159
|
+
namespace: str
|
1160
|
+
fxt: Any
|
1161
|
+
template: Any
|
1162
|
+
|
1163
|
+
@classmethod
|
1164
|
+
def setUpClass(cls) -> None:
|
1165
|
+
cls.fxt = Fixtures("saasherder")
|
1166
|
+
cls.cluster = "test-cluster"
|
1167
|
+
cls.template = cls.fxt.get_anymarkup("template_1.yml")
|
1168
|
+
|
1169
|
+
def setUp(self) -> None:
|
1170
|
+
self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
|
1171
|
+
SaasFile, Fixtures("saasherder").get_anymarkup("saas-soak-days.gql.yml")
|
1172
|
+
)
|
1173
|
+
self.state_patcher = patch("reconcile.utils.state.State", autospec=True)
|
1174
|
+
self.state_mock = self.state_patcher.start().return_value
|
1175
|
+
|
1176
|
+
self.ig_patcher = patch.object(SaasHerder, "_initiate_github", autospec=True)
|
1177
|
+
self.ig_patcher.start()
|
1178
|
+
|
1179
|
+
self.image_auth_patcher = patch.object(SaasHerder, "_initiate_image_auth")
|
1180
|
+
self.image_auth_patcher.start()
|
1181
|
+
|
1182
|
+
self.gfc_patcher = patch.object(SaasHerder, "_get_file_contents", autospec=True)
|
1183
|
+
gfc_mock = self.gfc_patcher.start()
|
1184
|
+
gfc_mock.return_value = (self.template, "url", "ahash")
|
1185
|
+
|
1186
|
+
self.deploy_current_state_fxt = self.fxt.get_anymarkup("saas_deploy.state.json")
|
1187
|
+
|
1188
|
+
self.post_deploy_current_state_fxt = self.fxt.get_anymarkup(
|
1189
|
+
"saas_post_deploy.state.json"
|
1190
|
+
)
|
1191
|
+
|
1192
|
+
self.saasherder = SaasHerder(
|
1193
|
+
[self.saas_file],
|
1194
|
+
secret_reader=MockSecretReader(),
|
1195
|
+
thread_pool_size=1,
|
1196
|
+
state=self.state_mock,
|
1197
|
+
integration="",
|
1198
|
+
integration_version="",
|
1199
|
+
hash_length=24,
|
1200
|
+
repo_url="https://repo-url.com",
|
1201
|
+
all_saas_files=[self.saas_file],
|
1202
|
+
)
|
1203
|
+
|
1204
|
+
# IMPORTANT: Populating desired state modify self.saas_files within
|
1205
|
+
# saasherder object.
|
1206
|
+
self.ri = ResourceInventory()
|
1207
|
+
for ns in ["test-ns-publisher", "test-ns-subscriber"]:
|
1208
|
+
for kind in ["Service", "Deployment"]:
|
1209
|
+
self.ri.initialize_resource_type(self.cluster, ns, kind)
|
1210
|
+
|
1211
|
+
self.saasherder.populate_desired_state(self.ri)
|
1212
|
+
if self.ri.has_error_registered():
|
1213
|
+
raise Exception("Errors registered in Resourceinventory")
|
1214
|
+
|
1215
|
+
def tearDown(self) -> None:
|
1216
|
+
self.state_patcher.stop()
|
1217
|
+
self.ig_patcher.stop()
|
1218
|
+
self.gfc_patcher.stop()
|
1219
|
+
|
1220
|
+
def test_soak_days_passed(self) -> None:
|
1221
|
+
"""A promotion is valid if the parent targets accumulated soak_days
|
1222
|
+
passed. We have a soakDays setting of 2 days.
|
1223
|
+
"""
|
1224
|
+
publisher_states = [
|
1225
|
+
{
|
1226
|
+
"success": True,
|
1227
|
+
"saas_file": self.saas_file.name,
|
1228
|
+
"target_config_hash": "ed2af38cf21f268c",
|
1229
|
+
# the deployment happened 1 hour ago
|
1230
|
+
"check_in": str(datetime.now(timezone.utc) - timedelta(hours=1)),
|
1231
|
+
},
|
1232
|
+
{
|
1233
|
+
"success": True,
|
1234
|
+
"saas_file": self.saas_file.name,
|
1235
|
+
"target_config_hash": "ed2af38cf21f268c",
|
1236
|
+
# the deployment happened 47 hours ago
|
1237
|
+
"check_in": str(datetime.now(timezone.utc) - timedelta(hours=47)),
|
1238
|
+
},
|
1239
|
+
]
|
1240
|
+
self.state_mock.get.side_effect = publisher_states
|
1241
|
+
result = self.saasherder.validate_promotions()
|
1242
|
+
self.assertTrue(result)
|
1243
|
+
|
1244
|
+
def test_soak_days_not_passed(self) -> None:
|
1245
|
+
"""A promotion is valid if the parent target accumulated soak_days
|
1246
|
+
passed. We have a soakDays setting of 2 days.
|
1247
|
+
"""
|
1248
|
+
publisher_states = [
|
1249
|
+
{
|
1250
|
+
"success": True,
|
1251
|
+
"saas_file": self.saas_file.name,
|
1252
|
+
"target_config_hash": "ed2af38cf21f268c",
|
1253
|
+
# the deployment happened 12 hours ago
|
1254
|
+
"check_in": str(datetime.now(timezone.utc) - timedelta(hours=12)),
|
1255
|
+
},
|
1256
|
+
{
|
1257
|
+
"success": True,
|
1258
|
+
"saas_file": self.saas_file.name,
|
1259
|
+
"target_config_hash": "ed2af38cf21f268c",
|
1260
|
+
# the deployment happened 1 hour ago
|
1261
|
+
"check_in": str(datetime.now(timezone.utc) - timedelta(hours=1)),
|
1262
|
+
},
|
1263
|
+
]
|
1264
|
+
self.state_mock.get.side_effect = publisher_states
|
1265
|
+
result = self.saasherder.validate_promotions()
|
1266
|
+
self.assertFalse(result)
|
1267
|
+
|
1268
|
+
|
1139
1269
|
@pytest.mark.usefixtures("inject_gql_class_factory")
|
1140
1270
|
class TestConfigHashTrigger(TestCase):
|
1141
1271
|
"""TestCase to test Openshift SAAS deploy configs trigger. SaasHerder is
|
@@ -242,6 +242,7 @@ class SaasResourceTemplateTargetPromotion(Protocol):
|
|
242
242
|
auto: Optional[bool]
|
243
243
|
publish: Optional[list[str]]
|
244
244
|
subscribe: Optional[list[str]]
|
245
|
+
soak_days: Optional[int]
|
245
246
|
|
246
247
|
@property
|
247
248
|
def promotion_data(self) -> Optional[Sequence[SaasPromotionData]]: ...
|
@@ -261,6 +262,7 @@ class SaasPromotion(Protocol):
|
|
261
262
|
publish: Optional[list[str]] = None
|
262
263
|
saas_file_paths: Optional[list[str]] = None
|
263
264
|
target_paths: Optional[list[str]] = None
|
265
|
+
soak_days: Optional[int] = None
|
264
266
|
|
265
267
|
@property
|
266
268
|
def promotion_data(self) -> Optional[Sequence[SaasPromotionData]]: ...
|
@@ -16,7 +16,7 @@ from collections.abc import (
|
|
16
16
|
Sequence,
|
17
17
|
)
|
18
18
|
from contextlib import suppress
|
19
|
-
from datetime import datetime, timezone
|
19
|
+
from datetime import datetime, timedelta, timezone
|
20
20
|
from types import TracebackType
|
21
21
|
from typing import (
|
22
22
|
Any,
|
@@ -1068,6 +1068,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1068
1068
|
parent_resource_template_name=resource_template_name,
|
1069
1069
|
parent_saas_file_name=saas_file_name,
|
1070
1070
|
),
|
1071
|
+
soak_days=target.promotion.soak_days or 0,
|
1071
1072
|
)
|
1072
1073
|
return resources, html_url, target_promotion
|
1073
1074
|
|
@@ -1880,82 +1881,98 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1880
1881
|
# TODO: add environment.parameters to the include list!?!?
|
1881
1882
|
)
|
1882
1883
|
|
1883
|
-
def
|
1884
|
-
|
1885
|
-
If there were promotion sections in the participating saas files
|
1886
|
-
validate that the conditions are met."""
|
1884
|
+
def _validate_promotion(self, promotion: Promotion) -> bool:
|
1885
|
+
# Placing this check here to make mypy happy
|
1887
1886
|
if not (self.state and self._promotion_state):
|
1888
1887
|
raise Exception("state is not initialized")
|
1889
1888
|
|
1890
|
-
|
1891
|
-
|
1892
|
-
continue
|
1893
|
-
# validate that the commit sha being promoted
|
1894
|
-
# was successfully published to the subscribed channel(s)
|
1895
|
-
if promotion.subscribe:
|
1896
|
-
for channel in promotion.subscribe:
|
1897
|
-
config_hashes: set[str] = set()
|
1898
|
-
for target_uid in channel.publisher_uids:
|
1899
|
-
deployment = self._promotion_state.get_promotion_data(
|
1900
|
-
sha=promotion.commit_sha,
|
1901
|
-
channel=channel.name,
|
1902
|
-
target_uid=target_uid,
|
1903
|
-
local_lookup=False,
|
1904
|
-
)
|
1905
|
-
if not (deployment and deployment.success):
|
1906
|
-
logging.error(
|
1907
|
-
f"Commit {promotion.commit_sha} was not "
|
1908
|
-
+ f"published with success to channel {channel.name}"
|
1909
|
-
)
|
1910
|
-
return False
|
1911
|
-
if deployment.target_config_hash:
|
1912
|
-
config_hashes.add(deployment.target_config_hash)
|
1913
|
-
|
1914
|
-
# This code supports current saas targets that does
|
1915
|
-
# not have promotion_data yet
|
1916
|
-
if not config_hashes or not promotion.promotion_data:
|
1917
|
-
logging.info(
|
1918
|
-
"Promotion data is missing; rely on the success "
|
1919
|
-
"state only"
|
1920
|
-
)
|
1921
|
-
return True
|
1922
|
-
|
1923
|
-
# Validate the promotion_data section.
|
1924
|
-
# Just validate parent_saas_config hash
|
1925
|
-
# promotion_data type by now.
|
1926
|
-
parent_saas_config = None
|
1927
|
-
for pd in promotion.promotion_data:
|
1928
|
-
if pd.channel == channel.name:
|
1929
|
-
for data in pd.data or []:
|
1930
|
-
if isinstance(data, SaasParentSaasPromotion):
|
1931
|
-
parent_saas_config = data
|
1932
|
-
|
1933
|
-
# This section might not exist due to a manual MR.
|
1934
|
-
# Promotion shall continue if this data is missing.
|
1935
|
-
# The parent at the same ref has succeed if this code
|
1936
|
-
# is reached though.
|
1937
|
-
if not parent_saas_config:
|
1938
|
-
logging.info(
|
1939
|
-
"Parent Saas config missing on target "
|
1940
|
-
"rely on the success state only"
|
1941
|
-
)
|
1942
|
-
return True
|
1943
|
-
|
1944
|
-
# Validate that the state config_hash set by the parent
|
1945
|
-
# matches with the hash set in promotion_data
|
1946
|
-
if parent_saas_config.target_config_hash in config_hashes:
|
1947
|
-
return True
|
1889
|
+
if not promotion.subscribe:
|
1890
|
+
return True
|
1948
1891
|
|
1892
|
+
now = datetime.now(timezone.utc)
|
1893
|
+
passed_soak_days = timedelta(days=0)
|
1894
|
+
|
1895
|
+
for channel in promotion.subscribe:
|
1896
|
+
config_hashes: set[str] = set()
|
1897
|
+
for target_uid in channel.publisher_uids:
|
1898
|
+
deployment = self._promotion_state.get_promotion_data(
|
1899
|
+
sha=promotion.commit_sha,
|
1900
|
+
channel=channel.name,
|
1901
|
+
target_uid=target_uid,
|
1902
|
+
local_lookup=False,
|
1903
|
+
)
|
1904
|
+
if not (deployment and deployment.success):
|
1949
1905
|
logging.error(
|
1950
|
-
"
|
1951
|
-
"
|
1952
|
-
"Check if other MR exists for this target, "
|
1953
|
-
f"or update {parent_saas_config.target_config_hash} "
|
1954
|
-
f"to any in {config_hashes} for channel {channel.name}"
|
1906
|
+
f"Commit {promotion.commit_sha} was not "
|
1907
|
+
+ f"published with success to channel {channel.name}"
|
1955
1908
|
)
|
1956
1909
|
return False
|
1910
|
+
if check_in := deployment.check_in:
|
1911
|
+
passed_soak_days += now - datetime.fromisoformat(check_in)
|
1912
|
+
if deployment.target_config_hash:
|
1913
|
+
config_hashes.add(deployment.target_config_hash)
|
1914
|
+
|
1915
|
+
# This code supports current saas targets that does
|
1916
|
+
# not have promotion_data yet
|
1917
|
+
if not config_hashes or not promotion.promotion_data:
|
1918
|
+
logging.info(
|
1919
|
+
"Promotion data is missing; rely on the success " "state only"
|
1920
|
+
)
|
1921
|
+
continue
|
1922
|
+
|
1923
|
+
# Validate the promotion_data section.
|
1924
|
+
# Just validate parent_saas_config hash
|
1925
|
+
# promotion_data type by now.
|
1926
|
+
parent_saas_config = None
|
1927
|
+
for pd in promotion.promotion_data:
|
1928
|
+
if pd.channel == channel.name:
|
1929
|
+
for data in pd.data or []:
|
1930
|
+
if isinstance(data, SaasParentSaasPromotion):
|
1931
|
+
parent_saas_config = data
|
1932
|
+
|
1933
|
+
# This section might not exist due to a manual MR.
|
1934
|
+
# Promotion shall continue if this data is missing.
|
1935
|
+
# The parent at the same ref has succeed if this code
|
1936
|
+
# is reached though.
|
1937
|
+
if not parent_saas_config:
|
1938
|
+
logging.info(
|
1939
|
+
"Parent Saas config missing on target "
|
1940
|
+
"rely on the success state only"
|
1941
|
+
)
|
1942
|
+
continue
|
1943
|
+
|
1944
|
+
# Validate that the state config_hash set by the parent
|
1945
|
+
# matches with the hash set in promotion_data
|
1946
|
+
if parent_saas_config.target_config_hash in config_hashes:
|
1947
|
+
continue
|
1948
|
+
|
1949
|
+
logging.error(
|
1950
|
+
"Parent saas target has run with a newer "
|
1951
|
+
"configuration and the same commit (ref). "
|
1952
|
+
"Check if other MR exists for this target, "
|
1953
|
+
f"or update {parent_saas_config.target_config_hash} "
|
1954
|
+
f"to any in {config_hashes} for channel {channel.name}"
|
1955
|
+
)
|
1956
|
+
return False
|
1957
|
+
|
1958
|
+
if passed_soak_days < timedelta(days=promotion.soak_days):
|
1959
|
+
logging.error(
|
1960
|
+
f"SoakDays in publishers did not pass. So far accumulated soakDays is {passed_soak_days},"
|
1961
|
+
f"but we have a soakDays setting of {promotion.soak_days}. We cannot proceed with this promotion."
|
1962
|
+
)
|
1963
|
+
return False
|
1957
1964
|
return True
|
1958
1965
|
|
1966
|
+
def validate_promotions(self) -> bool:
|
1967
|
+
"""
|
1968
|
+
If there were promotion sections in the participating saas files
|
1969
|
+
validate that the conditions are met."""
|
1970
|
+
return all(
|
1971
|
+
self._validate_promotion(promotion)
|
1972
|
+
for promotion in self.promotions
|
1973
|
+
if promotion is not None
|
1974
|
+
)
|
1975
|
+
|
1959
1976
|
def publish_promotions(
|
1960
1977
|
self,
|
1961
1978
|
success: bool,
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc850.dist-info → qontract_reconcile-0.10.1rc852.dist-info}/top_level.txt
RENAMED
File without changes
|