qontract-reconcile 0.10.1rc605__py3-none-any.whl → 0.10.1rc607__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.1rc605
3
+ Version: 0.10.1rc607
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
@@ -62,4 +62,5 @@ Requires-Dist: requests-oauthlib ~=1.3
62
62
  Requires-Dist: dt ==1.1.61
63
63
  Requires-Dist: jsonpatch ~=1.33
64
64
  Requires-Dist: jsonpointer ~=2.4
65
+ Requires-Dist: yamllint ==1.34.0
65
66
 
@@ -128,7 +128,7 @@ reconcile/aus/ocm_addons_upgrade_scheduler_org.py,sha256=fshslI27hrqT40qrVsVOQaW
128
128
  reconcile/aus/ocm_upgrade_scheduler.py,sha256=7cK2SakCFkl5EdnqUEAYdUo4pUnnf-SsUR10uytAGyE,3058
129
129
  reconcile/aus/ocm_upgrade_scheduler_org.py,sha256=OBgE5mnVdQQV4tMH0AE2V_PDt9Gy6d-LyuPceqjORts,2331
130
130
  reconcile/aus/upgrades.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
131
- reconcile/aus/version_gate_approver.py,sha256=VJ6Lrzkapr14QvulzIsE-sTfawIDcyn9UGOC3pIM1gs,6895
131
+ reconcile/aus/version_gate_approver.py,sha256=sZEVB0qVpAXpqymHxavFprkqmqt29fZAIuzSjPHLLrQ,7239
132
132
  reconcile/aus/version_gates/__init__.py,sha256=fWx-IvS132Wpa4gWNIuoNvFwqhkuUuFWYWq5-xiLklI,362
133
133
  reconcile/aus/version_gates/handler.py,sha256=S_isQPYHbG4DERiUEvQBZ6ngiFX3uMmATA-Q_eNKmFk,839
134
134
  reconcile/aus/version_gates/ocp_gate_handler.py,sha256=RW1ppDaCZXVegV9AzzqYXxDUu_Z_7d43Z5h2Pk_piKc,716
@@ -393,8 +393,8 @@ reconcile/templates/email.yml.j2,sha256=OZgczNRgXPj2gVYTgwQyHAQrMGu7xp-e4W1rX19G
393
393
  reconcile/templates/jira-checkpoint-missinginfo.j2,sha256=c_Vvg-lEENsB3tgxm9B6Y9igCUQhCnFDYh6xw-zcIbU,570
394
394
  reconcile/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
395
395
  reconcile/templating/renderer.py,sha256=IOZN0ASSKqp4JGXZ1BDba4XosD3nCuqhStxHMGBcxDI,5119
396
- reconcile/templating/rendering.py,sha256=2kMfstQz12FL-Qfh9OBuzAcYY1OggbFQrCYx1nxERSg,4737
397
- reconcile/templating/validator.py,sha256=W64nR1eoKVDGewJ4DEX9ZiWWKjFEKg5w5Xi6lrrPBd8,3434
396
+ reconcile/templating/rendering.py,sha256=msMGqCNh7M0c6A1l_7KBmT3GRHH9gsB8htN_9bGtwe4,4949
397
+ reconcile/templating/validator.py,sha256=6ZZs5cREIndgAUHiR1SeDGwaeSLJTJBKHww0nXzgqhQ,4141
398
398
  reconcile/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
399
399
  reconcile/test/conftest.py,sha256=rQousYrxUz-EwAIbsYO6bIwR1B4CrOz9y_zaUVo2lfI,4466
400
400
  reconcile/test/fixtures.py,sha256=9SDWAUlSd1rCx7z3GhULHcpr-I6FyCsXxaFAZIqYQsQ,591
@@ -670,7 +670,7 @@ reconcile/utils/ocm/syncsets.py,sha256=zvji9qWvInIRTdoybMaM9-VUhq4L1VewWfWCQmp4u
670
670
  reconcile/utils/ocm/upgrades.py,sha256=ZWDfg3VrmRGx7Re-JjecRjwmn7Rh-dsuLA3OljbCByg,6616
671
671
  reconcile/utils/rosa/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
672
672
  reconcile/utils/rosa/rosa_cli.py,sha256=zuXBRh-cJK4yOPjtiSRbUQRURemFiLlWdjj3XteuQ-4,10475
673
- reconcile/utils/rosa/session.py,sha256=HNE3od_gUL38q43lpKVvaIatd6LYgRnYzRBFbv5hHgQ,3875
673
+ reconcile/utils/rosa/session.py,sha256=nPEmSNos0_ATQEVg2KckAlVyIEBvxUVo7pa0WMACoOU,4150
674
674
  reconcile/utils/runtime/__init__.py,sha256=sfk92MGfsBh9tKYHl_FH17NdEsrGBwgDFTb7KNKoIfY,107
