qontract-reconcile 0.10.2.dev32__py3-none-any.whl → 0.10.2.dev34__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.4
2
2
  Name: qontract-reconcile
3
- Version: 0.10.2.dev32
3
+ Version: 0.10.2.dev34
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
@@ -30,7 +30,7 @@ reconcile/github_repo_permissions_validator.py,sha256=dcbXdUx6imjNchjp3pg9-z1i7l
30
30
  reconcile/github_users.py,sha256=nfTq78QRONIfDVj-5O3bD6psllJjzWFnog-EJ1WqFPU,3672
31
31
  reconcile/github_validator.py,sha256=cVTVxJIGR4a1Jz8wrdXEAb_CMpXUzvykVmUURX4cook,917
32
32
  reconcile/gitlab_fork_compliance.py,sha256=c7UfqSAsW04c1bWJmXXaQDwtUcG4Kb6nCJAyRU2uAuw,4449
33
- reconcile/gitlab_housekeeping.py,sha256=NRiMo4l1K3JeP4A-qEF_VCGhadXlLuf5X_7qcTTtnbI,22482
33
+ reconcile/gitlab_housekeeping.py,sha256=i4hNZViV2yfUTDNsYhVSLik211q8_c4nbTK8ilmpufc,25313
34
34
  reconcile/gitlab_labeler.py,sha256=4xJHmVX155fclrHqkR926sL1GH6RTN5XfZ8PnqNXbRA,4534
35
35
  reconcile/gitlab_members.py,sha256=MUIgYDLeJx2-_vMypyq2Pa17cpKdXATYhtVACS2ghpQ,8297
36
36
  reconcile/gitlab_mr_sqs_consumer.py,sha256=O46mdziPgGOndbU-0_UJKJVUaiEoVzJPEgKm4_UvYoI,2571
@@ -94,7 +94,7 @@ reconcile/quay_mirror.py,sha256=0KtQFwrvMNtlsPJ9F_-ICaVIjgIUjFxqipvAPcvyg3Q,1533
94
94
  reconcile/quay_mirror_org.py,sha256=tXKuF6JtmaNRwu8_g_65U_Vpd6sFBYeXmJA-flVhylE,10764
95
95
  reconcile/quay_permissions.py,sha256=9KOutS1w4RFQqkvMSy54VtsKNx56-phzP6yI_rEW-B8,4244
96
96
  reconcile/quay_repos.py,sha256=cuEYG0HUe0ut5yvLdEwOF5-CmccpXQHRb_wDazvDrvQ,6895
97
- reconcile/queries.py,sha256=of54G-Vqy-_deKdZcWs-B9LwxIdhQFUlQ198pBGCp3g,51488
97
+ reconcile/queries.py,sha256=3nqVO5zv_TsnCK4jsJfF8N4Z7ADfgtUaTD2nv8EzlMs,51506
98
98
  reconcile/query_validator.py,sha256=MSh5pKLBksws4AqfuvT8nrIGucIbqX-IOzYyPYTLO7k,1491
99
99
  reconcile/requests_sender.py,sha256=914iluuF4UVgG3VyxxtnHOu4yf6YKS2fIy6PViSsFTQ,3875
100
100
  reconcile/resource_scraper.py,sha256=znXCHrU7YwPfKuxGBiUrV7T1tYtn4vlz9qmZlfy6Flg,2307
@@ -215,7 +215,7 @@ reconcile/glitchtip_project_alerts/integration.py,sha256=BgMx-NyV9mTuv7Sotb2OioC
215
215
  reconcile/glitchtip_project_dsn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
216
216
  reconcile/glitchtip_project_dsn/integration.py,sha256=2iugub-kHYkHNK33n0v9_TeWonuxCPah_VkoTPvaajE,8077
217
217
  reconcile/gql_definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
- reconcile/gql_definitions/introspection.json,sha256=GoGW7nosXPWvZYQsq6m8nYVIJHIO5hBh9upeFhS_M_k,2216702
218
+ reconcile/gql_definitions/introspection.json,sha256=WWQ1wCVxaP7aNIvIttoxVfFtZkODTopzAUv_bCZVNR4,2217602
219
219
  reconcile/gql_definitions/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
220
220
  reconcile/gql_definitions/acs/acs_instances.py,sha256=L91WW9LbhJbBSrECqShQpFtjoBOsmNIYLRpMbx1io5o,2181
221
221
  reconcile/gql_definitions/acs/acs_policies.py,sha256=bN5i4mks10Z23KJSj7jqp966Osq2dps4d-sPH9gjxEA,7008
