qontract-reconcile 0.9.1rc235__py3-none-any.whl → 0.9.1rc236__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.
@@ -25,13 +25,11 @@ from reconcile.gql_definitions.common.saas_files import (
25
25
  SaasFileV2,
26
26
  SaasResourceTemplateTargetImageV1,
27
27
  SaasResourceTemplateTargetPromotionV1,
28
- SaasResourceTemplateTargetV2_SaasSecretParametersV1,
29
28
  SaasResourceTemplateV2,
30
29
  )
31
30
  from reconcile.utils.jjb_client import JJB
32
31
  from reconcile.utils.openshift_resource import ResourceInventory
33
32
  from reconcile.utils.saasherder import SaasHerder
34
- from reconcile.utils.saasherder.interfaces import SaasFile
35
33
  from reconcile.utils.saasherder.models import TriggerSpecMovingCommit
36
34
  from reconcile.utils.secret_reader import SecretReaderBase
37
35
 
@@ -939,119 +937,3 @@ class TestRemoveNoneAttributes(TestCase):
939
937
  expected: dict[Any, Any] = {}
940
938
  res = SaasHerder.remove_none_values(input)
941
939
  self.assertEqual(res, expected)
942
-
943
-
944
- def test_render_templated_parameters(
945
- gql_class_factory: Callable[..., SaasFile]
946
- ) -> None:
947
- saas_file = gql_class_factory(
948
- SaasFileV2,
949
- Fixtures("saasherder").get_anymarkup("saas-templated-params.gql.yml"),
950
- )
951
- SaasHerder.resolve_templated_parameters([saas_file])
952
- assert saas_file.resource_templates[0].targets[0].parameters == {
953
- "no-template": "v1",
954
- "ignore-go-template": "{{ .GO_PARAM }}-go",
955
- "template-param-1": "test-namespace-ns",
956
- "template-param-2": "appsres03ue1-cluster",
957
- }
958
- assert saas_file.resource_templates[0].targets[0].secret_parameters == [
959
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
960
- name="no-template",
961
- secret=dict(
962
- path="path/to/secret",
963
- field="secret_key",
964
- version=1,
965
- format=None,
966
- ),
967
- ),
968
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
969
- name="ignore-go-template",
970
- secret=dict(
971
- path="path/{{ .GO_PARAM }}/secret",
972
- field="{{ .GO_PARAM }}-secret_key",
973
- version=1,
974
- format=None,
975
- ),
976
- ),
977
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
978
- name="template-param-1",
979
- secret=dict(
980
- path="path/appsres03ue1/test-namespace/secret",
981
- field="secret_key",
982
- version=1,
983
- format=None,
984
- ),
985
- ),
986
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
987
- name="template-param-2",
988
- secret=dict(
989
- path="path/appsres03ue1/test-namespace/secret",
990
- field="App-SRE-stage-secret_key",
991
- version=1,
992
- format=None,
993
- ),
994
- ),
995
- ]
996
-
997
-
998
- def test_render_templated_parameters_in_init(
999
- gql_class_factory: Callable[..., SaasFile]
1000
- ) -> None:
1001
- saas_file = gql_class_factory(
1002
- SaasFileV2,
1003
- Fixtures("saasherder").get_anymarkup("saas-templated-params.gql.yml"),
1004
- )
1005
- SaasHerder(
1006
- [saas_file],
1007
- secret_reader=MockSecretReader(),
1008
- thread_pool_size=1,
1009
- integration="",
1010
- integration_version="",
1011
- hash_length=24,
1012
- repo_url="https://repo-url.com",
1013
- )
1014
- assert saas_file.resource_templates[0].targets[0].parameters == {
1015
- "no-template": "v1",
1016
- "ignore-go-template": "{{ .GO_PARAM }}-go",
1017
- "template-param-1": "test-namespace-ns",
1018
- "template-param-2": "appsres03ue1-cluster",
1019
- }
1020
- assert saas_file.resource_templates[0].targets[0].secret_parameters == [
1021
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1022
- name="no-template",
1023
- secret=dict(
1024
- path="path/to/secret",
1025
- field="secret_key",
1026
- version=1,
1027
- format=None,
1028
- ),
1029
- ),
1030
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1031
- name="ignore-go-template",
1032
- secret=dict(
1033
- path="path/{{ .GO_PARAM }}/secret",
1034
- field="{{ .GO_PARAM }}-secret_key",
1035
- version=1,
1036
- format=None,
1037
- ),
1038
- ),
1039
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1040
- name="template-param-1",
1041
- secret=dict(
1042
- path="path/appsres03ue1/test-namespace/secret",
1043
- field="secret_key",
1044
- version=1,
1045
- format=None,
1046
- ),
1047
- ),
1048
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1049
- name="template-param-2",
1050
- secret=dict(
1051
- path="path/appsres03ue1/test-namespace/secret",
1052
- field="App-SRE-stage-secret_key",
1053
- version=1,
1054
- format=None,
1055
- ),
1056
- ),
1057
- ]
@@ -2,7 +2,7 @@ from collections.abc import Callable
2
2
 
