qontract-reconcile 0.10.1rc885__py3-none-any.whl → 0.10.1rc886__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.1rc885
3
+ Version: 0.10.1rc886
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
@@ -810,7 +810,7 @@ tools/app_interface_metrics_exporter.py,sha256=zkwkxdAUAxjdc-pzx2_oJXG25fo0Fnyd5
810
810
  tools/app_interface_reporter.py,sha256=uy9eRHf6EdvD8ZY2WYdroGXm18DOdnqVZyxaWN3Bm_0,17724
811
811
  tools/glitchtip_access_reporter.py,sha256=oPBnk_YoDuljU3v0FaChzOwwnk4vap1xEE67QEjzdqs,2948
812
812
  tools/glitchtip_access_revalidation.py,sha256=8kbBJk04mkq28kWoRDDkfCGIF3GRg3pJrFAh1sW0dbk,2821
813
- tools/qontract_cli.py,sha256=xl_264MCbBAV-_YgZeBTVHnWPSagIzgbHNoQYEKWcFg,120361
813
+ tools/qontract_cli.py,sha256=PYFiVIc37qFOl3UxXt04o-V50Leu37pAFndTRMu_WBs,121059
814
814
  tools/sd_app_sre_alert_report.py,sha256=e9vAdyenUz2f5c8-z-5WY0wv-SJ9aePKDH2r4IwB6pc,5063
815
815
  tools/template_validation.py,sha256=qpKYaTgk0GOPGa2Ct5_5sKdwIHtCAKIBGzsMPuJU5fw,3371
816
816
  tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -830,16 +830,19 @@ tools/saas_metrics_exporter/commit_distance/__init__.py,sha256=47DEQpj8HBSa-_TIm
830
830
  tools/saas_metrics_exporter/commit_distance/channel.py,sha256=XEAh3eL8TmgMe7V2BsyxuXYWgvBBVdSJETd6Ec7cI04,2171
831
831
  tools/saas_metrics_exporter/commit_distance/commit_distance.py,sha256=pUWaZfZf0TYOwAW0gDdU8cYgTR586J_8S_DWxqHMWNA,3116
832
832
  tools/saas_metrics_exporter/commit_distance/metrics.py,sha256=5-y6n-sGACAS3eJ5ndY-2BFxcd0fxLfhvZmmBHu4JuA,426
833
+ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
834
+ tools/saas_promotion_state/saas_promotion_state.py,sha256=_jP9E8-VcWho6FIOGdcjNN6uvMVhpdXOMHw59qNnEmE,2855
833
835
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
834
836
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
835
837
  tools/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
836
- tools/test/conftest.py,sha256=YD8rdUD3XJAV_TvdzlhXkImaMQ3CU6m-8eYRkbc9qpg,993
838
+ tools/test/conftest.py,sha256=YLtiauk_StNFE-lirLnfG_BpJmlB2NGMZISE9A4zwvk,2421
837
839
  tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvftCWEEf-g1mfXOtgCog-g,1271
838
840
  tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jrss,4941
841
+ tools/test/test_saas_promotion_state.py,sha256=48Qe5UA5WTI5NVgL7Nz0TSS77osetcijfHNCNdsHfSI,2726
839
842
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
840
843
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
841
- qontract_reconcile-0.10.1rc885.dist-info/METADATA,sha256=nIpGToZHpAippnwf9bsAGA-Yr4rK5f_Cy_dpDPR8Ook,2273
842
- qontract_reconcile-0.10.1rc885.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
843
- qontract_reconcile-0.10.1rc885.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
844
- qontract_reconcile-0.10.1rc885.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
845
- qontract_reconcile-0.10.1rc885.dist-info/RECORD,,
844
+ qontract_reconcile-0.10.1rc886.dist-info/METADATA,sha256=4g0A5WsMn09DnEGihlWR7RXjtoeEjfpaBn6La8GQvmQ,2273
845
+ qontract_reconcile-0.10.1rc886.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
846
+ qontract_reconcile-0.10.1rc886.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
847
+ qontract_reconcile-0.10.1rc886.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
848
+ qontract_reconcile-0.10.1rc886.dist-info/RECORD,,
tools/qontract_cli.py CHANGED
@@ -3658,6 +3658,25 @@ def gpg_encrypt(
3658
3658
  ).execute()
3659
3659
 
3660
3660
 
