|
20 | 20 | from github_pr_comment.backend_fingerprint import fingerprint
|
21 | 21 | from github_pr_comment.cmp import plan_cmp, remove_warnings, remove_unchanged_attributes
|
22 | 22 | from github_pr_comment.comment import find_comment, TerraformComment, update_comment, serialize, deserialize
|
23 |
| -from github_pr_comment.hash import comment_hash, plan_hash |
| 23 | +from github_pr_comment.hash import comment_hash, plan_hash, plan_out_hash |
24 | 24 | from github_pr_comment.plan_formatting import format_diff
|
25 | 25 | from plan_renderer.outputs import render_outputs
|
26 | 26 | from plan_renderer.variables import render_argument_list, Sensitive
|
@@ -382,6 +382,9 @@ def is_approved(proposed_plan: str, comment: TerraformComment) -> bool:
|
382 | 382 | debug('Approving plan based on plan text')
|
383 | 383 | return plan_cmp(proposed_plan, comment.body)
|
384 | 384 |
|
| 385 | +def is_approved_binary_plan(plan_path: str, comment: TerraformComment) -> bool: |
| 386 | + return plan_out_hash(os.environ['PLAN_OUT'], comment.issue_url) == comment.headers['plan_out_hash'] |
| 387 | + |
385 | 388 | def truncate(text: str, max_size: int, too_big_message: str) -> str:
|
386 | 389 | lines = []
|
387 | 390 | total_size = 0
|
@@ -504,6 +507,12 @@ def main() -> int:
|
504 | 507 |
|
505 | 508 | headers = comment.headers.copy()
|
506 | 509 | headers['plan_job_ref'] = job_workflow_ref()
|
| 510 | + |
| 511 | + if os.environ.get('PLAN_OUT', ''): |
| 512 | + headers['plan_out_hash'] = plan_out_hash(os.environ['PLAN_OUT'], comment.issue_url) |
| 513 | + elif 'plan_out_hash' in headers: |
| 514 | + del headers['plan_out_hash'] |
| 515 | + |
507 | 516 | headers['plan_hash'] = plan_hash(body, comment.issue_url)
|
508 | 517 | format_type = os.environ.get('TF_ACTIONS_PLAN_FORMAT', 'diff')
|
509 | 518 | headers['plan_text_format'], plan_text = format_plan_text(body, format_type)
|
@@ -565,6 +574,29 @@ def main() -> int:
|
565 | 574 | with open(sys.argv[2], 'w') as f:
|
566 | 575 | f.write(comment.body)
|
567 | 576 |
|
| 577 | + elif sys.argv[1] == 'approved-binary': |
| 578 | + |
| 579 | + if comment.comment_url is None: |
| 580 | + sys.stdout.write("Plan not found on PR\n") |
| 581 | + sys.stdout.write("Generate the plan first using the dflook/terraform-plan action. Alternatively set the auto_approve input to 'true'\n") |
| 582 | + sys.stdout.write("If dflook/terraform-plan was used with add_github_comment set to changes-only, this may mean the plan has since changed to include changes\n") |
| 583 | + output('failure-reason', 'plan-changed') |
| 584 | + sys.exit(1) |
| 585 | + |
| 586 | + if not is_approved_binary_plan(sys.argv[2], comment): |
| 587 | + |
| 588 | + sys.stdout.write("Not applying the plan - it has changed from the plan on the PR\n") |
| 589 | + sys.stdout.write("The plan on the PR must be up to date. Alternatively, set the auto_approve input to 'true' to apply outdated plans\n") |
| 590 | + comment = update_comment(github, comment, status=f':x: Plan not applied in {job_markdown_ref()} (Plan has changed)') |
| 591 | + |
| 592 | + if plan_ref := comment.headers.get('plan_job_ref'): |
| 593 | + sys.stdout.write(f'\nThis plan is different to the plan generated by the dflook/terraform-plan action in {plan_ref}\n') |
| 594 | + |
| 595 | + output('failure-reason', 'plan-changed') |
| 596 | + |
| 597 | + step_cache['comment'] = serialize(comment) |
| 598 | + return 1 |
| 599 | + |
568 | 600 | elif sys.argv[1] == 'approved':
|
569 | 601 |
|
570 | 602 | proposed_plan = remove_warnings(remove_unchanged_attributes(Path(sys.argv[2]).read_text().strip()))
|
|
0 commit comments