22import boto3
33import typer
44import os
5+ import subprocess
56
67from devolv .drift .aws_fetcher import get_aws_policy_document , merge_policy_documents
78from devolv .drift .issues import create_approval_issue , wait_for_sync_choice
1011
1112app = typer .Typer ()
1213
14+ def push_branch (branch_name : str ):
15+ """
16+ Create and push a branch with committed changes.
17+ """
18+ try :
19+ subprocess .run (["git" , "checkout" , "-b" , branch_name ], check = True )
20+ subprocess .run (["git" , "add" , "." ], check = True )
21+ subprocess .run (["git" , "commit" , "-m" , f"Update policy from AWS: { branch_name } " ], check = True )
22+ subprocess .run (["git" , "push" , "--set-upstream" , "origin" , branch_name ], check = True )
23+ typer .echo (f"✅ Pushed branch { branch_name } to origin." )
24+ except subprocess .CalledProcessError as e :
25+ typer .echo (f"❌ Git command failed: { e } " )
26+ raise typer .Exit (1 )
27+
1328@app .command ()
1429def drift (
1530 policy_name : str = typer .Option (..., "--policy-name" , help = "Name of the IAM policy" ),
@@ -23,39 +38,33 @@ def drift(
2338 Detect drift between local policy (file) and AWS policy (ARN),
2439 create GitHub issue for approval, and perform sync based on comment.
2540 """
26- # Auto-detect account ID if not provided
2741 if not account_id :
2842 sts = boto3 .client ("sts" )
2943 account_id = sts .get_caller_identity ()["Account" ]
3044
3145 policy_arn = f"arn:aws:iam::{ account_id } :policy/{ policy_name } "
3246
33- # Load local policy document
3447 try :
3548 with open (policy_file ) as f :
3649 local_doc = json .load (f )
3750 except FileNotFoundError :
3851 typer .echo (f"❌ Local policy file { policy_file } not found." )
3952 raise typer .Exit (1 )
4053
41- # Fetch AWS policy document
4254 aws_doc = get_aws_policy_document (policy_arn )
4355 drift = detect_and_print_drift (local_doc , aws_doc )
4456
45- # If no drift and no approval flag, exit
4657 if not drift and not approval_anyway :
4758 typer .echo ("✅ No drift detected. Use --approval-anyway to force approval." )
4859 raise typer .Exit ()
4960
50- # Ensure we know which repo to use
5161 if not repo_full_name :
5262 repo_full_name = os .getenv ("GITHUB_REPOSITORY" )
5363
5464 if not repo_full_name :
5565 typer .echo ("❌ GitHub repo not specified. Use --repo or set GITHUB_REPOSITORY." )
5666 raise typer .Exit (1 )
5767
58- # Create GitHub issue
5968 token = os .getenv ("GITHUB_TOKEN" )
6069 if not token :
6170 typer .echo ("❌ GITHUB_TOKEN not set in environment." )
@@ -64,7 +73,6 @@ def drift(
6473 issue_num = create_approval_issue (repo_full_name , token , policy_name )
6574 typer .echo (f"Issue #{ issue_num } created for approval." )
6675
67- # Wait for sync choice comment
6876 choice = wait_for_sync_choice (repo_full_name , issue_num , token )
6977
7078 if choice == "local->aws" :
@@ -86,9 +94,12 @@ def drift(
8694 new_content = json .dumps (aws_doc , indent = 2 )
8795 with open (policy_file , "w" ) as f :
8896 f .write (new_content )
97+
8998 branch = f"update-policy-{ policy_name } "
9099 pr_title = f"Update { policy_file } from AWS policy"
91100 pr_body = "This PR updates the local policy file with the AWS default version."
101+
102+ push_branch (branch )
92103 pr_num = create_github_pr (repo_full_name , branch , pr_title , pr_body )
93104 typer .echo (f"✅ Created PR #{ pr_num } : updated { policy_file } from AWS policy." )
94105
0 commit comments