qontract-reconcile 0.10.2.dev33__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.
- {qontract_reconcile-0.10.2.dev33.dist-info → qontract_reconcile-0.10.2.dev34.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev33.dist-info → qontract_reconcile-0.10.2.dev34.dist-info}/RECORD +8 -8
- reconcile/gitlab_housekeeping.py +108 -30
- reconcile/gql_definitions/introspection.json +20 -0
- reconcile/queries.py +1 -0
- reconcile/utils/gitlab_api.py +4 -0
- {qontract_reconcile-0.10.2.dev33.dist-info → qontract_reconcile-0.10.2.dev34.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev33.dist-info → qontract_reconcile-0.10.2.dev34.dist-info}/entry_points.txt +0 -0
{qontract_reconcile-0.10.2.dev33.dist-info → qontract_reconcile-0.10.2.dev34.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.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
|
{qontract_reconcile-0.10.2.dev33.dist-info → qontract_reconcile-0.10.2.dev34.dist-info}/RECORD
RENAMED
@@ -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=
|
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=
|
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=
|
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=
|
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
|
@@ -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.
|
777
|
-
qontract_reconcile-0.10.2.
|
778
|
-
qontract_reconcile-0.10.2.
|
779
|
-
qontract_reconcile-0.10.2.
|
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,,
|
reconcile/gitlab_housekeeping.py
CHANGED
@@ -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
|
-
|
160
|
-
|
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
|
-
|
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"]
|
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
|
-
|
426
|
-
mr.source_project_id,
|
427
|
-
|
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,
|
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
|
-
|
505
|
-
mr.source_project_id,
|
506
|
-
|
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
reconcile/utils/gitlab_api.py
CHANGED
@@ -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
|
|
{qontract_reconcile-0.10.2.dev33.dist-info → qontract_reconcile-0.10.2.dev34.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|