@@ -584,7 +584,7 @@ reconcile/utils/external_resources.py,sha256=YzTb0xAcNdmKO326mGQy7BmST56CZcdru4l
584
584
  reconcile/utils/filtering.py,sha256=S4PbMHuFr3ED0P2Q_ea5CAaB7FimI62B-F5YTaKrphA,402
585
585
  reconcile/utils/git.py,sha256=wzVIYAeKlMGW538U1mkJWUI6h_mFRUY4lawh2AR8hw4,2345
586
586
  reconcile/utils/github_api.py,sha256=y3fxty7FKvfhdzfHgGSaIstL6A_Y2loUcMiyIK5TMDg,2750
587
- reconcile/utils/gitlab_api.py,sha256=SR0graae7UBZyLLKA7yGsOqKm7dXFe5cexcaa1SokQo,28486
587
+ reconcile/utils/gitlab_api.py,sha256=N1QdgF6Jdhg_nF03vtfouvqXP7MVfkPRnq7Q3z169kM,28662
588
588
  reconcile/utils/gpg.py,sha256=EKG7_fdMv8BMlV5yUdPiqoTx-KrzmVSEAl2sLkaKwWI,1123
589
589
  reconcile/utils/gql.py,sha256=C0thIm_k9MBldfqwHzyqtYZk9sIvMdm9IbbnXLGwjD8,14158
590
590
  reconcile/utils/grouping.py,sha256=vr9SFHZ7bqmHYrvYcEZt-Er3-yQYfAAdq5sHLZVmXPY,456
@@ -727,7 +727,7 @@ reconcile/utils/runtime/sharding.py,sha256=r0ieUtNed7NvknSw6qQrCkKpVXE1shuHGnfFc
727
727
  reconcile/utils/saasherder/__init__.py,sha256=3U8plqMAPRE1kjwZ5YnIsYsggTf4_gS7flRUEuXVBAs,343
728
728
  reconcile/utils/saasherder/interfaces.py,sha256=C2wrw34OXypshVocAsPrVZsSHptgw4g9u7Haa2wulZQ,9087
729
729
  reconcile/utils/saasherder/models.py,sha256=z8ln03zi2a8cu716NcNUDHp8Dv1VcVbhqdWVxCl7x9A,10148
730
- reconcile/utils/saasherder/saasherder.py,sha256=6d3ZK_hNfv6hVxULh3BhXzTRMXoTR2YvjcoQQ1oKF8M,86099
730
+ reconcile/utils/saasherder/saasherder.py,sha256=-Q1cMXuclv-1F1cMoy7ntzL_1_0C-X0q9sD6otfKbyw,86222
731
731
  reconcile/utils/terraform/__init__.py,sha256=zNbiyTWo35AT1sFTElL2j_AA0jJ_yWE_bfFn-nD2xik,250
732
732
  reconcile/utils/terraform/config.py,sha256=5UVrd563TMcvi4ooa5JvWVDW1I3bIWg484u79evfV_8,164
733
733
  reconcile/utils/terraform/config_client.py,sha256=gRL1rQ0AqvShei_rcGqC3HDYGskOFKE1nPrJyJE9yno,4676
@@ -773,7 +773,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
773
773
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
774
774
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
775
775
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
776
- qontract_reconcile-0.10.2.dev32.dist-info/METADATA,sha256=Aj39BfBFhSBY0PanTtlnlcY2FpVeG_k_VWiCOo0Enbc,24665
777
- qontract_reconcile-0.10.2.dev32.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
778
- qontract_reconcile-0.10.2.dev32.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
779
- qontract_reconcile-0.10.2.dev32.dist-info/RECORD,,
776
+ qontract_reconcile-0.10.2.dev34.dist-info/METADATA,sha256=gEINV-7WHJvNpdhF_iG3Mur5JD5aVPWJ_q2CghjgcQ4,24665
777
+ qontract_reconcile-0.10.2.dev34.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
778
+ qontract_reconcile-0.10.2.dev34.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
779
+ qontract_reconcile-0.10.2.dev34.dist-info/RECORD,,
@@ -49,6 +49,7 @@ from reconcile.utils.mr.labels import (
49
49
  prioritized_approval_label,
50
50
  )
51
51
  from reconcile.utils.sharding import is_in_shard
52
+ from reconcile.utils.state import State, init_state
52
53
 
