qontract-reconcile 0.10.1rc877__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qontract-reconcile
3
- Version: 0.10.1rc877
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
@@ -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=0F1dXJo7B37S2i5RQSvISOQOBlu2U2ue1rRsK5SZ4VU,16005
262
- reconcile/gql_definitions/common/saas_target_namespaces.py,sha256=gcTU9jrsNq9-HX-oOkj-nEZKYFTRytDHLs4SpEs93aw,2755
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=y4hA73Vchi3I1UW94r7CmecZe6KoHkCMEVuoiAF2xnc,3660
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
@@ -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=QFX6JrPCpB9jS-K_VleIjTf6NaF8NDa4UwddXXo1yPk,52127
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=Tte-BAJ71FZF1J_ADay1UVIxLCJZcbefq4SRua4mn5w,9141
791
- reconcile/utils/saasherder/models.py,sha256=XiAb9pSmTxaNFa3XqNNfe1JxlGgTqsmd1nLi17iIV_g,5566
792
- reconcile/utils/saasherder/saasherder.py,sha256=kPYklRU9hPcdg573QQ0OoaXYG26t_v5COAz62QVPS3c,86883
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.1rc877.dist-info/METADATA,sha256=eGK-obD-y6Kqjiz2fI4n5OefUFpshM_OXDvIMLxVkxw,2273
841
- qontract_reconcile-0.10.1rc877.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
842
- qontract_reconcile-0.10.1rc877.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
843
- qontract_reconcile-0.10.1rc877.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
844
- qontract_reconcile-0.10.1rc877.dist-info/RECORD,,
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):
@@ -61,6 +61,10 @@ fragment SaasTargetNamespace on Namespace_v1 {
61
61
  name
62
62
  email
63
63
  }
64
+ codeComponents {
65
+ url
66
+ hotfixVersions
67
+ }
64
68
  }
65
69
  cluster {
66
70
  name
@@ -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):
@@ -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
 
@@ -175,6 +175,7 @@ class Channel(BaseModel):
175
175
  class Promotion(BaseModel):
176
176
  """Implementation of the SaasPromotion interface for saasherder and AutoPromoter."""
177
177
 
178
+ url: str
178
179
  commit_sha: str
179
180
  saas_file: str
180
181
  target_config_hash: str
@@ -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