AWS CDKでリソースを記述し、PullRequestに対して自動でcdk diff
で変更があるものを表示して、mergeしたときにcdk deploy
する。
全体のコードはGitHubにある。
AWS CDKでCloudFormationのテンプレートをTypeScriptから生成しデプロイする - sambaiz-net
追記 (2019-08-29): このフローで起こったいくつかの問題を解決するため新しいツールを作った。 PR上でCDKのレビューやデプロイを行うツールcdkbotを作った - sambaiz-net
CI Userの作成
まずcdkコマンドを実行するためのCI Userを作成する。これはCDK管理外のスタックで、AWSコンソール上から手動で上げる。
AssumeRoleしかできないCIUserからCIAssumeRoleをassumeすることにした。
AWSTemplateFormatVersion: '2010-09-09'
Resources:
CIAssumeRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: 'CIAssumeRole'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/AdministratorAccess'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Principal:
AWS:
- !Ref AWS::AccountId
Action:
- 'sts:AssumeRole'
CIGroup:
Type: 'AWS::IAM::Group'
Properties:
GroupName: 'CI'
CIPolicies:
Type: 'AWS::IAM::Policy'
Properties:
PolicyName: 'CI'
PolicyDocument:
Statement:
- Effect: Allow
Action: 'sts:AssumeRole'
Resource: !GetAtt CIAssumeRole.Arn
Groups:
- !Ref CIGroup
CIUser:
Type: 'AWS::IAM::User'
Properties:
UserName: 'CI'
Groups:
- !Ref CIGroup
CIUserKeys:
Type: 'AWS::IAM::AccessKey'
Properties:
UserName: !Ref CIUser
Outputs:
AccessKey:
Value: !Ref CIUserKeys
SecretKey:
Value: !GetAtt CIUserKeys.SecretAccessKey
アップロードするとAccessKeyがOutputされるので、これに加え、SlackのWebhook URLとGitHubのTokenを作成しCircleCIのEnvironment Variablesに追加する。また、PR作成時にビルドが走るようにビルド設定のOnly build pull requestsをオンにする。
CircleCIの設定
.circleci/config.yml
はこんな感じ。PRが立っていればdiffと、develop/masterにmergeするとstg/prd環境へのdeployを行う。
ただ、Only build pull requestsをオンにすると、PR以外ではdefault branchにしているmasterへのpushでしかjobが走らなくなってしまうので、
stg環境へのデプロイはdevelop->masterへのPRが存在していなければPRを作成したときに行われる。
CircleCI 2.1からのOrbでdocker buildしてECRにpushし、Slackに通知させる - sambaiz-net
version: 2.1
orbs:
slack: circleci/[email protected]
executors:
default:
docker:
- image: 'circleci/node:12.2.0'
environment:
AWS_REGION: 'ap-northeast-1'
jobs:
build:
executor: 'default'
steps:
- run:
name: 'assume_role'
command: |
sudo apt-get install awscli
unset AWS_SESSION_TOKEN
echo $ASSUME_ROLE_ARN
temp_role=$(aws sts assume-role --role-arn $ASSUME_ROLE_ARN --role-session-name $CIRCLE_PROJECT_REPONAME)
echo "export AWS_ACCESS_KEY_ID=$(echo $temp_role | jq .Credentials.AccessKeyId | xargs)" >> $BASH_ENV
echo "export AWS_SECRET_ACCESS_KEY=$(echo $temp_role | jq .Credentials.SecretAccessKey | xargs)" >> $BASH_ENV
echo "export AWS_SESSION_TOKEN=$(echo $temp_role | jq .Credentials.SessionToken | xargs)" >> $BASH_ENV
- checkout
- run:
name: 'build'
command: |
npm install
npm run build
- run:
name: 'cdk_diff'
command: |
if [ -n "$CIRCLE_PULL_REQUEST" ]; then
export ENV=stg
if [ "${CIRCLE_BRANCH}" == "develop" ]; then
export ENV=prd
fi
pr_number=${CIRCLE_PULL_REQUEST##*/}
block='```'
diff=$(echo -e "cdk diff (env=${ENV})\n${block}\n$(npm run --silent ci_diff)\n${block}")
data=$(jq -n --arg body "$diff" '{ body: $body }') # escape
curl -X POST -H 'Content-Type:application/json' \
-H 'Accept: application/vnd.github.v3+json' \
-H "Authorization: token ${GITHUB_TOKEN}" \
-d "$data" \
"https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${pr_number}/comments"
fi
- run:
name: 'cdk_deploy'
command: |
if [ "${CIRCLE_BRANCH}" == "master" ]; then
ENV=prd npm run ci_deploy
elif [ "${CIRCLE_BRANCH}" == "develop" ]; then
ENV=stg npm run ci_deploy
fi
- slack/status:
success_message: "cdk build/deploy (${CIRCLE_BRANCH}) has succeeded :tada:"
failure_message: "cdk build/deploy (${CIRCLE_BRANCH}) has failed :crying_cat_face:"
webhook: $SLACK_WEBHOOK
only_for_branches: "develop,master"
Assumeしたkeyは$BASH_ENV
でexportされるようにして以後のstepでも自動で読み込まれるようにする。
GitHubのコメント作成APIを呼ぶのにPR番号が必要で$CIRCLE_PR_NUMBER
を使おうとしたが、folkされたPRでしか入らないので$CIRCLE_PULL_REQUEST
のURLから/末尾を取り出して使っている。
cdk diff
は差分があるとstderrに出力し終了ステータスを1で返してしまい、ビルドが中断してしまうのでstderrをstdoutに向けて||true
で強制的に成功させている。sedでは色を付けるためのescape sequenceを取り除いている。-c
でenvの値をcontextとして渡して各環境のStackが作られるようにしていて、deploy時に--require-approval never
を付けているのはsgへのルール追加時などに確認が入ってCIが止まらないようにするため。
huskyでcommit時にprettierを走らせている。
$ cat package.json | jq ".scripts"
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"format": "prettier --write \"lib/**/*.ts\"",
"ci_diff": "cdk diff -c env=${ENV:-stg} 2>&1 | sed -r 's/\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g' || true",
"ci_deploy": "cdk deploy -c env=${ENV:-stg} --require-approval never"
}
$ cat .huskyrc.json
{
"hooks": {
"pre-commit": "npm run format && git add ."
}
}
PRを作成するとcdk diff
の結果が表示される。
deployが完了するとSlackに通知が飛ぶ。