qontract-reconcile 0.10.1rc877__py3-none-any.whl → 0.10.1rc879__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.1rc879
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=8JoysSi-lAjsdGKTK3YBTBPYy95B47guXzfp71JHh1I,16516
262
+ reconcile/gql_definitions/common/saas_target_namespaces.py,sha256=4VYP2VbwY8WVwtSFk2-jsUNhSmRD3X4FWKxetOKvmd0,2835
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=8kMXeD7u2bmdjn10zHmMJ80ScOhUp6KqSfWfjWZW-40,4001
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=C2vwkdjk2cccS14GML2ByG8WKcRP8wgTJfzwaEUKdtw,54603
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=UG3O5zuamNrP1bgbfhQAtNfN_8W9_iSv7UBESnR-mXE,9363
791
+ reconcile/utils/saasherder/models.py,sha256=iLQZGXMx4FYnK62MJKkVp1T32BpsZ88NOcU7RW0WyAk,5579
792
+ reconcile/utils/saasherder/saasherder.py,sha256=boCEscTR7Vu6Zjjd7pJIQDO5B_gYiOljwgBa_KDUVLM,88141
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.1rc879.dist-info/METADATA,sha256=4h4jN56Q5GugfmWXxRkczGqc0xzefD2xu-AnO3BlsBc,2273
841
+ qontract_reconcile-0.10.1rc879.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
842
+ qontract_reconcile-0.10.1rc879.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
843
+ qontract_reconcile-0.10.1rc879.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
844
+ qontract_reconcile-0.10.1rc879.dist-info/RECORD,,
@@ -82,6 +82,11 @@ fragment SaasTargetNamespace on Namespace_v1 {
82
82
  name
83
83
  email
84
84
  }
85
+ codeComponents {
86
+ url
87
+ blockedVersions
88
+ hotfixVersions
89
+ }
85
90
  }
86
91
  cluster {
87
92
  name
@@ -137,6 +142,11 @@ query SaasFiles {
137
142
  name
138
143
  email
139
144
  }
145
+ codeComponents {
146
+ url
147
+ blockedVersions
148
+ hotfixVersions
149
+ }
140
150
  }
141
151
  pipelinesProvider {
142
152
  name
@@ -320,11 +330,18 @@ class OwnerV1(ConfiguredBaseModel):
320
330
  email: str = Field(..., alias="email")
321
331
 
322
332
 
333
+ class AppCodeComponentsV1(ConfiguredBaseModel):
334
+ url: str = Field(..., alias="url")
335
+ blocked_versions: Optional[list[str]] = Field(..., alias="blockedVersions")
336
+ hotfix_versions: Optional[list[str]] = Field(..., alias="hotfixVersions")
337
+
338
+
323
339
  class AppV1(ConfiguredBaseModel):
324
340
  name: str = Field(..., alias="name")
325
341
  parent_app: Optional[AppV1_AppV1] = Field(..., alias="parentApp")
326
342
  self_service_roles: Optional[list[RoleV1]] = Field(..., alias="selfServiceRoles")
327
343
  service_owners: Optional[list[OwnerV1]] = Field(..., alias="serviceOwners")
344
+ code_components: Optional[list[AppCodeComponentsV1]] = Field(..., alias="codeComponents")
328
345
 
329
346
 
330
347
  class PipelinesProviderV1(ConfiguredBaseModel):
@@ -61,6 +61,11 @@ fragment SaasTargetNamespace on Namespace_v1 {
61
61
  name
62
62
  email
63
63
  }
64
+ codeComponents {
65
+ url
66
+ blockedVersions
67
+ hotfixVersions
68
+ }
64
69
  }