3661
+ @root.command()
3662
+ @click.option("--channel", help="the channel that state is part of")
3663
+ @click.option("--sha", help="the commit sha we want state for")
3664
+ @environ(["APP_INTERFACE_STATE_BUCKET"])
3665
+ def get_promotion_state(channel: str, sha: str):
3666
+ from tools.saas_promotion_state.saas_promotion_state import (
3667
+ SaasPromotionState,
3668
+ )
3669
+
3670
+ promotion_state = SaasPromotionState.create(promotion_state=None, saas_files=None)
3671
+ for publisher_id, state in promotion_state.get(channel=channel, sha=sha).items():
3672
+ print()
3673
+ if not state:
3674
+ print(f"No state found for {publisher_id=}")
3675
+ else:
3676
+ print(f"State for {publisher_id=}:")
3677
+ print(state)
3678
+
3679
+
3661
3680
  @root.command()
3662
3681
  @click.option("--change-type-name")
3663
3682
  @click.option("--role-name")
File without changes
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+
5
+ from reconcile.openshift_saas_deploy import (
6
+ QONTRACT_INTEGRATION as OPENSHIFT_SAAS_DEPLOY,
7
+ )
8
+ from reconcile.typed_queries.app_interface_vault_settings import (
9
+ get_app_interface_vault_settings,
10
+ )
11
+ from reconcile.typed_queries.saas_files import SaasFile, get_saas_files
12
+ from reconcile.utils.promotion_state import PromotionData, PromotionState
13
+ from reconcile.utils.secret_reader import create_secret_reader
14
+ from reconcile.utils.state import init_state
15
+
16
+
17
+ class SaasPromotionState:
18
+ def __init__(
19
+ self, promotion_state: PromotionState, saas_files: Iterable[SaasFile]
20
+ ) -> None:
21
+ self._promotion_state = promotion_state
22
+ self._saas_files = saas_files
23
+
24
+ def _publisher_ids_for_channel(
25
+ self, channel: str, saas_files: Iterable[SaasFile]
26
+ ) -> list[str]:
27
+ publisher_uids: list[str] = []
28
+ for saas_file in saas_files:
29
+ for resource_template in saas_file.resource_templates:
30
+ for target in resource_template.targets:
31
+ if not target.promotion:
32
+ continue
33
+ for publish_channel in target.promotion.publish or []:
34
+ if publish_channel == channel:
35
+ publisher_uids.append(
36
+ target.uid(
37
+ parent_saas_file_name=saas_file.name,
38
+ parent_resource_template_name=resource_template.name,
39
+ )
40
+ )
41
+ return publisher_uids
42
+
43
+ def get(self, channel: str, sha: str) -> dict[str, PromotionData | None]:
44
+ return {
45
+ publisher_id: self._promotion_state.get_promotion_data(
46
+ sha=sha,
47
+ channel=channel,
48
+ use_cache=False,
49
+ target_uid=publisher_id,
50
+ pre_check_sha_exists=False,
51
+ )
52
+ for publisher_id in self._publisher_ids_for_channel(
53
+ channel=channel, saas_files=self._saas_files
54
+ )
55
+ }
56
+
57
+ @staticmethod
58
+ def create(
59
+ promotion_state: PromotionState | None, saas_files: Iterable[SaasFile] | None
60
+ ) -> SaasPromotionState:
61
+ if not promotion_state:
62
+ vault_settings = get_app_interface_vault_settings()
63
+ secret_reader = create_secret_reader(use_vault=vault_settings.vault)
64
+ saas_deploy_state = init_state(
65
+ integration=OPENSHIFT_SAAS_DEPLOY, secret_reader=secret_reader
66
+ )
67
+ promotion_state = PromotionState(state=saas_deploy_state)
68
+ if not saas_files:
69
+ saas_files = get_saas_files()
70
+ return SaasPromotionState(
71
+ promotion_state=promotion_state, saas_files=saas_files
72
+ )
tools/test/conftest.py CHANGED
@@ -1,13 +1,17 @@
1
1
  from collections.abc import (
2
2
  Callable,
3
+ Iterable,
4
+ Mapping,
3
5
  MutableMapping,
4
6
  )
7
+ from pathlib import Path
5
8
  from typing import Any
6
9
 
7
10
  import pytest
8
11
  from pydantic import BaseModel
9
12
  from pydantic.error_wrappers import ValidationError
10
13
 
14
+ from reconcile.typed_queries.saas_files import SaasFile
11
15
  from reconcile.utils.models import data_default_none
12
16
 
13
17
 
@@ -15,6 +19,44 @@ class GQLClassFactoryError(Exception):
15
19
  pass
16
20
 
17
21
 
