Skip to content

Instantly share code, notes, and snippets.

@cmdr2
Created August 20, 2025 13:39
Show Gist options
  • Save cmdr2/7261109b0e214942f1c7864024a6daa1 to your computer and use it in GitHub Desktop.
Save cmdr2/7261109b0e214942f1c7864024a6daa1 to your computer and use it in GitHub Desktop.
Utility script to easily setup a GitHub deploy action in a repository for pushing to S3 or Lambda. Creates the required IAM roles automatically.
#!/usr/bin/env python3
import os
import subprocess
import json
import time
def run_capture(cmd):
print(f"Running: {' '.join(cmd)}")
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
return result.stdout
def run(cmd):
print(f"Running: {' '.join(cmd)}")
subprocess.run(cmd, check=True)
def get_github_info():
github_repo = input("Enter GitHub repo (e.g., cmdr2/carbon, or full URL): ").strip()
github_branch = input("Enter GitHub branch [default=main]: ").strip() or "main"
if github_repo.startswith("http://") or github_repo.startswith("https://"):
parts = github_repo.rstrip("/").split("/")
owner, repo = parts[-2], parts[-1].replace(".git", "")
else:
owner, repo = github_repo.split("/")
return owner, repo, github_branch
def get_account_id():
output = run_capture(["aws", "sts", "get-caller-identity", "--query", "Account", "--output", "text"])
return output.strip()
def create_policy_and_role(base_name, policy_doc, trust_doc):
policy_file = f"/tmp/{base_name}-policy.json"
with open(policy_file, "w") as f:
json.dump(policy_doc, f, indent=2)
output = run_capture(
["aws", "iam", "create-policy", "--policy-name", base_name, "--policy-document", f"file://{policy_file}"]
)
policy_arn = json.loads(output)["Policy"]["Arn"]
trust_file = f"/tmp/{base_name}-trust.json"
with open(trust_file, "w") as f:
json.dump(trust_doc, f, indent=2)
output = run_capture(
["aws", "iam", "create-role", "--role-name", base_name, "--assume-role-policy-document", f"file://{trust_file}"]
)
role_arn = json.loads(output)["Role"]["Arn"]
print("Waiting 5 seconds for IAM policy to propagate...")
time.sleep(5)
run(["aws", "iam", "attach-role-policy", "--role-name", base_name, "--policy-arn", policy_arn])
return role_arn
def create_trust_policy(owner, repo, branch, account_id):
return {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": f"arn:aws:iam::{account_id}:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": f"repo:{owner}/{repo}:ref:refs/heads/{branch}",
}
},
}
],
}
def write_workflow(branch, role_var, deploy_step):
workflow_dir = ".github/workflows"
os.makedirs(workflow_dir, exist_ok=True)
workflow_file = os.path.join(workflow_dir, "deploy.yml")
workflow_yaml = f"""name: deploy
run-name: Deployment
on:
push:
branches:
- {branch}
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{{{ vars.{role_var} }}}}
aws-region: us-east-1
{deploy_step}
"""
with open(workflow_file, "w") as f:
f.write(workflow_yaml)
return workflow_file
def setup_s3(owner, repo, branch, account_id):
s3_path = input("Enter S3 path to deploy to (e.g., my-bucket-name/some/path): ").strip()
bucket_name = s3_path.split("/")[0]
base_name = f"{owner}-{repo}-github-deploy-to-s3"
policy_doc = {
"Version": "2012-10-17",
"Statement": [
{
"Action": ["s3:PutObject", "s3:PutObjectAcl"],
"Effect": "Allow",
"Resource": [f"arn:aws:s3:::{s3_path}/*"],
},
{"Action": ["s3:ListBucket"], "Effect": "Allow", "Resource": [f"arn:aws:s3:::{bucket_name}"]},
],
}
trust_doc = create_trust_policy(owner, repo, branch, account_id)
role_arn = create_policy_and_role(base_name, policy_doc, trust_doc)
deploy_step = f""" - name: Copy files to S3
run: |
aws s3 sync . "s3://{s3_path}" --exclude ".git/*" --exclude ".github/*" --exclude "LICENSE" --exclude "README.md" --exclude "*.sh" --acl public-read
shell: bash"""
workflow_file = write_workflow(branch, "S3_DEPLOY_ROLE", deploy_step)
print("\n✅ S3 setup complete.")
print(f"Workflow written: {workflow_file}. Please customize it as necessary.")
print(f"**IMPORTANT:** Set GitHub repo variable S3_DEPLOY_ROLE to {role_arn}")
def setup_lambda(owner, repo, branch, account_id):
function_name = input("Enter Lambda function name: ").strip()
base_name = f"{owner}-{repo}-github-deploy-to-lambda"
policy_doc = {
"Version": "2012-10-17",
"Statement": [
{
"Action": ["lambda:UpdateFunctionCode"],
"Effect": "Allow",
"Resource": [f"arn:aws:lambda:us-east-1:{account_id}:function:{function_name}"],
}
],
}
trust_doc = create_trust_policy(owner, repo, branch, account_id)
role_arn = create_policy_and_role(base_name, policy_doc, trust_doc)
deploy_step = f""" - name: Zip and Upload Lambda
run: |
zip -r function.zip . -x ".git/*" ".github/*" "LICENSE" "README.md" "*.sh"
aws lambda update-function-code --function-name {function_name} --zip-file fileb://function.zip
shell: bash"""
workflow_file = write_workflow(branch, "LAMBDA_DEPLOY_ROLE", deploy_step)
print("\n✅ Lambda setup complete.")
print(f"Workflow written: {workflow_file}. Please customize it as necessary.")
print(f"**IMPORTANT:** Set GitHub repo variable LAMBDA_DEPLOY_ROLE to {role_arn}")
def main():
if not os.path.exists(".git"):
print("This script must be run from a Git repository.")
return
print("Choose publish destination:")
print("1. S3")
print("2. Lambda")
choice = input("Enter number: ").strip()
owner, repo, branch = get_github_info()
account_id = get_account_id()
if choice == "1":
setup_s3(owner, repo, branch, account_id)
elif choice == "2":
setup_lambda(owner, repo, branch, account_id)
else:
print("Invalid choice. Exiting.")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment