qontract-reconcile 0.10.1rc216__py3-none-any.whl → 0.10.1rc218__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.1rc216.dist-info → qontract_reconcile-0.10.1rc218.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc216.dist-info → qontract_reconcile-0.10.1rc218.dist-info}/RECORD +12 -12
- reconcile/change_owners/change_owners.py +2 -3
- reconcile/gitlab_fork_compliance.py +1 -1
- reconcile/gitlab_housekeeping.py +12 -29
- reconcile/gitlab_owners.py +2 -2
- reconcile/test/test_gitlab_housekeeping.py +1 -3
- reconcile/utils/gitlab_api.py +24 -17
- reconcile/utils/terraform_client.py +98 -30
- {qontract_reconcile-0.10.1rc216.dist-info → qontract_reconcile-0.10.1rc218.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc216.dist-info → qontract_reconcile-0.10.1rc218.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc216.dist-info → qontract_reconcile-0.10.1rc218.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc216.dist-info → qontract_reconcile-0.10.1rc218.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.1rc218
|
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
|
{qontract_reconcile-0.10.1rc216.dist-info → qontract_reconcile-0.10.1rc218.dist-info}/RECORD
RENAMED
@@ -23,12 +23,12 @@ reconcile/github_repo_invites.py,sha256=n5_gUcGotBBnvx3F41auCqX_gbZKut_XRTWGlsrK
|
|
23
23
|
reconcile/github_repo_permissions_validator.py,sha256=dcbXdUx6imjNchjp3pg9-z1i7lFEGOr_28GvsiwO5Xw,1734
|
24
24
|
reconcile/github_users.py,sha256=nfTq78QRONIfDVj-5O3bD6psllJjzWFnog-EJ1WqFPU,3672
|
25
25
|
reconcile/github_validator.py,sha256=cVTVxJIGR4a1Jz8wrdXEAb_CMpXUzvykVmUURX4cook,917
|
26
|
-
reconcile/gitlab_fork_compliance.py,sha256=
|
27
|
-
reconcile/gitlab_housekeeping.py,sha256=
|
26
|
+
reconcile/gitlab_fork_compliance.py,sha256=nLrwtoj5bTlaMutpKUc4R6lqZtpqB0otcIOMNfIxUsU,4223
|
27
|
+
reconcile/gitlab_housekeeping.py,sha256=9rZZ97R0eMHW67YksHM2ATmHLQoq62bmimfUSeI_M0Q,21276
|
28
28
|
reconcile/gitlab_labeler.py,sha256=avNifNROyGhko6WDAaTH3ixA86qzxdBsSk1IisRoxao,4635
|
29
29
|
reconcile/gitlab_members.py,sha256=M6LwFOrwgvl1NNdOJa1mrQFUon-bEVv1AyhGeLed454,8443
|
30
30
|
reconcile/gitlab_mr_sqs_consumer.py,sha256=O46mdziPgGOndbU-0_UJKJVUaiEoVzJPEgKm4_UvYoI,2571
|
31
|
-
reconcile/gitlab_owners.py,sha256=
|
31
|
+
reconcile/gitlab_owners.py,sha256=n5_pX841OST3dGfMUEXtqIcyqV4NjzSEvh2E0up7Y1c,13876
|
32
32
|
reconcile/gitlab_permissions.py,sha256=ciEKj_wnRbS_vs_ZwcUeD6HkWVe3osAuotFqJSmvd94,1638
|
33
33
|
reconcile/gitlab_projects.py,sha256=K3tFf_aD1W4Ijp5q-9Qek3kwFGEWPcZ1kd7tzFJ4GyQ,1781
|
34
34
|
reconcile/integrations_manager.py,sha256=zUCh1bYrnNoT_6SSQO-yYA2QdDxfCuzwb1tjcByIOaE,8885
|
@@ -126,7 +126,7 @@ reconcile/aws_ami_cleanup/integration.py,sha256=E--71oG3HhrUgBieAf51wNE1N1xtsuOa
|
|
126
126
|
reconcile/change_owners/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
127
127
|
reconcile/change_owners/approver.py,sha256=L7XJWJ-rgn8BOmeMb6lBDV8lHFCUaNoHGDSD7OH03vA,2244
|
128
128
|
reconcile/change_owners/bundle.py,sha256=Bh9v08tIw8TieG4EE9kVI9iFN1sW7Z6Zyb8DTbsv6F0,5371
|
129
|
-
reconcile/change_owners/change_owners.py,sha256=
|
129
|
+
reconcile/change_owners/change_owners.py,sha256=2Z182TKDpcvPwLtyTDbHdA11IMXuWrBSQxUoeUmdXbU,13268
|
130
130
|
reconcile/change_owners/change_types.py,sha256=3UFjyuTW0-si9pvaKfqGCWGwTiBVi7n9HZnfHk_x5iY,31486
|
131
131
|
reconcile/change_owners/changes.py,sha256=KrtaDTM_jxyYsHPUEjJCWJr186L4a-NsxCnztdSUry4,16912
|
132
132
|
reconcile/change_owners/decision.py,sha256=JBMhNG8UqaKzTZgaKQLTPn8KW8CSEvq8clP76ZfQT6s,6381
|
@@ -330,7 +330,7 @@ reconcile/test/test_closedbox_endpoint_monitoring.py,sha256=isMHYwRWMFARU2nbJgbl
|
|
330
330
|
reconcile/test/test_gabi_authorized_users.py,sha256=vyO9-1w6OtGQJvJuTazJMVAjQbUd90sgsHcMyIYkUC8,2483
|
331
331
|
reconcile/test/test_github_org.py,sha256=j3KeB4OnSln1gm2hidce49xdMru-j75NS3cM-AEgzZc,4511
|
332
332
|
reconcile/test/test_github_repo_invites.py,sha256=QJ0VFk5B59rx4XtHoT6XOGWw9xRIZMen_cgtviN_Vi8,3419
|
333
|
-
reconcile/test/test_gitlab_housekeeping.py,sha256=
|
333
|
+
reconcile/test/test_gitlab_housekeeping.py,sha256=7EpWikaWJH52IlA2PZs7vz4GEgID-_dfZBpJNlwhMSw,10018
|
334
334
|
reconcile/test/test_gitlab_labeler.py,sha256=vFLUJXSIaCduj6wSqgw7Fg7FhlopaDnYI5SLzNHtLoY,4362
|
335
335
|
reconcile/test/test_gitlab_members.py,sha256=dP_dm-1THba9Vyzcq-EX1tdmBoX2hq8R-MY4Uqknq5s,9896
|
336
336
|
reconcile/test/test_instrumented_wrappers.py,sha256=CZzhnQH0c4i7-Rxjg7-0dfFMvVPegLHL46z5NHOOCwo,608
|
@@ -464,7 +464,7 @@ reconcile/utils/filtering.py,sha256=dw7Ok7HXjZb0ruvCWHFh194rtunX1COLDTRnNfOpwQU,
|
|
464
464
|
reconcile/utils/git.py,sha256=kgjN93MMB5mnkuNb1n53f5kldGGf5u0pBHj9YJbiE_c,1455
|
465
465
|
reconcile/utils/git_secrets.py,sha256=897nRs7tycA3m7YYeVEbzOhI8RFrI9IJT2E0di1eJhc,1956
|
466
466
|
reconcile/utils/github_api.py,sha256=6gdlKK0W3vZpxbbtOcohRgvZ4YkiSki7Gxdb16goHPo,2316
|
467
|
-
reconcile/utils/gitlab_api.py,sha256=
|
467
|
+
reconcile/utils/gitlab_api.py,sha256=v_cf2xt7trUlYFRYsuJ9ZkCEtFiO_0coAjlxBjjHedc,24839
|
468
468
|
reconcile/utils/gpg.py,sha256=EKG7_fdMv8BMlV5yUdPiqoTx-KrzmVSEAl2sLkaKwWI,1123
|
469
469
|
reconcile/utils/gql.py,sha256=KogegLFsvjiTWqPBDSb4qJToISrdsDeLJ3gkHwi1DQ8,11672
|
470
470
|
reconcile/utils/helm.py,sha256=IWlB_LrBK6ydwNQuZP2aMJGrtQw0lW7qdFqSrd3r8lg,1321
|
@@ -508,7 +508,7 @@ reconcile/utils/sqs_gateway.py,sha256=gFl9DM4DmGnptuxTOe4lS3YTyE80eSAvK42ljS8h4d
|
|
508
508
|
reconcile/utils/state.py,sha256=_SmE7fOEReET3iy9jRQ1pyuaJebg5962Zs9Iy1dzTJk,9530
|
509
509
|
reconcile/utils/structs.py,sha256=LcbLEg8WxfRqM6nW7NhcWN0YeqF7SQzxOgntmLs1SgY,352
|
510
510
|
reconcile/utils/template.py,sha256=wTvRU4AnAV_o042tD4Mwls2dwWMuk7MKnde3MaCjaYg,331
|
511
|
-
reconcile/utils/terraform_client.py,sha256=
|
511
|
+
reconcile/utils/terraform_client.py,sha256=Sy125P0LybUUkI9_BTK00m_Q4-Oc6GOAPBAQ6aRirCQ,33329
|
512
512
|
reconcile/utils/terrascript_aws_client.py,sha256=FP4LSlKynxAjdEqfotsML7fg6lFyKktXcWQrLghFuU0,260201
|
513
513
|
reconcile/utils/throughput.py,sha256=iP4UWAe2LVhDo69mPPmgo9nQ7RxHD6_GS8MZe-aSiuM,344
|
514
514
|
reconcile/utils/unleash.py,sha256=QGANGA8BHG7oC_bt39c2M7uRa2ycjzmahN8_m7Zovos,3094
|
@@ -577,8 +577,8 @@ tools/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
577
577
|
tools/test/test_qontract_cli.py,sha256=awwTHEc2DWlykuqGIYM0WOBoSL0KRnOraCLk3C7izis,1401
|
578
578
|
tools/test/test_sd_app_sre_alert_report.py,sha256=JeLhgzpKCPgLvptwg_4ZvJHLVWKNG1T5845HXTkMBxA,1826
|
579
579
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
580
|
-
qontract_reconcile-0.10.
|
581
|
-
qontract_reconcile-0.10.
|
582
|
-
qontract_reconcile-0.10.
|
583
|
-
qontract_reconcile-0.10.
|
584
|
-
qontract_reconcile-0.10.
|
580
|
+
qontract_reconcile-0.10.1rc218.dist-info/METADATA,sha256=NYrL4Jw0x9csqevY8_axCXnpuJXmobKSqauptV79qqY,2328
|
581
|
+
qontract_reconcile-0.10.1rc218.dist-info/WHEEL,sha256=AtBG6SXL3KF_v0NxLf0ehyVOh0cold-JbJYXNGorC6Q,92
|
582
|
+
qontract_reconcile-0.10.1rc218.dist-info/entry_points.txt,sha256=ErVY2Jp-0Rtuq5KOtMlW5yvna4nIEuc_1YbEdEdcy9o,301
|
583
|
+
qontract_reconcile-0.10.1rc218.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
584
|
+
qontract_reconcile-0.10.1rc218.dist-info/RECORD,,
|
@@ -372,9 +372,8 @@ def run(
|
|
372
372
|
# e.g. gitlab-housekeeper rejects direct lgtm labels and the review-queue
|
373
373
|
# skips MRs with this label
|
374
374
|
if SELF_SERVICEABLE in labels:
|
375
|
-
gl.
|
376
|
-
|
377
|
-
)
|
375
|
+
merge_request = gl.get_merge_request(gitlab_merge_request_id)
|
376
|
+
gl.remove_label(merge_request, SELF_SERVICEABLE)
|
378
377
|
|
379
378
|
except BaseException:
|
380
379
|
logging.error(traceback.format_exc())
|
@@ -80,7 +80,7 @@ class GitlabForkCompliance:
|
|
80
80
|
# it is set
|
81
81
|
mr_labels = self.gl_cli.get_merge_request_labels(self.mr.iid)
|
82
82
|
if BLOCKED_BOT_ACCESS in mr_labels:
|
83
|
-
self.gl_cli.
|
83
|
+
self.gl_cli.remove_label(self.mr, BLOCKED_BOT_ACCESS)
|
84
84
|
|
85
85
|
sys.exit(self.exit_code)
|
86
86
|
|
reconcile/gitlab_housekeeping.py
CHANGED
@@ -214,8 +214,7 @@ def handle_stale_items(
|
|
214
214
|
now = datetime.utcnow()
|
215
215
|
for item in items:
|
216
216
|
item_iid = item.attributes.get("iid")
|
217
|
-
|
218
|
-
if AUTO_MERGE in item_labels:
|
217
|
+
if AUTO_MERGE in item.labels:
|
219
218
|
if item.merge_status == MRStatus.UNCHECKED:
|
220
219
|
# this call triggers a status recheck
|
221
220
|
item = gl.get_merge_request(item_iid)
|
@@ -227,16 +226,16 @@ def handle_stale_items(
|
|
227
226
|
current_interval = now.date() - update_date.date()
|
228
227
|
if current_interval > timedelta(days=days_interval):
|
229
228
|
# if item does not have 'stale' label - add it
|
230
|
-
if LABEL not in
|
229
|
+
if LABEL not in item.labels:
|
231
230
|
logging.info(["add_label", gl.project.name, item_type, item_iid, LABEL])
|
232
231
|
if not dry_run:
|
233
|
-
gl.add_label(item,
|
232
|
+
gl.add_label(item, LABEL)
|
234
233
|
# if item has 'stale' label - close it
|
235
234
|
else:
|
236
235
|
close_item(dry_run, gl, enable_closing, item_type, item)
|
237
236
|
# if item is under days_interval
|
238
237
|
else:
|
239
|
-
if LABEL not in
|
238
|
+
if LABEL not in item.labels:
|
240
239
|
continue
|
241
240
|
|
242
241
|
# if item has 'stale' label - check the notes
|
@@ -261,7 +260,7 @@ def handle_stale_items(
|
|
261
260
|
["remove_label", gl.project.name, item_type, item_iid, LABEL]
|
262
261
|
)
|
263
262
|
if not dry_run:
|
264
|
-
gl.remove_label(item,
|
263
|
+
gl.remove_label(item, LABEL)
|
265
264
|
|
266
265
|
|
267
266
|
def is_good_to_merge(labels):
|
@@ -277,21 +276,6 @@ def is_rebased(mr, gl: GitLabApi) -> bool:
|
|
277
276
|
return len(result["commits"]) == 0
|
278
277
|
|
279
278
|
|
280
|
-
def get_labels(mr: ProjectMergeRequest, gl: GitLabApi) -> list[str]:
|
281
|
-
"""
|
282
|
-
This function used to contain logic for checking if labels were empty and calling
|
283
|
-
gl.get_merge_request_labels() if they were missing because there were reports of the
|
284
|
-
label attribute being empty sometimes when it shouldn't be. This was an expensive
|
285
|
-
approach, increasing the runtime of the integration by something around 20-30%.
|
286
|
-
Through investigation in APPSRE-6653 it was determined this no longer appears to be
|
287
|
-
an issue.
|
288
|
-
|
289
|
-
This is being left to continue to abstract the way that labels are pulled in case
|
290
|
-
this becomes an issue again in the future.
|
291
|
-
"""
|
292
|
-
return mr.attributes.get("labels")
|
293
|
-
|
294
|
-
|
295
279
|
def get_merge_requests(
|
296
280
|
dry_run: bool,
|
297
281
|
gl: GitLabApi,
|
@@ -324,7 +308,7 @@ def preprocess_merge_requests(
|
|
324
308
|
if len(mr.commits()) == 0:
|
325
309
|
continue
|
326
310
|
|
327
|
-
labels =
|
311
|
+
labels = mr.labels
|
328
312
|
if not labels:
|
329
313
|
continue
|
330
314
|
|
@@ -336,7 +320,7 @@ def preprocess_merge_requests(
|
|
336
320
|
+ "suitable for self serviceable MRs. removing 'lgtm' label"
|
337
321
|
)
|
338
322
|
if not dry_run:
|
339
|
-
gl.
|
323
|
+
gl.remove_label(mr, LGTM)
|
340
324
|
continue
|
341
325
|
|
342
326
|
label_events = gl.get_merge_request_label_events(mr)
|
@@ -366,18 +350,17 @@ def preprocess_merge_requests(
|
|
366
350
|
approved_by = added_by
|
367
351
|
|
368
352
|
for bad_label in labels_by_unauthorized_users - labels_by_authorized_users:
|
369
|
-
if bad_label not in labels:
|
353
|
+
if bad_label not in mr.labels:
|
370
354
|
continue
|
371
355
|
logging.warning(
|
372
356
|
f"[{gl.project.name}/{mr.iid}] someone added a label who "
|
373
357
|
f"isn't allowed. removing label {bad_label}"
|
374
358
|
)
|
375
|
-
# Remove bad_label from the cached labels list. Otherwise, we may face a caching bug
|
376
|
-
labels.remove(bad_label)
|
377
359
|
if not dry_run:
|
378
|
-
|
360
|
+
# TODO: optimize this to remove all bad labels at once
|
361
|
+
gl.remove_label(mr, bad_label)
|
379
362
|
|
380
|
-
if not is_good_to_merge(labels):
|
363
|
+
if not is_good_to_merge(mr.labels):
|
381
364
|
continue
|
382
365
|
|
383
366
|
label_priority = min(
|
@@ -540,7 +523,7 @@ def merge_merge_requests(
|
|
540
523
|
if not dry_run and merges < merge_limit:
|
541
524
|
try:
|
542
525
|
mr.merge()
|
543
|
-
labels =
|
526
|
+
labels = mr.labels
|
544
527
|
merged_merge_requests.labels(
|
545
528
|
project_id=mr.target_project_id,
|
546
529
|
self_service=SELF_SERVICEABLE in labels,
|
reconcile/gitlab_owners.py
CHANGED
@@ -370,7 +370,7 @@ def act(repo, dry_run, instance, settings, defer=None):
|
|
370
370
|
f"- removing approval"
|
371
371
|
]
|
372
372
|
)
|
373
|
-
gitlab_cli.
|
373
|
+
gitlab_cli.remove_label(mr, APPROVED)
|
374
374
|
|
375
375
|
if approval_status["report"] is not None:
|
376
376
|
_LOG.info(
|
@@ -382,7 +382,7 @@ def act(repo, dry_run, instance, settings, defer=None):
|
|
382
382
|
)
|
383
383
|
|
384
384
|
if not dry_run:
|
385
|
-
gitlab_cli.
|
385
|
+
gitlab_cli.remove_label(mr, APPROVED)
|
386
386
|
mr.notes.create({"body": approval_status["report"]})
|
387
387
|
continue
|
388
388
|
|
@@ -177,9 +177,7 @@ def can_be_merged_merge_request() -> ProjectMergeRequest:
|
|
177
177
|
mr.merge_status = "can_be_merged"
|
178
178
|
mr.work_in_progress = False
|
179
179
|
mr.commits.return_value = [create_autospec(ProjectCommit)]
|
180
|
-
mr.
|
181
|
-
"labels": ["lgtm"],
|
182
|
-
}
|
180
|
+
mr.labels = ["lgtm"]
|
183
181
|
mr.iid = 1
|
184
182
|
mr.target_project_id = 3
|
185
183
|
mr.author = {"username": "user"}
|
reconcile/utils/gitlab_api.py
CHANGED
@@ -14,6 +14,7 @@ import gitlab
|
|
14
14
|
import urllib3
|
15
15
|
from gitlab.v4.objects import (
|
16
16
|
CurrentUser,
|
17
|
+
ProjectIssue,
|
17
18
|
ProjectMergeRequest,
|
18
19
|
ProjectMergeRequestNote,
|
19
20
|
)
|
@@ -449,14 +450,6 @@ class GitLabApi: # pylint: disable=too-many-public-methods
|
|
449
450
|
merge_request = self.project.mergerequests.get(mr_id)
|
450
451
|
self.update_labels(merge_request, "merge-request", labels)
|
451
452
|
|
452
|
-
def remove_label_from_merge_request(self, mr_id, label):
|
453
|
-
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
454
|
-
merge_request = self.project.mergerequests.get(mr_id)
|
455
|
-
labels = merge_request.attributes.get("labels")
|
456
|
-
if label in labels:
|
457
|
-
labels.remove(label)
|
458
|
-
self.update_labels(merge_request, "merge-request", labels)
|
459
|
-
|
460
453
|
def add_comment_to_merge_request(self, mr_id, body):
|
461
454
|
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
462
455
|
merge_request = self.project.mergerequests.get(mr_id)
|
@@ -480,20 +473,34 @@ class GitLabApi: # pylint: disable=too-many-public-methods
|
|
480
473
|
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
481
474
|
self.project.labels.create({"name": label_text, "color": label_color})
|
482
475
|
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
476
|
+
@staticmethod
|
477
|
+
def add_label(
|
478
|
+
item: ProjectMergeRequest | ProjectIssue,
|
479
|
+
label: str,
|
480
|
+
):
|
481
|
+
labels = item.labels
|
482
|
+
if label in labels:
|
483
|
+
return
|
488
484
|
labels.append(label)
|
485
|
+
note_body = (
|
486
|
+
f"item has been marked as {label}. " f"to remove say `/{label} cancel`"
|
487
|
+
)
|
489
488
|
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
490
489
|
item.notes.create({"body": note_body})
|
491
|
-
|
490
|
+
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
491
|
+
item.save()
|
492
492
|
|
493
|
-
|
494
|
-
|
493
|
+
@staticmethod
|
494
|
+
def remove_label(
|
495
|
+
item: ProjectMergeRequest | ProjectIssue,
|
496
|
+
label: str,
|
497
|
+
):
|
498
|
+
labels = item.labels
|
499
|
+
if label not in labels:
|
500
|
+
return
|
495
501
|
labels.remove(label)
|
496
|
-
|
502
|
+
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
503
|
+
item.save()
|
497
504
|
|
498
505
|
def update_labels(self, item, item_type, labels):
|
499
506
|
if item_type == "issue":
|
@@ -1,11 +1,17 @@
|
|
1
1
|
import json
|
2
2
|
import logging
|
3
|
+
import os
|
4
|
+
import re
|
3
5
|
import shutil
|
6
|
+
import tempfile
|
7
|
+
import typing
|
4
8
|
from collections import defaultdict
|
5
9
|
from collections.abc import (
|
10
|
+
Generator,
|
6
11
|
Iterable,
|
7
12
|
Mapping,
|
8
13
|
)
|
14
|
+
from contextlib import contextmanager
|
9
15
|
from dataclasses import dataclass
|
10
16
|
from datetime import (
|
11
17
|
datetime,
|
@@ -18,6 +24,7 @@ from typing import (
|
|
18
24
|
cast,
|
19
25
|
)
|
20
26
|
|
27
|
+
import python_terraform
|
21
28
|
from botocore.errorfactory import ClientError
|
22
29
|
from python_terraform import (
|
23
30
|
IsFlagged,
|
@@ -39,6 +46,8 @@ from reconcile.utils.external_resource_spec import (
|
|
39
46
|
|
40
47
|
ALLOWED_TF_SHOW_FORMAT_VERSION = "0.1"
|
41
48
|
DATE_FORMAT = "%Y-%m-%d"
|
49
|
+
PROVIDER_LOG_REGEX = r""".*(?:\[INFO]|\[WARN]|\[ERROR]).+(?:\[WARN]|\[ERROR]).*"""
|
50
|
+
TERRAFORM_LOG_LEVEL = "TRACE" # can change to INFO after tf 0.15
|
42
51
|
|
43
52
|
|
44
53
|
@dataclass
|
@@ -72,6 +81,7 @@ class TerraformClient: # pylint: disable=too-many-public-methods
|
|
72
81
|
self.thread_pool_size = thread_pool_size
|
73
82
|
self._aws_api = aws_api
|
74
83
|
self._log_lock = Lock()
|
84
|
+
self._tf_env_lock = Lock()
|
75
85
|
self.should_apply = False
|
76
86
|
|
77
87
|
self.init_specs()
|
@@ -110,30 +120,76 @@ class TerraformClient: # pylint: disable=too-many-public-methods
|
|
110
120
|
|
111
121
|
def init_specs(self):
|
112
122
|
wd_specs = [{"name": name, "wd": wd} for name, wd in self.working_dirs.items()]
|
113
|
-
|
123
|
+
with self._monkey_patch_terraform_env():
|
124
|
+
results = threaded.run(self.terraform_init, wd_specs, self.thread_pool_size)
|
114
125
|
self.specs = [{"name": name, "tf": tf} for name, tf in results]
|
115
126
|
|
127
|
+
@contextmanager
|
128
|
+
def _monkey_patch_terraform_env(self) -> Generator[None, None, None]:
|
129
|
+
# https://github.com/beelit94/python-terraform/blob/release/0.10.1/python_terraform/__init__.py#L290
|
130
|
+
old_copy = python_terraform.os.environ.copy
|
131
|
+
|
132
|
+
def new_copy():
|
133
|
+
result = old_copy()
|
134
|
+
self._tf_env_lock.release()
|
135
|
+
return result
|
136
|
+
|
137
|
+
python_terraform.os.environ.copy = new_copy
|
138
|
+
|
139
|
+
try:
|
140
|
+
yield
|
141
|
+
finally:
|
142
|
+
python_terraform.os.environ.copy = old_copy
|
143
|
+
|
144
|
+
@contextmanager
|
145
|
+
def _terraform_log_file(self, working_dir: str) -> Generator[typing.IO, None, None]:
|
146
|
+
with tempfile.NamedTemporaryFile(dir=working_dir) as f:
|
147
|
+
# lock is released in terraform method monkey patched in _monkey_patch_terraform_env
|
148
|
+
self._tf_env_lock.acquire()
|
149
|
+
os.environ["TF_LOG"] = TERRAFORM_LOG_LEVEL
|
150
|
+
os.environ["TF_LOG_PATH"] = f.name
|
151
|
+
try:
|
152
|
+
yield f
|
153
|
+
except Exception:
|
154
|
+
# release lock if exception raised before monkey patched os.environ.copy called
|
155
|
+
if self._tf_env_lock.locked():
|
156
|
+
self._tf_env_lock.release()
|
157
|
+
raise
|
158
|
+
finally:
|
159
|
+
with self._tf_env_lock:
|
160
|
+
if "TF_LOG" in os.environ:
|
161
|
+
del os.environ["TF_LOG"]
|
162
|
+
if "TF_LOG_PATH" in os.environ:
|
163
|
+
del os.environ["TF_LOG_PATH"]
|
164
|
+
|
116
165
|
@retry(exceptions=TerraformCommandError)
|
117
166
|
def terraform_init(self, init_spec):
|
118
167
|
name = init_spec["name"]
|
119
168
|
wd = init_spec["wd"]
|
120
|
-
tf = Terraform(working_dir=wd)
|
121
|
-
|
122
|
-
|
169
|
+
tf = Terraform(working_dir=wd, is_env_vars_included=True)
|
170
|
+
with self._terraform_log_file(tf.working_dir) as f:
|
171
|
+
return_code, stdout, stderr = tf.init()
|
172
|
+
log = f.read().decode("utf-8")
|
173
|
+
error = self.check_output(name, "init", return_code, stdout, stderr, log)
|
123
174
|
if error:
|
124
175
|
raise TerraformCommandError(return_code, "init", out=stdout, err=stderr)
|
125
176
|
return name, tf
|
126
177
|
|
127
178
|
def init_outputs(self):
|
128
|
-
|
179
|
+
with self._monkey_patch_terraform_env():
|
180
|
+
results = threaded.run(
|
181
|
+
self.terraform_output, self.specs, self.thread_pool_size
|
182
|
+
)
|
129
183
|
self.outputs = dict(results)
|
130
184
|
|
131
185
|
@retry(exceptions=TerraformCommandError)
|
132
186
|
def terraform_output(self, spec):
|
133
187
|
name = spec["name"]
|
134
188
|
tf = spec["tf"]
|
135
|
-
|
136
|
-
|
189
|
+
with self._terraform_log_file(tf.working_dir) as f:
|
190
|
+
return_code, stdout, stderr = tf.output_cmd(json=IsFlagged)
|
191
|
+
log = f.read().decode("utf-8")
|
192
|
+
error = self.check_output(name, "output", return_code, stdout, stderr, log)
|
137
193
|
no_output_error = (
|
138
194
|
"The module root could not be found. There is nothing to output."
|
139
195
|
)
|
@@ -150,12 +206,13 @@ class TerraformClient: # pylint: disable=too-many-public-methods
|
|
150
206
|
def plan(self, enable_deletion):
|
151
207
|
errors = False
|
152
208
|
disabled_deletions_detected = False
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
209
|
+
with self._monkey_patch_terraform_env():
|
210
|
+
results = threaded.run(
|
211
|
+
self.terraform_plan,
|
212
|
+
self.specs,
|
213
|
+
self.thread_pool_size,
|
214
|
+
enable_deletion=enable_deletion,
|
215
|
+
)
|
159
216
|
|
160
217
|
self.created_users = []
|
161
218
|
for disabled_deletion_detected, created_users, error in results:
|
@@ -172,10 +229,12 @@ class TerraformClient: # pylint: disable=too-many-public-methods
|
|
172
229
|
) -> tuple[bool, list[AccountUser], bool]:
|
173
230
|
name = plan_spec["name"]
|
174
231
|
tf = plan_spec["tf"]
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
232
|
+
with self._terraform_log_file(tf.working_dir) as f:
|
233
|
+
return_code, stdout, stderr = tf.plan(
|
234
|
+
detailed_exitcode=False, parallelism=self.parallelism, out=name
|
235
|
+
)
|
236
|
+
log = f.read().decode("utf-8")
|
237
|
+
error = self.check_output(name, "plan", return_code, stdout, stderr, log)
|
179
238
|
disabled_deletion_detected, created_users = self.log_plan_diff(
|
180
239
|
name, tf, enable_deletion
|
181
240
|
)
|
@@ -370,7 +429,10 @@ class TerraformClient: # pylint: disable=too-many-public-methods
|
|
370
429
|
def apply(self):
|
371
430
|
errors = False
|
372
431
|
|
373
|
-
|
432
|
+
with self._monkey_patch_terraform_env():
|
433
|
+
results = threaded.run(
|
434
|
+
self.terraform_apply, self.specs, self.thread_pool_size
|
435
|
+
)
|
374
436
|
|
375
437
|
for error in results:
|
376
438
|
if error:
|
@@ -380,10 +442,12 @@ class TerraformClient: # pylint: disable=too-many-public-methods
|
|
380
442
|
def terraform_apply(self, apply_spec):
|
381
443
|
name = apply_spec["name"]
|
382
444
|
tf = apply_spec["tf"]
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
445
|
+
with self._terraform_log_file(tf.working_dir) as f:
|
446
|
+
# adding var=None to allow applying the saved plan
|
447
|
+
# https://github.com/beelit94/python-terraform/issues/67
|
448
|
+
return_code, stdout, stderr = tf.apply(dir_or_plan=name, var=None)
|
449
|
+
log = f.read().decode("utf-8")
|
450
|
+
error = self.check_output(name, "apply", return_code, stdout, stderr, log)
|
387
451
|
return error
|
388
452
|
|
389
453
|
def get_terraform_output_secrets(self) -> dict[str, dict[str, dict[str, str]]]:
|
@@ -553,22 +617,26 @@ class TerraformClient: # pylint: disable=too-many-public-methods
|
|
553
617
|
name: str,
|
554
618
|
cmd: str,
|
555
619
|
return_code: int,
|
556
|
-
stdout:
|
557
|
-
stderr:
|
620
|
+
stdout: str,
|
621
|
+
stderr: str,
|
622
|
+
log: str,
|
558
623
|
) -> bool:
|
559
|
-
error_occured =
|
624
|
+
error_occured = return_code != 0
|
560
625
|
line_format = "[{} - {}] {}"
|
561
|
-
stdout, stderr = self.split_to_lines(stdout, stderr)
|
626
|
+
stdout, stderr, log = self.split_to_lines(stdout, stderr, log)
|
627
|
+
provider_log_re = re.compile(PROVIDER_LOG_REGEX)
|
562
628
|
with self._log_lock:
|
563
629
|
for line in stdout:
|
564
630
|
logging.debug(line_format.format(name, cmd, line))
|
565
|
-
if
|
631
|
+
if error_occured:
|
566
632
|
for line in stderr:
|
567
|
-
logging.
|
633
|
+
logging.error(line_format.format(name, cmd, line))
|
568
634
|
else:
|
569
635
|
for line in stderr:
|
570
|
-
logging.
|
571
|
-
|
636
|
+
logging.warning(line_format.format(name, cmd, line))
|
637
|
+
for line in log:
|
638
|
+
if provider_log_re.match(line):
|
639
|
+
logging.warning(line_format.format(name, cmd, line))
|
572
640
|
return error_occured
|
573
641
|
|
574
642
|
@staticmethod
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc216.dist-info → qontract_reconcile-0.10.1rc218.dist-info}/top_level.txt
RENAMED
File without changes
|