22
+ @pytest.fixture
23
+ def saas_files_builder(
24
+ gql_class_factory: Callable[[type[SaasFile], Mapping], SaasFile],
25
+ ) -> Callable[[Iterable[MutableMapping]], list[SaasFile]]:
26
+ def builder(data: Iterable[MutableMapping]) -> list[SaasFile]:
27
+ for d in data:
28
+ if "app" not in d:
29
+ d["app"] = {}
30
+ if "pipelinesProvider" not in d:
31
+ d["pipelinesProvider"] = {}
32
+ if "managedResourceTypes" not in d:
33
+ d["managedResourceTypes"] = []
34
+ if "imagePatterns" not in d:
35
+ d["imagePatterns"] = []
36
+ for rt in d.get("resourceTemplates", []):
37
+ for t in rt.get("targets", []):
38
+ ns = t["namespace"]
39
+ if "name" not in ns:
40
+ ns["name"] = "some_name"
41
+ if "environment" not in ns:
42
+ ns["environment"] = {}
43
+ if "app" not in ns:
44
+ ns["app"] = {}
45
+ if "cluster" not in ns:
46
+ ns["cluster"] = {}
47
+ return [gql_class_factory(SaasFile, d) for d in data]
48
+
49
+ return builder
50
+
51
+
52
+ @pytest.fixture
53
+ def fx() -> Callable:
54
+ def _fx(name: str) -> str:
55
+ return (Path(__file__).parent / "fixtures" / name).read_text()
56
+
57
+ return _fx
58
+
59
+
18
60
  @pytest.fixture
19
61
  def gql_class_factory() -> (
20
62
  Callable[
@@ -0,0 +1,86 @@
1
+ from collections.abc import (
2
+ Callable,
3
+ Iterable,
4
+ Mapping,
5
+ )
6
+ from unittest.mock import (
7
+ create_autospec,
8
+ )
9
+
10
+ from reconcile.typed_queries.saas_files import SaasFile
11
+ from reconcile.utils.promotion_state import PromotionData, PromotionState
12
+ from tools.saas_promotion_state.saas_promotion_state import (
13
+ SaasPromotionState,
14
+ )
15
+
16
+
17
+ def test_saas_promotion_state(
18
+ saas_files_builder: Callable[[Iterable[Mapping]], list[SaasFile]],
19
+ ) -> None:
20
+ saas_files = saas_files_builder([
21
+ {
22
+ "path": "/saas1.yml",
23
+ "name": "saas_1",
24
+ "resourceTemplates": [
25
+ {
26
+ "name": "template_1",
27
+ "url": "repo1/url",
28
+ "targets": [
29
+ {
30
+ "ref": "main",
31
+ "namespace": {"path": "/namespace1.yml"},
32
+ "promotion": {
33
+ "publish": ["channel-a"],
34
+ },
35
+ }
36
+ ],
37
+ }
38
+ ],
39
+ },
40
+ {
41
+ "path": "/saas2.yml",
42
+ "name": "saas_2",
43
+ "resourceTemplates": [
44
+ {
45
+ "name": "template_2",
46
+ "url": "repo2/url",
47
+ "targets": [
48
+ {
49
+ "ref": "main",
50
+ "namespace": {"path": "/namespace2.yml"},
51
+ "promotion": {
52
+ "publish": ["channel-b"],
53
+ "subscribe": ["channel-a"],
54
+ },
55
+ },
56
+ {
57
+ "ref": "main",
58
+ "namespace": {"path": "/namespace3.yml"},
59
+ },
60
+ ],
61
+ }
62
+ ],
63
+ },
64
+ ])
65
+
66
+ expected = PromotionData(
67
+ check_in="test1",
68
+ saas_file="test2",
69
+ success=True,
70
+ target_config_hash="test3",
71
+ )
72
+ promotion_state = create_autospec(spec=PromotionState)
73
+ promotion_state.get_promotion_data.return_value = expected
74
+ saas_promotion_state = SaasPromotionState.create(
75
+ promotion_state=promotion_state, saas_files=saas_files
76
+ )
77
+ result = saas_promotion_state.get(channel="channel-a", sha="main")
78
+
79
+ assert result == {"616af45d7fad7f4eea8d52b8b5e8a058cef82ab0": expected}
80
+ promotion_state.get_promotion_data.assert_called_once_with(
81
+ sha="main",
82
+ channel="channel-a",
83
+ use_cache=False,
84
+ target_uid="616af45d7fad7f4eea8d52b8b5e8a058cef82ab0",
85
+ pre_check_sha_exists=False,
86
+ )