qontract-reconcile 0.10.1rc735__py3-none-any.whl → 0.10.1rc736__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.1rc735
3
+ Version: 0.10.1rc736
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
@@ -331,8 +331,8 @@ reconcile/gql_definitions/status_board/status_board.py,sha256=vHEzncabujkqbjJ-ib
331
331
  reconcile/gql_definitions/statuspage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
332
332
  reconcile/gql_definitions/statuspage/statuspages.py,sha256=gxDb42H93nwtBg7oFRb6Gk9pbAZpsWk_y4Y0s3_g3nE,3520
333
333
  reconcile/gql_definitions/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
334
- reconcile/gql_definitions/templating/template_collection.py,sha256=QYqEsy8BBdX9GK4SuOdSe_hqOHK4IHNz9eJ5P2f4TRQ,3007
335
- reconcile/gql_definitions/templating/templates.py,sha256=ujPPFZm9BbkRrxkcR7ZyReDYfFem4uxONYoPuzgiBTc,2735
334
+ reconcile/gql_definitions/templating/template_collection.py,sha256=lS0vzEKV2ZrzOqOEriqpy0yBgKjb2Ftrzgx6PIH46_4,3310
335
+ reconcile/gql_definitions/templating/templates.py,sha256=ejAvQ13zfNMQTz3FWtRUic6dSvio3aAgBKEqt600hbk,2821
336
336
  reconcile/gql_definitions/terraform_cloudflare_dns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
337
337
  reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py,sha256=eyGX9HcTF6MZbOYZ6Kl6Mg3k6nJTUtwqs9gDxBP_8Dk,1920
338
338
  reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py,sha256=uVZYu5EUcvdAQYBK5YKD0mjoMKDb5inSuCJrrOD5KpE,5704
@@ -419,11 +419,11 @@ reconcile/templates/jira-checkpoint-missinginfo.j2,sha256=c_Vvg-lEENsB3tgxm9B6Y9
419
419
  reconcile/templates/rosa-classic-cluster-creation.sh.j2,sha256=0UHfYtXRVJqP07VJQx456cRI6EbZNBgamtP_8nb4WPY,2353
420
420
  reconcile/templates/rosa-hcp-cluster-creation.sh.j2,sha256=O7Bf3WQIJhsZoEqaYA0wRktUO4yXXCb4BQkuvvp-C80,2385
421
421
  reconcile/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
422
- reconcile/templating/renderer.py,sha256=JUUzXGXzhcq-bhj81se7lLWgVfrUIEpO5vh6JgppbrI,10178
423
- reconcile/templating/validator.py,sha256=QGH2VSk7sVBuojhk9quRAbj_XBykHN-KZ53DbEneUJs,4391
422
+ reconcile/templating/renderer.py,sha256=2FWbnefT2siozQpXXkuvVKUo6cePMqLY4BMYpqXg6xM,10652
423
+ reconcile/templating/validator.py,sha256=pvDEc6veznEZzjypkoRJUGMMFLWosU-zd7i3j7JeNjE,4670
424
424
  reconcile/templating/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
425
- reconcile/templating/lib/merge_request_manager.py,sha256=El3ufdVjHP4EW-LztX7zPiI9syJBJ6ETGRmiM9K4Nqw,5112
426
- reconcile/templating/lib/model.py,sha256=TYiH2xL63awd30U-fkZDLEzuxkLdUEgzJ913DEzpFoM,265
425
+ reconcile/templating/lib/merge_request_manager.py,sha256=JUkfF3smaQ8onzKF5F7UpmA7MWaQpftANy6dDo1FCug,5464
426
+ reconcile/templating/lib/model.py,sha256=fb6FYYLQjmoh2DjVKO7TEWCuDPf1Q34xmOx0M9Z07ek,324
427
427
  reconcile/templating/lib/rendering.py,sha256=_BVQ2gqip8K1AgLYfaTWh8NKJFTW6VjUZ6rBI_GH30E,5061
428
428
  reconcile/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
429
429
  reconcile/test/conftest.py,sha256=rQousYrxUz-EwAIbsYO6bIwR1B4CrOz9y_zaUVo2lfI,4466
@@ -770,8 +770,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
770
770
  tools/test/test_qontract_cli.py,sha256=w2l4BHB09k1d-BGJ1jBUNCqDv7zkqYrMHojQXg-21kQ,4155
771
771
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
772
772
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
773
- qontract_reconcile-0.10.1rc735.dist-info/METADATA,sha256=AQww9xL2v9VdTWvywruYZoPnQRbH2TO1wlIpHlpfGqE,2382
774
- qontract_reconcile-0.10.1rc735.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
775
- qontract_reconcile-0.10.1rc735.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
776
- qontract_reconcile-0.10.1rc735.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
777
- qontract_reconcile-0.10.1rc735.dist-info/RECORD,,
773
+ qontract_reconcile-0.10.1rc736.dist-info/METADATA,sha256=HGDJxGb77YYojenQNeOYhLfp3EOLLqkOwx8Tat8ONWA,2382
774
+ qontract_reconcile-0.10.1rc736.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
775
+ qontract_reconcile-0.10.1rc736.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
776
+ qontract_reconcile-0.10.1rc736.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
777
+ qontract_reconcile-0.10.1rc736.dist-info/RECORD,,
@@ -22,7 +22,9 @@ DEFINITION = """
22
22
  query TemplateCollection_v1 {
23
23
  template_collection_v1 {
24
24
  name
25
+ additionalMrLabels
25
26
  description
27
+ enableAutoApproval
26
28
  variables {
27
29
  static
28
30
  dynamic {
@@ -32,6 +34,7 @@ query TemplateCollection_v1 {
32
34
  }
33
35
  templates {
34
36
  name
37
+ autoApproved
35
38
  condition
36
39
  targetPath
37
40
  patch {
@@ -68,6 +71,7 @@ class TemplatePatchV1(ConfiguredBaseModel):
68
71
 
69
72
  class TemplateV1(ConfiguredBaseModel):
70
73
  name: str = Field(..., alias="name")
74
+ auto_approved: Optional[bool] = Field(..., alias="autoApproved")
71
75
  condition: Optional[str] = Field(..., alias="condition")
72
76
  target_path: str = Field(..., alias="targetPath")
73
77
  patch: Optional[TemplatePatchV1] = Field(..., alias="patch")
@@ -76,7 +80,9 @@ class TemplateV1(ConfiguredBaseModel):
76
80
 
77
81
  class TemplateCollectionV1(ConfiguredBaseModel):
78
82
  name: str = Field(..., alias="name")
83
+ additional_mr_labels: Optional[list[str]] = Field(..., alias="additionalMrLabels")
79
84
  description: str = Field(..., alias="description")
85
+ enable_auto_approval: Optional[bool] = Field(..., alias="enableAutoApproval")
80
86
  variables: Optional[TemplateCollectionVariablesV1] = Field(..., alias="variables")
81
87
  templates: list[TemplateV1] = Field(..., alias="templates")
82
88
 
@@ -22,6 +22,7 @@ DEFINITION = """
22
22
  query Templatev1 {
23
23
  template_v1 {
24
24
  name
25
+ autoApproved
25
26
  condition
26
27
  patch {
27
28
  path
@@ -64,6 +65,7 @@ class TemplateTestV1(ConfiguredBaseModel):
64
65
 
65
66
  class TemplateV1(ConfiguredBaseModel):
66
67
  name: str = Field(..., alias="name")
68
+ auto_approved: Optional[bool] = Field(..., alias="autoApproved")
67
69
  condition: Optional[str] = Field(..., alias="condition")
68
70
  patch: Optional[TemplatePatchV1] = Field(..., alias="patch")
69
71
  target_path: str = Field(..., alias="targetPath")
@@ -13,6 +13,7 @@ from reconcile.utils.merge_request_manager.parser import (
13
13
  Parser,
14
14
  )
15
15
  from reconcile.utils.mr import MergeRequestBase
16
+ from reconcile.utils.mr.labels import AUTO_MERGE
16
17
  from reconcile.utils.vcs import VCS
17
18
 
18
19
  DATA_SEPARATOR = (
@@ -120,16 +121,23 @@ class TemplateRenderingMR(MergeRequestBase):
120
121
  )
121
122
 
122
123
 
124
+ class MrData(BaseModel):
125
+ data: list[TemplateOutput]
126
+ auto_approved: bool
127
+
128
+
123
129
  class MergeRequestManager(MergeRequestManagerBase[TemplateInfo]):
124
130
  def __init__(self, vcs: VCS, parser: Parser):
125
131
  super().__init__(vcs, parser, TR_LABEL)
126
132
 
127
- def create_merge_request(self, output: list[TemplateOutput]) -> None:
133
+ def create_merge_request(self, data: MrData) -> None:
128
134
  if not self._housekeeping_ran:
129
135
  self.housekeeping()
130
136
 
131
- collections = {o.input.collection for o in output if o.input}
132
- collection_hashes = {o.input.collection_hash for o in output if o.input}
137
+ output = data.data
138
+ collections = {o.input.collection for o in output}
139
+ collection_hashes = {o.input.collection_hash for o in output}
140
+ additional_labels = {label for o in output for label in o.input.labels}
133
141
  # From the way the code is written, we can assert that there is only one collection and one template hash
134
142
  assert len(collections) == 1
135
143
  assert len(collection_hashes) == 1
@@ -158,6 +166,12 @@ class MergeRequestManager(MergeRequestManagerBase[TemplateInfo]):
158
166
  logging.info("Opening MR for %s with hash (%s)", collection, collection_hash)
159
167
  mr_labels = [TR_LABEL]
160
168
 
169
+ if data.auto_approved:
170
+ mr_labels.append(AUTO_MERGE)
171
+
172
+ if additional_labels:
173
+ mr_labels.extend(additional_labels)
174
+
161
175
  self._vcs.open_app_interface_merge_request(
162
176
  mr=TemplateRenderingMR(
163
177
  title=title,
@@ -1,15 +1,16 @@
1
- from typing import Optional
2
-
3
1
  from pydantic import BaseModel
4
2
 
5
3
 
6
4
  class TemplateInput(BaseModel):
7
5
  collection: str
8
6
  collection_hash: str
7
+ enable_auto_approval: bool = False
8
+ labels: list[str] = []
9
9
 
10
10
 
11
11
  class TemplateOutput(BaseModel):
12
- input: Optional[TemplateInput]
12
+ input: TemplateInput
13
13
  is_new: bool = False
14
14
  path: str
15
15
  content: str
16
+ auto_approved: bool = False
@@ -17,6 +17,7 @@ from reconcile.gql_definitions.templating.template_collection import (
17
17
  )
18
18
  from reconcile.templating.lib.merge_request_manager import (
19
19
  MergeRequestManager,
20
+ MrData,
20
21
  create_parser,
21
22
  )
22
23
  from reconcile.templating.lib.model import TemplateInput, TemplateOutput
@@ -59,6 +60,19 @@ class FilePersistence(ABC):
59
60
  def read(self, path: str) -> Optional[str]:
60
61
  pass
61
62
 
63
+ @staticmethod
64
+ def _read_local_file(path: str) -> Optional[str]:
65
+ try:
66
+ with open(
67
+ path,
68
+ "r",
69
+ encoding="utf-8",
70
+ ) as f:
71
+ return f.read()
72
+ except FileNotFoundError:
73
+ logging.debug(f"File not found: {path}, need to create it")
74
+ return None
75
+
62
76
 
63
77
  class LocalFilePersistence(FilePersistence):
64
78
  """
@@ -80,16 +94,7 @@ class LocalFilePersistence(FilePersistence):
80
94
  f.write(output.content)
81
95
 
82
96
  def read(self, path: str) -> Optional[str]:
83
- try:
84
- with open(
85
- f"{join_path(self.app_interface_data_path, path)}",
86
- "r",
87
- encoding="utf-8",
88
- ) as f:
89
- return f.read()
90
- except FileNotFoundError:
91
- logging.debug(f"File not found: {path}, need to create it")
92
- return None
97
+ return self._read_local_file(join_path(self.app_interface_data_path, path))
93
98
 
94
99
 
95
100
  class PersistenceTransaction(FilePersistence):
@@ -127,6 +132,8 @@ class ClonedRepoGitlabPersistence(FilePersistence):
127
132
  """
128
133
  This class is used to persist the rendered templates in a cloned gitlab repo
129
134
  Reads are from the local filesystem, writes are done via utils.VCS abstraction
135
+
136
+ Only one MR is created per run. Auto-approval MRs are prefered.
130
137
  """
131
138
 
132
139
  def __init__(self, local_path: str, vcs: VCS, mr_manager: MergeRequestManager):
@@ -136,19 +143,19 @@ class ClonedRepoGitlabPersistence(FilePersistence):
136
143
 
137
144
  def write(self, outputs: list[TemplateOutput]) -> None:
138
145
  self.mr_manager.housekeeping()
139
- self.mr_manager.create_merge_request(outputs)
146
+
147
+ if any([o.input.enable_auto_approval for o in outputs]):
148
+ auto_approved = [o for o in outputs if o.auto_approved]
149
+ if auto_approved:
150
+ self.mr_manager.create_merge_request(
151
+ MrData(data=auto_approved, auto_approved=True)
152
+ )
153
+ return
154
+
155
+ self.mr_manager.create_merge_request(MrData(data=outputs, auto_approved=False))
140
156
 
141
157
  def read(self, path: str) -> Optional[str]:
142
- try:
143
- with open(
144
- f"{join_path(self.local_path, path)}",
145
- "r",
146
- encoding="utf-8",
147
- ) as f:
148
- return f.read()
149
- except FileNotFoundError:
150
- logging.debug(f"File not found: {path}, need to create it")
151
- return None
158
+ return self._read_local_file(join_path(self.local_path, path))
152
159
 
153
160
 
154
161
  def unpack_static_variables(
@@ -187,6 +194,7 @@ class TemplateRendererIntegration(QontractReconcileIntegration):
187
194
  variables: dict,
188
195
  persistence: FilePersistence,
189
196
  ruaml_instance: yaml.YAML,
197
+ template_input: TemplateInput,
190
198
  ) -> Optional[TemplateOutput]:
191
199
  r = create_renderer(
192
200
  template,
@@ -217,6 +225,8 @@ class TemplateRendererIntegration(QontractReconcileIntegration):
217
225
  path=target_path,
218
226
  content=output,
219
227
  is_new=current_str is None,
228
+ auto_approved=template.auto_approved or False,
229
+ input=template_input,
220
230
  )
221
231
  return None
222
232
 
@@ -244,18 +254,17 @@ class TemplateRendererIntegration(QontractReconcileIntegration):
244
254
  ).hexdigest()
245
255
 
246
256
  with PersistenceTransaction(persistence, dry_run) as p:
257
+ input = TemplateInput(
258
+ collection=c.name,
259
+ collection_hash=template_hash,
260
+ enable_auto_approval=c.enable_auto_approval or False,
261
+ labels=c.additional_mr_labels or [],
262
+ )
247
263
  for template in c.templates:
248
264
  output = self.process_template(
249
- template,
250
- variables,
251
- p,
252
- ruamel_instance,
265
+ template, variables, p, ruamel_instance, input
253
266
  )
254
267
  if output:
255
- output.input = TemplateInput(
256
- collection=c.name,
257
- collection_hash=template_hash,
258
- )
259
268
  outputs.append(output)
260
269
 
261
270
  if not dry_run:
@@ -127,7 +127,11 @@ class TemplateValidatorIntegration(QontractReconcileIntegration):
127
127
  if diffs:
128
128
  for diff in diffs:
129
129
  logging.error(f"template: {diff.template}, test: {diff.test}")
130
- logging.debug(diff.diff)
130
+ # This log should never be added except for local debugging.
131
+ # Credentials could be leaked, i.e. creating an MR with a diff,
132
+ # using a template, that uses the vault function.
133
+ # Use template-validator CLI instead.
134
+ # logging.debug(diff.diff)
131
135
  raise ValueError("Template validation failed")
132
136
 
133
137
  @property