675
675
  reconcile/utils/runtime/desired_state_diff.py,sha256=finZnWoVDCiYTgu4lGk8G4QOFAGgiIDhD3fcnVqYoxM,8108
676
676
  reconcile/utils/runtime/environment.py,sha256=JLptHJoYyeLdMBghJppttP3wZ5HxHLMLgUcfGjIiKLM,2087
@@ -700,6 +700,7 @@ tools/glitchtip_access_reporter.py,sha256=oPBnk_YoDuljU3v0FaChzOwwnk4vap1xEE67QE
700
700
  tools/glitchtip_access_revalidation.py,sha256=8kbBJk04mkq28kWoRDDkfCGIF3GRg3pJrFAh1sW0dbk,2821
701
701
  tools/qontract_cli.py,sha256=cnZLJJS-FRUQFUAD90ivt-WEv8NfaemqBvZ9CaFV_kI,110957
702
702
  tools/sd_app_sre_alert_report.py,sha256=e9vAdyenUz2f5c8-z-5WY0wv-SJ9aePKDH2r4IwB6pc,5063
703
+ tools/template_validation.py,sha256=XZ6i1iTN2Lknh-8uS-kcbTmYBzJhzl2tLYN6hP33N2Q,3244
703
704
  tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
704
705
  tools/cli_commands/gpg_encrypt.py,sha256=w8hl4jIEWk5wKbEFN6fVEOwUJGmdlvOqYodW3XSN7mU,4978
705
706
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
@@ -709,8 +710,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
709
710
  tools/test/test_qontract_cli.py,sha256=OvalpVRfY4pNmpMaWHHYqBjV68b1eGQjX8SCyTAXb1w,3501
710
711
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
711
712
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
712
- qontract_reconcile-0.10.1rc605.dist-info/METADATA,sha256=bx0cGNR1BJbl-kyvPzH5U9kttb_fURemj1y_idTO9PU,2349
713
- qontract_reconcile-0.10.1rc605.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
714
- qontract_reconcile-0.10.1rc605.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
715
- qontract_reconcile-0.10.1rc605.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
716
- qontract_reconcile-0.10.1rc605.dist-info/RECORD,,
713
+ qontract_reconcile-0.10.1rc607.dist-info/METADATA,sha256=YA_uMko_z5JCYKe6FRbUNu7lVLlorRq_4HKpwJDrj3Q,2382
714
+ qontract_reconcile-0.10.1rc607.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
715
+ qontract_reconcile-0.10.1rc607.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
716
+ qontract_reconcile-0.10.1rc607.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
717
+ qontract_reconcile-0.10.1rc607.dist-info/RECORD,,
@@ -5,3 +5,4 @@ glitchtip-access-reporter = tools.glitchtip_access_reporter:main
5
5
  glitchtip-access-revalidation = tools.glitchtip_access_revalidation:main
6
6
  qontract-cli = tools.qontract_cli:root
7
7
  qontract-reconcile = reconcile.cli:integration
