qontract-reconcile 0.10.2.dev292__py3-none-any.whl → 0.10.2.dev293__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.2.dev292.dist-info → qontract_reconcile-0.10.2.dev293.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev292.dist-info → qontract_reconcile-0.10.2.dev293.dist-info}/RECORD +9 -9
- reconcile/gql_definitions/introspection.json +36 -0
- reconcile/gql_definitions/templating/template_collection.py +2 -0
- reconcile/gql_definitions/templating/templates.py +2 -0
- reconcile/templating/lib/rendering.py +75 -54
- reconcile/templating/renderer.py +6 -3
- {qontract_reconcile-0.10.2.dev292.dist-info → qontract_reconcile-0.10.2.dev293.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev292.dist-info → qontract_reconcile-0.10.2.dev293.dist-info}/entry_points.txt +0 -0
{qontract_reconcile-0.10.2.dev292.dist-info → qontract_reconcile-0.10.2.dev293.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.2.
|
3
|
+
Version: 0.10.2.dev293
|
4
4
|
Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
|
5
5
|
Project-URL: homepage, https://github.com/app-sre/qontract-reconcile
|
6
6
|
Project-URL: repository, https://github.com/app-sre/qontract-reconcile
|
{qontract_reconcile-0.10.2.dev292.dist-info → qontract_reconcile-0.10.2.dev293.dist-info}/RECORD
RENAMED
@@ -213,7 +213,7 @@ reconcile/glitchtip_project_alerts/integration.py,sha256=d3PMy-mQSbSZdIGAVaZCA2U
|
|
213
213
|
reconcile/glitchtip_project_dsn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
214
214
|
reconcile/glitchtip_project_dsn/integration.py,sha256=3GgcqUM6hWhLpo9Yx5Xr9vrdexF-WNevVCNL9bJ0Upc,8162
|
215
215
|
reconcile/gql_definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
216
|
-
reconcile/gql_definitions/introspection.json,sha256=
|
216
|
+
reconcile/gql_definitions/introspection.json,sha256=CQAoUjEbdjRlHyYeLjs1PTMiP_RQrPzD0yCj08aYawA,2348381
|
217
217
|
reconcile/gql_definitions/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
218
218
|
reconcile/gql_definitions/acs/acs_instances.py,sha256=L91WW9LbhJbBSrECqShQpFtjoBOsmNIYLRpMbx1io5o,2181
|
219
219
|
reconcile/gql_definitions/acs/acs_policies.py,sha256=Ygpfl2-VkYLSlJvHgp_dJBfb66K_Rwfdfpsa18w1v1s,4338
|
@@ -409,8 +409,8 @@ reconcile/gql_definitions/status_board/status_board.py,sha256=BYq-1g_-AjNKHIKmY2
|
|
409
409
|
reconcile/gql_definitions/statuspage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
410
410
|
reconcile/gql_definitions/statuspage/statuspages.py,sha256=CTRzjiR9k41LqlkgyoNHwC2JERsoD_Run_aK7jw_Ono,5299
|
411
411
|
reconcile/gql_definitions/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
412
|
-
reconcile/gql_definitions/templating/template_collection.py,sha256=
|
413
|
-
reconcile/gql_definitions/templating/templates.py,sha256=
|
412
|
+
reconcile/gql_definitions/templating/template_collection.py,sha256=alQA1qLhL0nOFcnA0O2Z7HCuuAviZXM66t6yeur9XWo,4123
|
413
|
+
reconcile/gql_definitions/templating/templates.py,sha256=rQ14gs5dMttxVuW5q26p23Y8c5vCNh8dSec9ucTGlKc,3372
|
414
414
|
reconcile/gql_definitions/terraform_cloudflare_dns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
415
415
|
reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py,sha256=eyGX9HcTF6MZbOYZ6Kl6Mg3k6nJTUtwqs9gDxBP_8Dk,1920
|
416
416
|
reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py,sha256=dBQ2tyAp-eRZs59mguaTc6-x67JUoSxtZ8mOjbRqDuc,5832
|
@@ -507,12 +507,12 @@ reconcile/templates/jira-checkpoint-missinginfo.j2,sha256=c_Vvg-lEENsB3tgxm9B6Y9
|
|
507
507
|
reconcile/templates/rosa-classic-cluster-creation.sh.j2,sha256=M-nzp-GtkQNRe8rdoDAndSKJSJhcJwNFKeql-JP2W7M,2094
|
508
508
|
reconcile/templates/rosa-hcp-cluster-creation.sh.j2,sha256=eeT7xUhmz7Q_HtJOQALF5snZE8cUPGkIh6WUlcBHhhs,2349
|
509
509
|
reconcile/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
510
|
-
reconcile/templating/renderer.py,sha256=
|
510
|
+
reconcile/templating/renderer.py,sha256=YVKhk1piAnk3faT81oeZeMHnFV78Mt-_wgtIC693vtE,14907
|
511
511
|
reconcile/templating/validator.py,sha256=5f9f35PCHOOdjb7KZquL2YdabyuAUokPDa4xutSEHIQ,5360
|
512
512
|
reconcile/templating/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
513
513
|
reconcile/templating/lib/merge_request_manager.py,sha256=XwpOR4rVS9ZiJ_Mn8qfCXPZ7CRLMGSJgeIkqD-8Jhgc,5228
|
514
514
|
reconcile/templating/lib/model.py,sha256=YVUIXuPny3_kpFgBMSud8q_ndY5o882wKiX0l0A14L4,481
|
515
|
-
reconcile/templating/lib/rendering.py,sha256=
|
515
|
+
reconcile/templating/lib/rendering.py,sha256=qjs7WAVSvWH4edbBk6Aaql4ZT_OG9W7L8qi_YeJkHVI,6973
|
516
516
|
reconcile/terraform_init/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
517
517
|
reconcile/terraform_init/integration.py,sha256=pPi4YAjbEE8vDaaRizGf-d-PewqqSJmjcLgAsWFS7G0,6236
|
518
518
|
reconcile/terraform_init/merge_request.py,sha256=3CYtgSd7Q9zjKg4wsDz437EPCRfGeZZ8fZ0Y-ChKXJY,1475
|
@@ -796,7 +796,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
796
796
|
tools/saas_promotion_state/saas_promotion_state.py,sha256=uQv2QJAmUXP1g2GPIH30WTlvL9soY6m9lefpZEVDM5w,3965
|
797
797
|
tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
|
798
798
|
tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
|
799
|
-
qontract_reconcile-0.10.2.
|
800
|
-
qontract_reconcile-0.10.2.
|
801
|
-
qontract_reconcile-0.10.2.
|
802
|
-
qontract_reconcile-0.10.2.
|
799
|
+
qontract_reconcile-0.10.2.dev293.dist-info/METADATA,sha256=1dwm4wJoCGKDdjglqMJw3fdWAvuquaPeE2BIpH7wkUc,24916
|
800
|
+
qontract_reconcile-0.10.2.dev293.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
801
|
+
qontract_reconcile-0.10.2.dev293.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
|
802
|
+
qontract_reconcile-0.10.2.dev293.dist-info/RECORD,,
|
@@ -6451,6 +6451,18 @@
|
|
6451
6451
|
"isDeprecated": false,
|
6452
6452
|
"deprecationReason": null
|
6453
6453
|
},
|
6454
|
+
{
|
6455
|
+
"name": "externalOrgId",
|
6456
|
+
"description": null,
|
6457
|
+
"args": [],
|
6458
|
+
"type": {
|
6459
|
+
"kind": "SCALAR",
|
6460
|
+
"name": "String",
|
6461
|
+
"ofType": null
|
6462
|
+
},
|
6463
|
+
"isDeprecated": false,
|
6464
|
+
"deprecationReason": null
|
6465
|
+
},
|
6454
6466
|
{
|
6455
6467
|
"name": "environment",
|
6456
6468
|
"description": null,
|
@@ -34178,6 +34190,18 @@
|
|
34178
34190
|
"isDeprecated": false,
|
34179
34191
|
"deprecationReason": null
|
34180
34192
|
},
|
34193
|
+
{
|
34194
|
+
"name": "overwrite",
|
34195
|
+
"description": null,
|
34196
|
+
"args": [],
|
34197
|
+
"type": {
|
34198
|
+
"kind": "SCALAR",
|
34199
|
+
"name": "Boolean",
|
34200
|
+
"ofType": null
|
34201
|
+
},
|
34202
|
+
"isDeprecated": false,
|
34203
|
+
"deprecationReason": null
|
34204
|
+
},
|
34181
34205
|
{
|
34182
34206
|
"name": "patch",
|
34183
34207
|
"description": null,
|
@@ -41014,6 +41038,18 @@
|
|
41014
41038
|
},
|
41015
41039
|
"isDeprecated": false,
|
41016
41040
|
"deprecationReason": null
|
41041
|
+
},
|
41042
|
+
{
|
41043
|
+
"name": "promtool_version",
|
41044
|
+
"description": null,
|
41045
|
+
"args": [],
|
41046
|
+
"type": {
|
41047
|
+
"kind": "SCALAR",
|
41048
|
+
"name": "String",
|
41049
|
+
"ofType": null
|
41050
|
+
},
|
41051
|
+
"isDeprecated": false,
|
41052
|
+
"deprecationReason": null
|
41017
41053
|
}
|
41018
41054
|
],
|
41019
41055
|
"inputFields": null,
|
@@ -40,6 +40,7 @@ query TemplateCollection_v1($name: String) {
|
|
40
40
|
autoApproved
|
41
41
|
condition
|
42
42
|
targetPath
|
43
|
+
overwrite
|
43
44
|
patch {
|
44
45
|
path
|
45
46
|
identifier
|
@@ -92,6 +93,7 @@ class TemplateV1(ConfiguredBaseModel):
|
|
92
93
|
auto_approved: Optional[bool] = Field(..., alias="autoApproved")
|
93
94
|
condition: Optional[str] = Field(..., alias="condition")
|
94
95
|
target_path: str = Field(..., alias="targetPath")
|
96
|
+
overwrite: Optional[bool] = Field(..., alias="overwrite")
|
95
97
|
patch: Optional[TemplatePatchV1] = Field(..., alias="patch")
|
96
98
|
template: str = Field(..., alias="template")
|
97
99
|
template_render_options: Optional[TemplateRenderOptionsV1] = Field(..., alias="templateRenderOptions")
|
@@ -24,6 +24,7 @@ query Templatev1 {
|
|
24
24
|
name
|
25
25
|
autoApproved
|
26
26
|
condition
|
27
|
+
overwrite
|
27
28
|
patch {
|
28
29
|
path
|
29
30
|
identifier
|
@@ -78,6 +79,7 @@ class TemplateV1(ConfiguredBaseModel):
|
|
78
79
|
name: str = Field(..., alias="name")
|
79
80
|
auto_approved: Optional[bool] = Field(..., alias="autoApproved")
|
80
81
|
condition: Optional[str] = Field(..., alias="condition")
|
82
|
+
overwrite: Optional[bool] = Field(..., alias="overwrite")
|
81
83
|
patch: Optional[TemplatePatchV1] = Field(..., alias="patch")
|
82
84
|
target_path: str = Field(..., alias="targetPath")
|
83
85
|
template: str = Field(..., alias="template")
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
from abc import ABC, abstractmethod
|
3
|
+
from functools import cached_property
|
3
4
|
from io import StringIO
|
4
5
|
from typing import Any, Protocol
|
5
6
|
|
@@ -33,6 +34,7 @@ class Template(Protocol):
|
|
33
34
|
condition: str | None
|
34
35
|
target_path: str
|
35
36
|
template: str
|
37
|
+
overwrite: bool | None
|
36
38
|
|
37
39
|
def dict(self) -> dict[str, str]: ...
|
38
40
|
|
@@ -77,14 +79,28 @@ class Renderer(ABC):
|
|
77
79
|
"""
|
78
80
|
pass
|
79
81
|
|
82
|
+
@abstractmethod
|
83
|
+
def target_exist(self) -> bool:
|
84
|
+
"""
|
85
|
+
if target (file or patch block) already exists.
|
86
|
+
"""
|
87
|
+
pass
|
88
|
+
|
80
89
|
def render_target_path(self) -> str:
|
81
90
|
return self._render_template(self.template.target_path).strip()
|
82
91
|
|
83
92
|
def render_condition(self) -> bool:
|
84
|
-
|
93
|
+
if self._render_template(self.template.condition or "True") != "True":
|
94
|
+
return False
|
95
|
+
if self.template.overwrite:
|
96
|
+
return True
|
97
|
+
return not self.target_exist()
|
85
98
|
|
86
99
|
|
87
100
|
class FullRenderer(Renderer):
|
101
|
+
def target_exist(self) -> bool:
|
102
|
+
return self.data.current is not None
|
103
|
+
|
88
104
|
def render_output(self) -> str:
|
89
105
|
"""
|
90
106
|
Take the variables from Template Data and render the template with it.
|
@@ -95,6 +111,12 @@ class FullRenderer(Renderer):
|
|
95
111
|
|
96
112
|
|
97
113
|
class PatchRenderer(Renderer):
|
114
|
+
def target_exist(self) -> bool:
|
115
|
+
if isinstance(self._matched_value, list):
|
116
|
+
dta_identifier = self._get_identifier(self._data_to_add)
|
117
|
+
return self._find_index(self._matched_value, dta_identifier) is not None
|
118
|
+
return True
|
119
|
+
|
98
120
|
def render_output(self) -> str:
|
99
121
|
"""
|
100
122
|
Takes the variables from Template Data and render the template with it.
|
@@ -105,70 +127,69 @@ class PatchRenderer(Renderer):
|
|
105
127
|
|
106
128
|
This method returns the entire file as a string.
|
107
129
|
"""
|
108
|
-
if self.
|
109
|
-
|
110
|
-
|
111
|
-
p = parse_jsonpath(self._render_template(self.template.patch.path))
|
112
|
-
|
113
|
-
matched_values = [match.value for match in p.find(self.data.current)]
|
114
|
-
|
115
|
-
if len(matched_values) != 1:
|
116
|
-
raise ValueError(
|
117
|
-
f"Expected exactly one match for {self.template.patch.path}, got {len(matched_values)}"
|
118
|
-
)
|
119
|
-
matched_value = matched_values[0]
|
120
|
-
|
121
|
-
data_to_add = self.ruamel_instance.load(
|
122
|
-
self._render_template(self.template.template)
|
123
|
-
)
|
124
|
-
|
125
|
-
if isinstance(matched_value, list):
|
126
|
-
|
127
|
-
def get_identifier(data: dict[str, Any]) -> Any:
|
128
|
-
assert self.template.patch is not None # mypy
|
129
|
-
if not self.template.patch.identifier:
|
130
|
-
raise ValueError(
|
131
|
-
f"Expected identifier in patch for list at {self.template}"
|
132
|
-
)
|
133
|
-
if self.template.patch.identifier.startswith(
|
134
|
-
"$"
|
135
|
-
) and not self.template.patch.identifier.startswith("$ref"):
|
136
|
-
# jsonpath and list of strings support
|
137
|
-
if matches := [
|
138
|
-
match.value
|
139
|
-
for match in parse_jsonpath(
|
140
|
-
self.template.patch.identifier
|
141
|
-
).find(data)
|
142
|
-
]:
|
143
|
-
return matches[0]
|
144
|
-
return None
|
145
|
-
return data.get(self.template.patch.identifier)
|
146
|
-
|
147
|
-
dta_identifier = get_identifier(data_to_add)
|
130
|
+
if isinstance(self._matched_value, list):
|
131
|
+
dta_identifier = self._get_identifier(self._data_to_add)
|
148
132
|
if not dta_identifier:
|
133
|
+
assert self.template.patch is not None # mypy
|
149
134
|
raise ValueError(
|
150
135
|
f"Expected identifier {self.template.patch.identifier} in data to add"
|
151
136
|
)
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
for index, data in enumerate(matched_value)
|
157
|
-
if get_identifier(data) == dta_identifier
|
158
|
-
),
|
159
|
-
None,
|
160
|
-
)
|
161
|
-
if index is None:
|
162
|
-
matched_value.append(data_to_add)
|
137
|
+
if (
|
138
|
+
index := self._find_index(self._matched_value, dta_identifier)
|
139
|
+
) is not None:
|
140
|
+
self._matched_value[index] = self._data_to_add
|
163
141
|
else:
|
164
|
-
|
142
|
+
self._matched_value.append(self._data_to_add)
|
165
143
|
else:
|
166
|
-
|
144
|
+
self._matched_value.update(self._data_to_add)
|
167
145
|
|
168
146
|
with StringIO() as s:
|
169
147
|
self.ruamel_instance.dump(self.data.current, s)
|
170
148
|
return s.getvalue()
|
171
149
|
|
150
|
+
@cached_property
|
151
|
+
def _matched_value(self) -> Any:
|
152
|
+
assert self.template.patch is not None # mypy
|
153
|
+
p = parse_jsonpath(self._render_template(self.template.patch.path))
|
154
|
+
matched_values = [match.value for match in p.find(self.data.current)]
|
155
|
+
if len(matched_values) != 1:
|
156
|
+
raise ValueError(
|
157
|
+
f"Expected exactly one match for {self.template.patch.path}, got {len(matched_values)}"
|
158
|
+
)
|
159
|
+
return matched_values[0]
|
160
|
+
|
161
|
+
@cached_property
|
162
|
+
def _data_to_add(self) -> Any:
|
163
|
+
return self.ruamel_instance.load(self._render_template(self.template.template))
|
164
|
+
|
165
|
+
def _get_identifier(self, data: dict[str, Any]) -> Any:
|
166
|
+
assert self.template.patch is not None # mypy
|
167
|
+
if not self.template.patch.identifier:
|
168
|
+
raise ValueError(
|
169
|
+
f"Expected identifier in patch for list at {self.template}"
|
170
|
+
)
|
171
|
+
if self.template.patch.identifier.startswith(
|
172
|
+
"$"
|
173
|
+
) and not self.template.patch.identifier.startswith("$ref"):
|
174
|
+
# jsonpath and list of strings support
|
175
|
+
if matches := [
|
176
|
+
match.value
|
177
|
+
for match in parse_jsonpath(self.template.patch.identifier).find(data)
|
178
|
+
]:
|
179
|
+
return matches[0]
|
180
|
+
return None
|
181
|
+
return data.get(self.template.patch.identifier)
|
182
|
+
|
183
|
+
def _find_index(
|
184
|
+
self,
|
185
|
+
matched_value: list,
|
186
|
+
dta_identifier: Any,
|
187
|
+
) -> int | None:
|
188
|
+
for index, data in enumerate(matched_value):
|
189
|
+
if self._get_identifier(data) == dta_identifier:
|
190
|
+
return index
|
191
|
+
return None
|
192
|
+
|
172
193
|
|
173
194
|
def create_renderer(
|
174
195
|
template: Template,
|
reconcile/templating/renderer.py
CHANGED
@@ -104,14 +104,17 @@ class LocalFilePersistence(FilePersistence):
|
|
104
104
|
def read(self, path: str) -> str | None:
|
105
105
|
return self._read_local_file(join_path(self.app_interface_data_path, path))
|
106
106
|
|
107
|
-
def
|
108
|
-
if self.dry_run:
|
109
|
-
return
|
107
|
+
def flush(self) -> None:
|
110
108
|
for output in self.outputs:
|
111
109
|
filepath = Path(join_path(self.app_interface_data_path, output.path))
|
112
110
|
filepath.parent.mkdir(parents=True, exist_ok=True)
|
113
111
|
filepath.write_text(output.content, encoding="utf-8")
|
114
112
|
|
113
|
+
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
114
|
+
if self.dry_run:
|
115
|
+
return
|
116
|
+
self.flush()
|
117
|
+
|
115
118
|
|
116
119
|
class PersistenceTransaction(FilePersistence):
|
117
120
|
"""
|
{qontract_reconcile-0.10.2.dev292.dist-info → qontract_reconcile-0.10.2.dev293.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|