65
70
  cluster {
66
71
  name
@@ -52,12 +52,19 @@ 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
+ blocked_versions: Optional[list[str]] = Field(..., alias="blockedVersions")
58
+ hotfix_versions: Optional[list[str]] = Field(..., alias="hotfixVersions")
59
+
60
+
55
61
  class AppV1(ConfiguredBaseModel):
56
62
  name: str = Field(..., alias="name")
57
63
  parent_app: Optional[AppV1_AppV1] = Field(..., alias="parentApp")
58
64
  labels: Optional[Json] = Field(..., alias="labels")
59
65
  self_service_roles: Optional[list[RoleV1]] = Field(..., alias="selfServiceRoles")
60
66
  service_owners: Optional[list[OwnerV1]] = Field(..., alias="serviceOwners")
67
+ code_components: Optional[list[AppCodeComponentsV1]] = Field(..., alias="codeComponents")
61
68
 
62
69
 
63
70
  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,65 @@ class TestRemoveNoneAttributes(TestCase):
1350
1353
  self.assertEqual(res, expected)
1351
1354
 
1352
1355
 
1356
+ @pytest.mark.usefixtures("inject_gql_class_factory")
1357
+ class TestPromotionBlockedHoxfixVersions(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_blocked_hotfix_version_promotion_validity(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
+ self.saasherder.blocked_versions[code_component_url] = {hotfix_version}
1412
+ self.assertFalse(self.saasherder.validate_promotions())
1413
+
1414
+
1353
1415
  def test_render_templated_parameters(
1354
1416
  gql_class_factory: Callable[..., SaasFileInterface],
1355
1417
  ) -> 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,12 @@ class SaasServiceOwner(Protocol):
356
359
  email: str
357
360
 
358
361
 
362
+ class AppCodeComponent(Protocol):
363
+ url: str
364
+ blocked_versions: Optional[list[str]]
365
+ hotfix_versions: Optional[list[str]]
366
+
367
+
359
368
  SaasPipelinesProviders = Union[SaasPipelinesProviderTekton, SaasPipelinesProvider]
360
369
 
361
370
 
@@ -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,8 @@ 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.blocked_versions = self._collect_blocked_versions()
154
+ self.hotfix_versions = self._collect_hotfix_versions()
153
155
 
154
156
  # each namespace is in fact a target,
155
157
  # so we can use it to calculate.
@@ -1057,6 +1059,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1057
1059
  self._channel_map[sub] for sub in target.promotion.subscribe or []
1058
1060
  ]
1059
1061
  target_promotion = Promotion(
1062
+ url=url,
1060
1063
  auto=target.promotion.auto,
1061
1064
  publish=target.promotion.publish,
1062
1065
  subscribe=channels,
@@ -1098,6 +1101,22 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1098
1101
  channel_map[publish].publisher_uids.append(publisher_uid)
1099
1102
  return channel_map
1100
1103
 
1104
+ def _collect_blocked_versions(self) -> dict[str, set[str]]:
1105
+ blocked_versions: dict[str, set[str]] = {}
1106
+ for saas_file in self.saas_files:
1107
+ for cc in saas_file.app.code_components or []:
1108
+ for v in cc.blocked_versions or []:
1109
+ blocked_versions.setdefault(cc.url, set()).add(v)
1110
+ return blocked_versions
1111
+
1112
+ def _collect_hotfix_versions(self) -> dict[str, set[str]]:
1113
+ hotfix_versions: dict[str, set[str]] = {}
1114
+ for saas_file in self.saas_files:
1115
+ for cc in saas_file.app.code_components or []:
1116
+ for v in cc.hotfix_versions or []:
1117
+ hotfix_versions.setdefault(cc.url, set()).add(v)
1118
+ return hotfix_versions
1119
+
1101
1120
  @staticmethod
1102
1121
  def _collect_images(resource: Resource) -> set[str]:
1103
1122
  images = set()
@@ -1889,6 +1908,14 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1889
1908
  if not promotion.subscribe:
1890
1909
  return True
1891
1910
 
1911
+ if promotion.commit_sha in self.blocked_versions.get(promotion.url, set()):
1912
+ logging.error(f"Commit {promotion.commit_sha} is blocked!")
1913
+ return False
1914
+
1915
+ # hotfix must run before further gates are evaluated to override them
1916
+ if promotion.commit_sha in self.hotfix_versions.get(promotion.url, set()):
1917
+ return True
1918
+
1892
1919
  now = datetime.now(timezone.utc)
1893
1920
  passed_soak_days = timedelta(days=0)
1894
1921