8
+ template-validation = tools.template_validation:main
@@ -178,6 +178,9 @@ class VersionGateApprover(QontractReconcileIntegration[VersionGateApproverParams
178
178
  for gate in gates:
179
179
  if gate.label in self.handlers and gate.label not in enabled_gate_handlers:
180
180
  continue
181
+ logging.info(
182
+ f"handle gate {gate.label} for cluster {cluster.name} {{gate_id = {gate.id}), version = {gate.version_raw_id_prefix}, cluster_id = {cluster.id}, org_id = {ocm_org_id}}}"
183
+ )
181
184
  success = self.handlers[gate.label].handle(
182
185
  ocm_api=ocm_api,
183
186
  ocm_org_id=ocm_org_id,
@@ -189,7 +192,7 @@ class VersionGateApprover(QontractReconcileIntegration[VersionGateApproverParams
189
192
  create_version_agreement(ocm_api, gate.id, cluster.id)
190
193
  elif not success:
191
194
  logging.error(
192
- f"Failed to handle gate {gate.id} for cluster {cluster.name}"
195
+ f"failed to handle gate {gate.label} for cluster {cluster.name} {{gate_id = {gate.id}), version = {gate.version_raw_id_prefix}, cluster_id = {cluster.id}, org_id = {ocm_org_id}}}"
193
196
  )
194
197
 
195
198
 
@@ -1,12 +1,13 @@
1
1
  import logging
2
2
  from abc import ABC, abstractmethod
3
+ from io import StringIO
3
4
  from typing import Any, Optional, Protocol
4
5
 
5
6
  from pydantic import BaseModel
6
- from ruamel import yaml
7
7
 
8
8
  from reconcile.utils.jinja2.utils import Jinja2TemplateError, process_jinja2_template
9
9
  from reconcile.utils.jsonpath import parse_jsonpath
10
+ from reconcile.utils.ruamel import create_ruamel_instance
10
11
  from reconcile.utils.secret_reader import SecretReaderBase
11
12
 
12
13
 
@@ -40,11 +41,12 @@ class Renderer(ABC):
40
41
  self,
41
42
  template: Template,
42
43
  data: TemplateData,
43
- secret_reader: SecretReaderBase,
44
+ secret_reader: Optional[SecretReaderBase] = None,
44
45
  ):
45
46
  self.template = template
46
47
  self.data = data
47
48
  self.secret_reader = secret_reader
49
+ self.ruaml_instace = create_ruamel_instance()
48
50
 
49
51
  def _jinja2_render_kwargs(self) -> dict[str, Any]:
50
52
  return {**self.data.variables, "current": self.data.current}
@@ -108,7 +110,9 @@ class PatchRenderer(Renderer):
108
110
  )
109
111
  matched_value = matched_values[0]
110
112
 
111
- data_to_add = yaml.safe_load(self._render_template(self.template.template))
113
+ data_to_add = self.ruaml_instace.load(
114
+ self._render_template(self.template.template)
115
+ )
112
116
 
113
117
  if isinstance(matched_value, list):
114
118
  if not self.template.patch.identifier:
@@ -136,13 +140,15 @@ class PatchRenderer(Renderer):
136
140
  else:
137
141
  matched_value.update(data_to_add)
138
142
 
139
- return yaml.dump(self.data.current, width=4096, Dumper=yaml.RoundTripDumper)
143
+ with StringIO() as s:
144
+ self.ruaml_instace.dump(self.data.current, s)
145
+ return s.getvalue()
140
146
 
141
147
 
142
148
  def create_renderer(
143
149
  template: Template,
144
150
  data: TemplateData,
145
- secret_reader: SecretReaderBase,
151
+ secret_reader: Optional[SecretReaderBase] = None,
146
152
  ) -> Renderer:
147
153
  if template.patch:
148
154
  return PatchRenderer(template=template, data=data, secret_reader=secret_reader)
@@ -5,13 +5,17 @@ from typing import Callable, Optional
5
5
  from pydantic import BaseModel
6
6
  from ruamel import yaml
7
7
 
8
- from reconcile.gql_definitions.templating.templates import TemplateV1, query
9
- from reconcile.templating.rendering import TemplateData, create_renderer
8
+ from reconcile.gql_definitions.templating.templates import (
9
+ TemplateTestV1,
10
+ TemplateV1,
11
+ query,
12
+ )
13
+ from reconcile.templating.rendering import Renderer, TemplateData, create_renderer
10
14
  from reconcile.utils import gql
11
15
  from reconcile.utils.runtime.integration import (
12
16
  QontractReconcileIntegration,
13
- RunParamsTypeVar,
14
17
  )
18
+ from reconcile.utils.secret_reader import SecretReaderBase
15
19
 
16
20
  QONTRACT_INTEGRATION = "template-validator"
17
21
 
@@ -31,70 +35,95 @@ class TemplateDiff(BaseModel):
31
35
 
32
36
 
33
37
  class TemplateValidatorIntegration(QontractReconcileIntegration):
34
- def __init__(self, params: RunParamsTypeVar) -> None:
35
- super().__init__(params)
36
- self.diffs: list[TemplateDiff] = []
37
-
38
- def diff_result(
39
- self, template_name: str, test_name: str, output: str, expected: str
40
- ) -> None:
41
- diff = list(
42
- context_diff(
43
- output.splitlines(keepends=True), expected.splitlines(keepends=True)
44
- )
38
+ @staticmethod
39
+ def _create_renderer(
40
+ template: TemplateV1,
41
+ template_test: TemplateTestV1,
42
+ secret_reader: Optional[SecretReaderBase] = None,
43
+ ) -> Renderer:
44
+ return create_renderer(
45
+ template,
46
+ TemplateData(
47
+ variables=template_test.variables or {},
48
+ current=yaml.load(
49
+ template_test.current or "", Loader=yaml.RoundTripLoader
50
+ ),
51
+ ),
52
+ secret_reader=secret_reader,
53
+ )
54
+
55
+ @staticmethod
56
+ def validate_template(
57
+ template: TemplateV1,
58
+ template_test: TemplateTestV1,
59
+ secret_reader: Optional[SecretReaderBase] = None,
60
+ ) -> list[TemplateDiff]:
61
+ diffs: list[TemplateDiff] = []
62
+
63
+ r = TemplateValidatorIntegration._create_renderer(
64
+ template, template_test, secret_reader=secret_reader
45
65
  )
46
- if diff:
47
- self.diffs.append(
48
- TemplateDiff(template=template_name, test=test_name, diff="".join(diff))
66
+
67
+ # Check target path
68
+ if template_test.expected_target_path:
69
+ rendered_target_path = r.render_target_path().strip()
70
+ if rendered_target_path != template_test.expected_target_path.strip():
71
+ diffs.append(
72
+ TemplateDiff(
73
+ template=template.name,
74
+ test=template_test.name,
75
+ diff=f"Target path mismatch, got: {rendered_target_path}, expected: {template_test.expected_target_path}",
76
+ )
77
+ )
78
+
79
+ # Check condition
80
+ should_render = r.render_condition()
81
+ if (
82
+ template_test.expected_to_render is not None
83
+ and template_test.expected_to_render != should_render
84
+ ):
85
+ diffs.append(
86
+ TemplateDiff(
87
+ template=template.name,
88
+ test=template_test.name,
89
+ diff=f"Condition mismatch, got: {should_render}, expected: {template_test.expected_to_render}",
90
+ )
49
91
  )
50
92
 
93
+ # Check template output
94
+ if should_render:
95
+ output = r.render_output()
96
+ diff = list(
97
+ context_diff(
98
+ output.splitlines(keepends=True),
99
+ template_test.expected_output.splitlines(keepends=True),
100
+ )
101
+ )
102
+ if diff:
103
+ diffs.append(
104
+ TemplateDiff(
105
+ template=template.name,
106
+ test=template_test.name,
107
+ diff="".join(diff),
108
+ )
109
+ )
110
+
111
+ return diffs
112
+
51
113
  def run(self, dry_run: bool) -> None:
114
+ diffs: list[TemplateDiff] = []
115
+
52
116
  for template in get_templates():
53
117
  for test in template.template_test:
54
118
  logging.info(f"Running test {test.name} for template {template.name}")
55
- r = create_renderer(
56
- template,
57
- TemplateData(
58
- variables=test.variables or {},
59
- current=yaml.load(
60
- test.current or "", Loader=yaml.RoundTripLoader
61
- ),
62
- ),
63
- secret_reader=self.secret_reader,
64
- )
65
- if test.expected_target_path:
66
- self.diff_result(
67
- template.name,
68
- test.name,
69
- r.render_target_path().strip(),
70
- test.expected_target_path.strip(),
71
- )
72
- should_render = r.render_condition()
73
- if (
74
- test.expected_to_render is not None
75
- and test.expected_to_render != should_render
76
- ):
77
- self.diffs.append(
78
- TemplateDiff(
79
- template=template.name,
80
- test=test.name,
81
- diff=f"Condition mismatch, got: {should_render}, expected: {test.expected_to_render}",
82
- )
83
- )
84
- if should_render:
85
- self.diff_result(
86
- template.name,
87
- test.name,
88
- r.render_output().strip(),
89
- test.expected_output.strip(),
90
- )
119
+ diffs.extend(self.validate_template(template, test, self.secret_reader))
91
120
 
92
- if self.diffs:
93
- for diff in self.diffs:
121
+ if diffs:
122
+ for diff in diffs:
94
123
  logging.error(
95
124
  f"template: {diff.template}, test: {diff.test}: {diff.diff}"
96
125
  )
97
- raise ValueError("Template validation")
126
+ raise ValueError("Template validation failed")
98
127
 
99
128
  @property
100
129
  def name(self) -> str:
@@ -89,6 +89,9 @@ class RosaSession:
89
89
  def upgrade_account_roles(
90
90
  self, role_prefix: str, minor_version: str, channel_group: str, dry_run: bool
91
91
  ) -> None:
92
+ logging.info(
93
+ f"Upgrade account roles in AWS account {self.aws_account_id} to {minor_version} ({channel_group})"
94
+ )
92
95
  if not dry_run:
93
96
  result = self.cli_execute(
94
97
  f"rosa upgrade account-roles --prefix {role_prefix} --version {minor_version} --channel-group {channel_group} -y -m=auto"
@@ -104,6 +107,9 @@ class RosaSession:
104
107
  Upgrades the operator roles of a cluster to match the latest
105
108
  policy versions available for the cluster.
106
109
  """
110
+ logging.info(
111
+ f"Upgrade operator roles in AWS account {self.aws_account_id} for cluster {cluster_id}"
112
+ )
107
113
  if not dry_run:
108
114
  result = self.cli_execute(
109
115
  cmd=f"rosa upgrade operator-roles --cluster {cluster_id} -y -m=auto",
@@ -0,0 +1,107 @@
1
+ import json
2
+ import os.path
3
+ import sys
4
+
5
+ import click
6
+ from yamllint import linter # type: ignore
7
+ from yamllint.config import YamlLintConfig # type: ignore
8
+
9
+ from reconcile.gql_definitions.templating.templates import TemplateV1
10
+ from reconcile.templating.validator import TemplateDiff, TemplateValidatorIntegration
11
+ from reconcile.utils.models import data_default_none
12
+ from reconcile.utils.ruamel import create_ruamel_instance
13
+
14
+
15
+ def load_clean_yaml(ai_path: str, path: str) -> dict:
16
+ if not path.startswith("/data/"):
17
+ to_load = os.path.join(ai_path, "data", path.lstrip("/"))
18
+ else:
19
+ to_load = os.path.join(ai_path, path.lstrip("/"))
20
+
21
+ y = load_yaml(to_load)
22
+
23
+ if "$schema" in y:
24
+ del y["$schema"]
25
+ return y
26
+
27
+
28
+ def load_yaml(to_load: str) -> dict:
29
+ ruamel_instance = create_ruamel_instance()
30
+ with open(to_load, "r", encoding="utf-8") as file:
31
+ return ruamel_instance.load(file)
32
+
33
+
34
+ def print_lint_problems(lint_problems: list[linter.LintProblem]) -> None:
35
+ print("--------- LINTING ISSUES ---------")
36
+ print(f"Found {len(lint_problems)} lint problems:")
37
+ for problem in lint_problems:
38
+ print(f"Lint error in line: {problem.line}, {problem.desc}")
39
+ print("--------- END LINTING ISSUES ---------")
40
+
41
+
42
+ def print_test_diffs(diffs: list[TemplateDiff]) -> None:
43
+ print("--------- TEMPLATE TEST DIFF ---------")
44
+ print("Template validation failed")
45
+ for d in diffs:
46
+ for line in d.diff.splitlines():
47
+ if line:
48
+ print(f"{line}")
49
+ print("--------- END TEMPLATE TEST DIFF ---------")
50
+
51
+
52
+ @click.command()
53
+ @click.option(
54
+ "--ai-path",
55
+ help="Path to the bundle file",
56
+ default=None,
57
+ required=True,
58
+ )
59
+ @click.option(
60
+ "--template-path",
61
+ help="Path to the template file",
62
+ default=None,
63
+ required=True,
64
+ )
65
+ def main(ai_path: str, template_path: str) -> None:
66
+ okay = True
67
+ templateRaw = load_clean_yaml(ai_path, template_path)
68
+
69
+ tests = []
70
+ for testRaw in templateRaw["templateTest"]:
71
+ test_yaml = load_clean_yaml(ai_path, testRaw["$ref"])
72
+ variables = json.dumps(test_yaml["variables"])
73
+ test_yaml["variables"] = variables
74
+ tests.append(test_yaml)
75
+
76
+ templateRaw["templateTest"] = tests
77
+ template: TemplateV1 = TemplateV1(**data_default_none(TemplateV1, templateRaw))
78
+
79
+ # templates_to_validate = {}
80
+ for test in template.template_test:
81
+ diffs: list[TemplateDiff] = []
82
+ print("Running tests:", test.name)
83
+ diffs.extend(TemplateValidatorIntegration.validate_template(template, test))
84
+
85
+ renderer = TemplateValidatorIntegration._create_renderer(template, test)
86
+ if renderer.render_condition():
87
+ output = renderer.render_output()
88
+ lint_problems = list(
89
+ linter.run(output, YamlLintConfig(file=f"{ai_path}/.yamllint"), "")
90
+ )
91
+ if lint_problems:
92
+ okay = False
93
+ print_lint_problems(lint_problems)
94
+
95
+ if diffs:
96
+ okay = False
97
+ print_test_diffs(diffs)
98
+
99
+ if okay:
100
+ print("... passed")
101
+ sys.exit(0)
102
+ print("... failed")
103
+ sys.exit(1)
104
+
105
+
106
+ if __name__ == "__main__":
107
+ main() # pylint: disable=no-value-for-parameter