3
3
  import pytest
4
4
 
5
- from reconcile.typed_queries.saas_files import SaasFile
5
+ from reconcile.gql_definitions.common.saas_files import SaasFileV2
6
6
  from reconcile.utils.saasherder import SaasHerder
7
7
  from reconcile.utils.secret_reader import SecretReader
8
8
 
@@ -27,7 +27,7 @@ def test_saasherder_allowed_secret_paths(
27
27
  secret_reader: SecretReader,
28
28
  gql_class_factory: Callable[
29
29
  ...,
30
- SaasFile,
30
+ SaasFileV2,
31
31
  ],
32
32
  ):
33
33
  """
@@ -35,7 +35,7 @@ def test_saasherder_allowed_secret_paths(
35
35
  """
36
36
  saas_files = [
37
37
  gql_class_factory(
38
- SaasFile,
38
+ SaasFileV2,
39
39
  {
40
40
  "path": "path1",
41
41
  "name": "a1",
@@ -0,0 +1,35 @@
1
+ from typing import Any
2
+
3
+ import pytest
4
+
5
+ from reconcile.gql_definitions.common.pagerduty_instances import PagerDutyInstanceV1
6
+ from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
7
+ from reconcile.test.fixtures import Fixtures
8
+ from reconcile.typed_queries.pagerduty_instances import get_pagerduty_instances
9
+
10
+
11
+ @pytest.fixture
12
+ def fxt() -> Fixtures:
13
+ return Fixtures("typed_queries")
14
+
15
+
16
+ def test_get_pagerduty_instances(fxt: Fixtures) -> None:
17
+ def q(*args: Any, **kwargs: Any) -> dict[Any, Any]:
18
+ return fxt.get_anymarkup("pagerduty_instances.yml")
19
+
20
+ items = get_pagerduty_instances(q)
21
+ assert len(items) == 2
22
+ assert items == [
23
+ PagerDutyInstanceV1(
24
+ name="instance-1",
25
+ token=VaultSecret(
26
+ path="vault-path", field="token", version=None, format=None
27
+ ),
28
+ ),
29
+ PagerDutyInstanceV1(
30
+ name="instance-2",
31
+ token=VaultSecret(
32
+ path="vault-path2", field="token2", version=2, format="format"
33
+ ),
34
+ ),
35
+ ]
@@ -1,189 +1,14 @@
1
- import json
2
1
  from collections.abc import Callable
3
- from typing import (
4
- Any,
5
- Optional,
6
- Union,
7
- )
8
-
9
- from jsonpath_ng.exceptions import JsonPathParserError
10
- from jsonpath_ng.ext import parser
11
- from pydantic import (
12
- BaseModel,
13
- Extra,
14
- Field,
15
- Json,
16
- )
2
+ from typing import Optional
17
3
 
18
- from reconcile.gql_definitions.common.saas_files import (
19
- AppV1,
20
- ConfiguredBaseModel,
21
- DeployResourcesV1,
22
- PipelinesProviderTektonV1,
23
- PipelinesProviderV1,
24
- RoleV1,
25
- SaasFileAuthenticationV1,
26
- SaasResourceTemplateTargetImageV1,
27
- SaasResourceTemplateTargetNamespaceSelectorV1,
28
- SaasResourceTemplateTargetPromotionV1,
29
- SaasResourceTemplateTargetUpstreamV1,
30
- SaasResourceTemplateTargetV2,
31
- SaasResourceTemplateTargetV2_SaasSecretParametersV1,
32
- SaasResourceTemplateV2_SaasSecretParametersV1,
33
- SaasSecretParametersV1,
34
- SlackOutputV1,
35
- )
4
+ from reconcile.gql_definitions.common.saas_files import SaasFileV2
36
5
  from reconcile.gql_definitions.common.saas_files import query as saas_files_query
37
- from reconcile.gql_definitions.common.saas_target_namespaces import (
38
- query as namespaces_query,
39
- )
40
6
  from reconcile.gql_definitions.common.saasherder_settings import AppInterfaceSettingsV1
41
7
  from reconcile.gql_definitions.common.saasherder_settings import (
42
8
  query as saasherder_settings_query,
43
9
  )
44
- from reconcile.gql_definitions.fragments.saas_target_namespace import (
45
- SaasTargetNamespace,
46
- )
47
10
  from reconcile.utils import gql
48
- from reconcile.utils.exceptions import (
49
- AppInterfaceSettingsError,
50
- ParameterError,
51
- )
52
-
53
-
54
- class SaasResourceTemplateTarget(ConfiguredBaseModel):
55
- path: Optional[str] = Field(..., alias="path")
56
- name: Optional[str] = Field(..., alias="name")
57
- # the namespace must be required to fulfill the saas file schema (utils.saasherder.interface.SaasFile)
58
- namespace: SaasTargetNamespace = Field(..., alias="namespace")
59
- ref: str = Field(..., alias="ref")
60
- promotion: Optional[SaasResourceTemplateTargetPromotionV1] = Field(
61
- ..., alias="promotion"
62
- )
63
- parameters: Optional[Json] = Field(..., alias="parameters")
64
- secret_parameters: Optional[
65
- list[SaasResourceTemplateTargetV2_SaasSecretParametersV1]
66
- ] = Field(..., alias="secretParameters")
67
- upstream: Optional[SaasResourceTemplateTargetUpstreamV1] = Field(
68
- ..., alias="upstream"
69
- )
70
- image: Optional[SaasResourceTemplateTargetImageV1] = Field(..., alias="image")
71
- disable: Optional[bool] = Field(..., alias="disable")
72
- delete: Optional[bool] = Field(..., alias="delete")
73
-
74
- class Config:
75
- # ignore `namespaceSelector` and 'provider' fields from the GQL schema
76
- extra = Extra.ignore
77
-
78
-
79
- class SaasResourceTemplate(ConfiguredBaseModel):
80
- name: str = Field(..., alias="name")
81
- url: str = Field(..., alias="url")
82
- path: str = Field(..., alias="path")
83
- provider: Optional[str] = Field(..., alias="provider")
84
- hash_length: Optional[int] = Field(..., alias="hash_length")
85
- parameters: Optional[Json] = Field(..., alias="parameters")
86
- secret_parameters: Optional[
87
- list[SaasResourceTemplateV2_SaasSecretParametersV1]
88
- ] = Field(..., alias="secretParameters")
89
- targets: list[SaasResourceTemplateTarget] = Field(..., alias="targets")
90
-
91
-
92
- class SaasFile(ConfiguredBaseModel):
93
- path: str = Field(..., alias="path")
94
- name: str = Field(..., alias="name")
95
- app: AppV1 = Field(..., alias="app")
96
- pipelines_provider: Union[PipelinesProviderTektonV1, PipelinesProviderV1] = Field(
97
- ..., alias="pipelinesProvider"
98
- )
99
- deploy_resources: Optional[DeployResourcesV1] = Field(..., alias="deployResources")
100
- slack: Optional[SlackOutputV1] = Field(..., alias="slack")
101
- managed_resource_types: list[str] = Field(..., alias="managedResourceTypes")
102
- takeover: Optional[bool] = Field(..., alias="takeover")
103
- deprecated: Optional[bool] = Field(..., alias="deprecated")
104
- compare: Optional[bool] = Field(..., alias="compare")
105
- timeout: Optional[str] = Field(..., alias="timeout")
106
- publish_job_logs: Optional[bool] = Field(..., alias="publishJobLogs")
107
- cluster_admin: Optional[bool] = Field(..., alias="clusterAdmin")
108
- image_patterns: list[str] = Field(..., alias="imagePatterns")
109
- allowed_secret_parameter_paths: Optional[list[str]] = Field(
110
- ..., alias="allowedSecretParameterPaths"
111
- )
112
- use_channel_in_image_tag: Optional[bool] = Field(
113
- ..., alias="use_channel_in_image_tag"
114
- )
115
- authentication: Optional[SaasFileAuthenticationV1] = Field(
116
- ..., alias="authentication"
117
- )
118
- parameters: Optional[Json] = Field(..., alias="parameters")
119
- secret_parameters: Optional[list[SaasSecretParametersV1]] = Field(
120
- ..., alias="secretParameters"
121
- )
122
- resource_templates: list[SaasResourceTemplate] = Field(
123
- ..., alias="resourceTemplates"
124
- )
125
- self_service_roles: Optional[list[RoleV1]] = Field(..., alias="selfServiceRoles")
126
-
127
-
128
- def get_namespaces_by_selector(
129
- namespaces: list[SaasTargetNamespace],
130
- namespace_selector: SaasResourceTemplateTargetNamespaceSelectorV1,
131
- ) -> list[SaasTargetNamespace]:
132
- namespaces_as_dict = {"namespace": [export_model(n) for n in namespaces]}
133
- filtered_namespaces: dict[str, Any] = {}
134
-
135
- try:
136
- for include in namespace_selector.json_path_selectors.include:
137
- for match in parser.parse(include).find(namespaces_as_dict):
138
- ns = SaasTargetNamespace(**match.value)
139
- filtered_namespaces[f"{ns.cluster.name}-{ns.name}"] = ns
140
- except JsonPathParserError as e:
141
- raise ParameterError(
142
- f"Invalid jsonpath expression in namespaceSelector '{include}' :{e}"
143
- )
144
-
145
- try:
146
- for exclude in namespace_selector.json_path_selectors.exclude or []:
147
- for match in parser.parse(exclude).find(namespaces_as_dict):
148
- ns = SaasTargetNamespace(**match.value)
149
- filtered_namespaces.pop(f"{ns.cluster.name}-{ns.name}", None)
150
- except JsonPathParserError as e:
151
- raise ParameterError(
152
- f"Invalid jsonpath expression in namespaceSelector '{exclude}' :{e}"
153
- )
154
- return list(filtered_namespaces.values())
155
-
156
-
157
- def convert_parameters_to_json_string(root: dict[str, Any]) -> dict[str, Any]:
158
- """Find all parameter occurrences and convert them to a json string."""
159
- for key, value in root.items():
160
- if key == "parameters":
161
- root[key] = json.dumps(value)
162
- elif isinstance(value, dict):
163
- root[key] = convert_parameters_to_json_string(value)
164
- elif isinstance(value, list):
165
- root[key] = [
166
- convert_parameters_to_json_string(v) if isinstance(v, dict) else v
167
- for v in value
168
- ]
169
- return root
170
-
171
-
172
- def export_model(model: BaseModel) -> dict[str, Any]:
173
- return convert_parameters_to_json_string(model.dict(by_alias=True))
174
-
175
-
176
- def create_targets_for_namespace_selector(
177
- target: SaasResourceTemplateTargetV2,
178
- namespaces: list[SaasTargetNamespace],
179
- namespace_selector: SaasResourceTemplateTargetNamespaceSelectorV1,
180
- ) -> list[SaasResourceTemplateTargetV2]:
181
- targets = []
182
- for namespace in get_namespaces_by_selector(namespaces, namespace_selector):
183
- target_dict = export_model(target)
184
- target_dict["namespace"] = export_model(namespace)
185
- targets.append(SaasResourceTemplateTargetV2(**target_dict))
186
- return targets
11
+ from reconcile.utils.exceptions import AppInterfaceSettingsError
187
12
 
188
13
 
189
14
  def get_saas_files(
@@ -191,43 +16,11 @@ def get_saas_files(
191
16
  env_name: Optional[str] = None,
192
17
  app_name: Optional[str] = None,
193
18
  query_func: Optional[Callable] = None,
194
- namespaces: Optional[list[SaasTargetNamespace]] = None,
195
- ) -> list[SaasFile]:
19
+ ) -> list[SaasFileV2]:
196
20
  if not query_func:
197
21
  query_func = gql.get_api().query
198
22
  data = saas_files_query(query_func)
199
- saas_files: list[SaasFile] = []
200
- if not namespaces:
201
- namespaces = namespaces_query(query_func).namespaces or []
202
-
203
- # resolve namespaceSelectors to real namespaces
204
- for saas_file_gql in list(data.saas_files or []):
205
- for rt_gql in saas_file_gql.resource_templates:
206
- for target_gql in rt_gql.targets[:]:
207
- # either namespace or namespaceSelector must be set
208
- if target_gql.namespace and target_gql.namespace_selector:
209
- raise ParameterError(
210
- f"SaasFile {saas_file_gql.name}: namespace and namespaceSelector are mutually exclusive"
211
- )
212
- if not target_gql.provider:
213
- target_gql.provider = "static"
214
-
215
- if (
216
- target_gql.namespace_selector
217
- and not target_gql.provider == "dynamic"
218
- ):
219
- raise ParameterError(
220
- f"SaasFile {saas_file_gql.name}: namespaceSelector can only be used with 'provider: dynamic'"
221
- )
222
- if target_gql.namespace_selector and target_gql.provider == "dynamic":
223
- rt_gql.targets.remove(target_gql)
224
- rt_gql.targets += create_targets_for_namespace_selector(
225
- target_gql, namespaces, target_gql.namespace_selector
226
- )
227
- # convert SaasFileV2 (with optional resource_templates.targets.namespace field)
228
- # to SaasFile (with required resource_templates.targets.namespace field)
229
- saas_files.append(SaasFile(**export_model(saas_file_gql)))
230
-
23
+ saas_files = list(data.saas_files or [])
231
24
  if name is None and env_name is None and app_name is None:
232
25
  return saas_files
233
26
  if name == "" or env_name == "" or app_name == "":
@@ -204,7 +204,7 @@ class SaasResourceTemplateTargetNamespace(Protocol):
204
204
  self,
205
205
  *,
206
206
  by_alias: bool = False,
207
- include: Optional[Union[AbstractSetIntStr, MappingIntStrAny]] = None,
207
+ include: Union[AbstractSetIntStr, MappingIntStrAny],
208
208
  ) -> dict[str, Any]:
209
209
  ...
210
210
 
@@ -117,7 +117,6 @@ class SaasHerder: # pylint: disable=too-many-public-methods
117
117
  self.error_registered = False
118
118
  self.saas_files = saas_files
119
119
  self.repo_urls = self._collect_repo_urls()
120
- self.resolve_templated_parameters(self.saas_files)
121
120
  if validate:
122
121
  self._validate_saas_files()
123
122
  if not self.valid:
@@ -1875,32 +1874,3 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1875
1874
  subscribe_target_path_map[channel].add(target.path)
1876
1875
 
1877
1876
  return subscribe_saas_file_path_map, subscribe_target_path_map
1878
-
1879
- @staticmethod
1880
- def resolve_templated_parameters(saas_files: Iterable[SaasFile]) -> None:
1881
- """Resolve templated target parameters in saas files."""
1882
- from reconcile.openshift_resources_base import (
1883
- compile_jinja2_template, # avoid circular import
1884
- )
1885
-
1886
- for saas_file in saas_files:
1887
- for rt in saas_file.resource_templates:
1888
- for target in rt.targets:
1889
- template_vars = {
1890
- "resource": {"namespace": target.namespace.dict(by_alias=True)}
1891
- }
1892
- if target.parameters:
1893
- for param in target.parameters:
1894
- if not isinstance(target.parameters[param], str):
1895
- continue
1896
- target.parameters[param] = compile_jinja2_template(
1897
- target.parameters[param], extra_curly=True
1898
- ).render(template_vars)
1899
- if target.secret_parameters:
1900
- for secret_param in target.secret_parameters:
1901
- secret_param.secret.field = compile_jinja2_template(
1902
- secret_param.secret.field, extra_curly=True
1903
- ).render(template_vars)
1904
- secret_param.secret.path = compile_jinja2_template(
1905
- secret_param.secret.path, extra_curly=True
1906
- ).render(template_vars)
@@ -29,10 +29,6 @@ from reconcile.jenkins_job_builder import (
29
29
  get_openshift_saas_deploy_job_name,
30
30
  init_jjb,
31
31
  )
32
- from reconcile.typed_queries.saas_files import (
33
- export_model,
34
- get_saas_files,
35
- )
36
32
  from reconcile.utils.jjb_client import JJB
37
33
  from reconcile.utils.mr import CreateAppInterfaceReporter
38
34
  from reconcile.utils.runtime.environment import init_env
@@ -231,7 +227,7 @@ def get_apps_data(date, month_delta=1, thread_pool_size=10):
231
227
  secret_reader = SecretReader(settings)
232
228
 
233
229
  apps = queries.get_apps()
234
- saas_files = [export_model(saas_file) for saas_file in get_saas_files()]
230
+ saas_files = queries.get_saas_files()
235
231
  jjb: JJB = init_jjb(secret_reader)
236
232
  jenkins_map = jenkins_base.get_jenkins_map()
237
233
  time_limit = date - relativedelta(months=month_delta)