AtlantisのようにPull Requestが作成されたタイミングでlambroll deploy --dry-runが実行され、/deployとコメントすることでlambroll deployが実行されるGitHub Actionsのワークフローを作ってみた。
デモ

- デプロイされたら自動でマージされる
- エラーになったらマージされない
- ボットのコメントは最新のもの以外は自動で最小化される
実装
ファイル構成
/ ├── .github/ │ └── workflows/ │ ├── deploy.yml │ ├── dry-run.yml │ └── lambroll.yml ├── .gitignore ├── .lambdaignore ├── function.jsonnet ├── index.mjs └── option.jsonnet
deploy.yml
name: Deploy on: issue_comment: types: [created] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: deploy: uses: ./.github/workflows/lambroll.yml if: startsWith(github.event.comment.body, '/deploy') secrets: inherit with: deploy: true pr_num: ${{ github.event.issue.number }} reaction: runs-on: ubuntu-latest if: startsWith(github.event.comment.body, '/deploy') env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Reaction run: | gh api --method=POST -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2022-11-28' \ /repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \ -f 'content=+1'
dry-run.yml
name: Deploy (dry-run) on: pull_request: branches: [main] paths: - function.jsonnet - index.mjs - option.jsonnet types: [opened, synchronize] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: deploy: uses: ./.github/workflows/lambroll.yml secrets: inherit with: deploy: false pr_num: ${{ github.event.number }}
lambroll.yml
name: lambroll on: workflow_call: inputs: deploy: type: boolean required: true pr_num: type: number required: true checkout_ref: type: string default: "" permissions: id-token: write contents: write issues: write pull-requests: write jobs: lambroll: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: ref: ${{ inputs.checkout_ref }} - uses: winebarrel/lastcmt@v0.6.3 - uses: aws-actions/configure-aws-credentials@v5 with: aws-region: ap-northeast-1 role-to-assume: arn:aws:iam::123456789012:role/lambroll-deploy - uses: fujiwara/lambroll@v1 with: version: v1.4.1 - name: Deploy env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} LAMBROLL_OPTION: option.jsonnet DEPLOY: ${{ inputs.deploy }} OPTS: ${{ !inputs.deploy && '--dry-run' || '' }} PR_NUM: ${{ inputs.pr_num }} LASTCMT_KEY: lambroll-deploy-${{ inputs.deploy }} run: | set -exo pipefail MERGEABLE=$(gh pr view $PR_NUM --json mergeable -q '.mergeable') if $DEPLOY && [ "$MERGEABLE" != "MERGEABLE" ]; then echo ':red_circle: Pull request is not mergeable.' | lastcmt $PR_NUM exit 1 fi set +e lambroll deploy --no-color $OPTS 2>&1 | tee deploy.log RET=$? set -e ICON=$([ $RET -eq 0 ] && echo -n ':green_circle:' || echo -n ':red_circle:') echo "## $ICON lambroll deploy $OPTS" > comment.txt echo '```' >> comment.txt cat deploy.log >> comment.txt echo '```' >> comment.txt if $DEPLOY; then echo ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} >> comment.txt fi lastcmt $PR_NUM comment.txt if $DEPLOY && [ $RET -eq 0 ]; then gh pr merge $PR_NUM --merge --delete-branch fi exit $RET
IAM
resource "aws_iam_openid_connect_provider" "github" { url = "https://token.actions.githubusercontent.com" client_id_list = [ "sts.amazonaws.com" ] } resource "aws_iam_role" "lambroll_deploy" { name = "lambroll-deploy" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Principal = { Federated = aws_iam_openid_connect_provider.github.arn } Action = [ "sts:AssumeRoleWithWebIdentity", "sts:TagSession", ] Condition = { StringEquals = { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" } StringLike = { "token.actions.githubusercontent.com:sub" = "repo:foo/bar:*" } } }, ] }) } resource "aws_iam_role_policy" "lambroll_deploy" { role = aws_iam_role.lambroll_deploy.id name = "lambroll-deploy" policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "iam:PassRole" Resource = "arn:aws:iam::1234567890:role/lambda-role" }, { Effect = "Allow" Action = [ "lambda:Get*", "lambda:List*", "lambda:CreateAlias", "lambda:DeleteFunction", "lambda:UpdateAlias", "lambda:UpdateFunctionCode", "lambda:UpdateFunctionConfiguration", ] Resource = "*" }, { Effect = "Allow" Action = "s3:GetObject" Resource = "arn:aws:s3:::my-bucket/terraform.tfstate" }, ] }) } resource "aws_iam_role" "lambda_hello" { name = "lambda-hello" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } Action = "sts:AssumeRole" } ] }) } resource "aws_iam_role_policy_attachment" "lambda_hello" { role = aws_iam_role.lambda_hello.name arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" }