53
54
  MERGE_LABELS_PRIORITY = [
54
55
  prioritized_approval_label(p.value) for p in ChangeTypePriority
@@ -71,7 +72,6 @@ QONTRACT_INTEGRATION = "gitlab-housekeeping"
71
72
  DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
72
73
  EXPIRATION_DATE_FORMAT = "%Y-%m-%d"
73
74
 
74
-
75
75
  merged_merge_requests = Counter(
76
76
  name="qontract_reconcile_merged_merge_requests",
77
77
  documentation="Number of merge requests that have been successfully merged in a repository",
@@ -156,16 +156,12 @@ def get_timed_out_pipelines(
156
156
 
157
157
  def clean_pipelines(
158
158
  dry_run: bool,
159
- gl_instance: str,
160
- gl_project_id: int,
161
- gl_settings: str,
159
+ gl: GitLabApi,
160
+ fork_project_id: int,
162
161
  pipelines: list[dict],
163
162
  ) -> None:
164
163
  if not dry_run:
165
- with GitLabApi(
166
- gl_instance, project_id=gl_project_id, settings=gl_settings
167
- ) as gl:
168
- gl_piplelines = gl.project.pipelines
164
+ gl_piplelines = gl.get_project_by_id(fork_project_id).pipelines
169
165
 
170
166
  for p in pipelines:
171
167
  logging.info(["canceling", p["web_url"]])
@@ -179,6 +175,66 @@ def clean_pipelines(
179
175
  )
180
176
 
181
177
 
178
+ def verify_on_demand_tests(
179
+ dry_run: bool,
180
+ mr: ProjectMergeRequest,
181
+ must_pass: Iterable[str],
182
+ gl: GitLabApi,
183
+ state: State,
184
+ ) -> bool:
185
+ """
186
+ Check if MR has passed all necessary test jobs and add comments to indicate test results.
187
+ """
188
+ pipelines = gl.get_merge_request_pipelines(mr)
189
+ running_pipelines = [p for p in pipelines if p["status"] == "running"]
190
+ if running_pipelines:
191
+ # wait for pipelines complate
192
+ return False
193
+
194
+ commit = next(mr.commits())
195
+ fork_project = gl.get_project_by_id(mr.source_project_id)
196
+ statuses = fork_project.commits.get(commit.id).statuses.list()
197
+ test_state = {s.name: s.status for s in statuses}
198
+ remaining_tests = [t for t in must_pass if test_state.get(t) != "success"]
199
+ state_key = f"{gl.project.path_with_namespace}/{mr.iid}/{commit.id}"
200
+ # only add comment when state changes
201
+ state_change = state.get(state_key, None) != remaining_tests
202
+
203
+ if remaining_tests:
204
+ logging.info([
205
+ "on-demand tests",
206
+ "add comment",
207
+ gl.project.name,
208
+ mr.iid,
209
+ commit.id,
210
+ ])
211
+ if not dry_run and state_change:
212
+ markdown_report = (
213
+ f"On-demand Tests: \n\n For latest [commit]({commit.web_url}) You will need to pass following test jobs to get this MR merged.\n\n"
214
+ f"Add comment with `/test [test_name]` to trigger the tests.\n\n"
215
+ )
216
+ markdown_report += f"* {', '.join(remaining_tests)}\n"
217
+ gl.delete_merge_request_comments(mr, startswith="On-demand Tests:")
218
+ gl.add_comment_to_merge_request(mr, markdown_report)
219
+ state.add(state_key, remaining_tests, force=True)
220
+ return False
221
+ else:
222
+ # no remain_tests, pass the check
223
+ logging.info([
224
+ "on-demand tests",
225
+ "check pass",
226
+ gl.project.name,
227
+ mr.iid,
228
+ commit.id,
229
+ ])
230
+ if not dry_run and state_change:
231
+ markdown_report = f"On-demand Tests: \n\n All necessary tests have paased for latest [commit]({commit.web_url})\n"
232
+ gl.delete_merge_request_comments(mr, startswith="On-demand Tests:")
233
+ gl.add_comment_to_merge_request(mr, markdown_report)
234
+ state.add(state_key, remaining_tests, force=True)
235
+ return True
236
+
237
+
182
238
  def close_item(
183
239
  dry_run: bool,
184
240
  gl: GitLabApi,
@@ -291,6 +347,7 @@ def is_rebased(mr, gl: GitLabApi) -> bool:
291
347
  def get_merge_requests(
292
348
  dry_run: bool,
293
349
  gl: GitLabApi,
350
+ state: State,
294
351
  users_allowed_to_label: Iterable[str] | None = None,
295
352
  ) -> list[dict[str, Any]]:
296
353
  mrs = gl.get_merge_requests(state=MRState.OPENED)
@@ -298,6 +355,7 @@ def get_merge_requests(
298
355
  dry_run=dry_run,
299
356
  gl=gl,
300
357
  project_merge_requests=mrs,
358
+ state=state,
301
359
  users_allowed_to_label=users_allowed_to_label,
302
360
  )
303
361
 
@@ -306,7 +364,9 @@ def preprocess_merge_requests(
306
364
  dry_run: bool,
307
365
  gl: GitLabApi,
308
366
  project_merge_requests: list[ProjectMergeRequest],
367
+ state: State,
309
368
  users_allowed_to_label: Iterable[str] | None = None,
369
+ must_pass: Iterable[str] | None = None,
310
370
  ) -> list[dict[str, Any]]:
311
371
  results = []
312
372
  for mr in project_merge_requests:
@@ -320,6 +380,15 @@ def preprocess_merge_requests(
320
380
  if len(mr.commits()) == 0:
321
381
  continue
322
382
 
383
+ if must_pass and not verify_on_demand_tests(
384
+ dry_run=dry_run,
385
+ mr=mr,
386
+ must_pass=must_pass,
387
+ gl=gl,
388
+ state=state,
389
+ ):
390
+ continue
391
+
323
392
  labels = set(mr.labels)
324
393
  if not labels:
325
394
  continue
@@ -402,13 +471,18 @@ def rebase_merge_requests(
402
471
  rebase_limit,
403
472
  pipeline_timeout=None,
404
473
  wait_for_pipeline=False,
405
- gl_instance=None,
406
- gl_settings=None,
407
474
  users_allowed_to_label=None,
475
+ state=None,
408
476
  ):
409
477
  rebases = 0
410
478
  merge_requests = [
411
- item["mr"] for item in get_merge_requests(dry_run, gl, users_allowed_to_label)
479
+ item["mr"]
480
+ for item in get_merge_requests(
481
+ dry_run=dry_run,
482
+ gl=gl,
483
+ state=state,
484
+ users_allowed_to_label=users_allowed_to_label,
485
+ )
412
486
  ]
413
487
  for mr in merge_requests:
414
488
  if is_rebased(mr, gl):
@@ -421,11 +495,10 @@ def rebase_merge_requests(
421
495
  timed_out_pipelines = get_timed_out_pipelines(pipelines, pipeline_timeout)
422
496
  if timed_out_pipelines:
423
497
  clean_pipelines(
424
- dry_run,
425
- gl_instance,
426
- mr.source_project_id,
427
- gl_settings,
428
- timed_out_pipelines,
498
+ dry_run=dry_run,
499
+ gl=gl,
500
+ fork_project_id=mr.source_project_id,
501
+ pipelines=timed_out_pipelines,
429
502
  )
430
503
 
431
504
  if wait_for_pipeline:
@@ -474,15 +547,20 @@ def merge_merge_requests(
474
547
  pipeline_timeout=None,
475
548
  insist=False,
476
549
  wait_for_pipeline=False,
477
- gl_instance=None,
478
- gl_settings=None,
479
550
  users_allowed_to_label=None,
551
+ must_pass=None,
552
+ state=None,
480
553
  ):
481
554
  merges = 0
482
555
  if reload_toggle.reload:
483
556
  project_merge_requests = gl.get_merge_requests(state=MRState.OPENED)
484
557
  merge_requests = preprocess_merge_requests(
485
- dry_run, gl, project_merge_requests, users_allowed_to_label
558
+ dry_run=dry_run,
559
+ gl=gl,
560
+ project_merge_requests=project_merge_requests,
561
+ state=state,
562
+ users_allowed_to_label=users_allowed_to_label,
563
+ must_pass=must_pass,
486
564
  )
487
565
  merge_requests_waiting.labels(gl.project.id).set(len(merge_requests))
488
566
 
@@ -500,11 +578,10 @@ def merge_merge_requests(
500
578
  timed_out_pipelines = get_timed_out_pipelines(pipelines, pipeline_timeout)
501
579
  if timed_out_pipelines:
502
580
  clean_pipelines(
503
- dry_run,
504
- gl_instance,
505
- mr.source_project_id,
506
- gl_settings,
507
- timed_out_pipelines,
581
+ dry_run=dry_run,
582
+ gl=gl,
583
+ fork_project_id=mr.source_project_id,
584
+ pipelines=timed_out_pipelines,
508
585
  )
509
586
 
510
587
  if wait_for_pipeline:
@@ -582,6 +659,7 @@ def run(dry_run, wait_for_pipeline):
582
659
  repos = queries.get_repos_gitlab_housekeeping(server=instance["url"])
583
660
  repos = [r for r in repos if is_in_shard(r["url"])]
584
661
  app_sre_usernames: Set[str] = set()
662
+ state = init_state(QONTRACT_INTEGRATION)
585
663
 
586
664
  for repo in repos:
587
665
  hk = repo["housekeeping"]
@@ -624,6 +702,7 @@ def run(dry_run, wait_for_pipeline):
624
702
  ]
625
703
  reload_toggle = ReloadToggle(reload=False)
626
704
  rebase = hk.get("rebase")
705
+ must_pass = hk.get("must_pass")
627
706
  try:
628
707
  merge_merge_requests(
629
708
  dry_run,
@@ -636,9 +715,9 @@ def run(dry_run, wait_for_pipeline):
636
715
  pipeline_timeout,
637
716
  insist=True,
638
717
  wait_for_pipeline=wait_for_pipeline,
639
- gl_instance=instance,
640
- gl_settings=settings,
641
718
  users_allowed_to_label=users_allowed_to_label,
719
+ must_pass=must_pass,
720
+ state=state,
642
721
  )
643
722
  except Exception:
644
723
  logging.error(
@@ -654,9 +733,9 @@ def run(dry_run, wait_for_pipeline):
654
733
  app_sre_usernames,
655
734
  pipeline_timeout,
656
735
  wait_for_pipeline=wait_for_pipeline,
657
- gl_instance=instance,
658
- gl_settings=settings,
659
736
  users_allowed_to_label=users_allowed_to_label,
737
+ must_pass=must_pass,
738
+ state=state,
660
739
  )
661
740
  if rebase:
662
741
  rebase_merge_requests(
@@ -665,7 +744,6 @@ def run(dry_run, wait_for_pipeline):
665
744
  limit,
666
745
  pipeline_timeout=pipeline_timeout,
667
746
  wait_for_pipeline=wait_for_pipeline,
668
- gl_instance=instance,
669
- gl_settings=settings,
670
747
  users_allowed_to_label=users_allowed_to_label,
748
+ state=state,
671
749
  )
@@ -20519,6 +20519,26 @@
20519
20519
  },
20520
20520
  "isDeprecated": false,
20521
20521
  "deprecationReason": null
20522
+ },
20523
+ {
20524
+ "name": "must_pass",
20525
+ "description": null,
20526
+ "args": [],
20527
+ "type": {
20528
+ "kind": "LIST",
20529
+ "name": null,
20530
+ "ofType": {
20531
+ "kind": "NON_NULL",
20532
+ "name": null,
20533
+ "ofType": {
20534
+ "kind": "SCALAR",
20535
+ "name": "String",
20536
+ "ofType": null
20537
+ }
20538
+ }
20539
+ },
20540
+ "isDeprecated": false,
20541
+ "deprecationReason": null
20522
20542
  }
20523
20543
  ],
20524
20544
  "inputFields": null,
reconcile/queries.py CHANGED
@@ -1648,6 +1648,7 @@ APPS_QUERY = """
1648
1648
  }
1649
1649
  }
1650
1650
  }
1651
+ must_pass
1651
1652
  }
1652
1653
  jira {
1653
1654
  serverUrl
@@ -394,6 +394,10 @@ class GitLabApi: # pylint: disable=too-many-public-methods
394
394
  project = None
395
395
  return project
396
396
 
397
+ def get_project_by_id(self, project_id: int) -> Project:
398
+ gitlab_request.labels(integration=INTEGRATION_NAME).inc()
399
+ return self.gl.projects.get(project_id)
400
+
397
401
  def get_issues(self, state):
398
402
  return self.get_items(self.project.issues.list, state=state)
399
403
 
@@ -1361,8 +1361,9 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1361
1361
  except ResourceKeyExistsError:
1362
1362
  ri.register_error()
1363
1363
  msg = (
1364
- f"[{spec.cluster}/{spec.namespace}] desired item "
1365
- + f"already exists: {resource['kind']}/{resource['metadata']['name']}. "
1364
+ f"[{spec.cluster}/{spec.namespace}] Duplicate resources in your deployment template detected. "
1365
+ + "The following is defined multiple times in your deployment template: "
1366
+ + f"{resource['kind']}/{resource['metadata']['name']}. "
1366
1367
  + f"saas file name: {spec.saas_file_name}, "
1367
1368
  + "resource template name: "
1368
1369
  + f"{spec.resource_template_name}."