🐞
Github ActionsでAWS CDKの自動Pull Request生成に挑戦した話
概要
- 趣味の開発でよくAWS CDKを利用してAWSの各種サービスを構築することが多い
- AWS CDKはインフラやCloud Formationの知識が少なくとも、好きな開発言語を使ってAWS環境を構築できるため、非常に優秀なソリューションであると感じる
- しかし、AWS自体の進化速度が非常に速く、これに劣らずAWS CDKも頻繁にアップデートされる(週1くらいでどんどんバージョンが上がる)
- 従来は、「気づいたときに」以下のように適宜手動でアップグレード対応をしていた
$ ncu -u
$ npm ci
$ npm run build
- AWS CDKは好きな言語で開発できるが、いくつか試してみてTypeScriptが書きやすいため好んで使っている
- TypeScriptの場合、
package.json
でCDKのバージョンを管理するが、これをアップグレードするために npm-check-updatesというパッケージを利用している - 上記のようにこれを利用すると、
package.json
の内容を自動で更新してくれる - しかしながら、AWS CDKのリポジトリを複数管理するとなると、このアップグレードの面倒を見るのが手作業では厳しい
- このため、CIから自動でアップグレードをサジェストしてくれる機能が欲しくなった
- 最初に思い立った上記ツイートが
2022-01-06 AM5:20
とかだが、一応その翌日には一通りできたのでまあ良しとしたいw - 全体像はそれほど複雑ではないが、どちらかというと「こういうときどう書けば・・・?」というハマりが割とあり勉強になったため、自身の備忘録と、何かのお役に立てばという気持ちでこちらの記事を書きました
開発したもの
- 前置きが長くなったが、今回は以下のGithub Actionsを開発した
- Github Actionsの選定理由は、(余程のヘビーユースをしなければ)無料で手早く利用でき、メンテナンスも容易なので、個人開発のCIツールとして扱いやすいため
- 尚、現在はまだ試験運用中のため、マーケットプレイスへの公開は控えている
- 安定稼働が見込めたら公開するかもしれない
仕様
- 下表に簡単に記述する
仕様 |
---|
npm-check-updates (ncu) パッケージをインストール、実行し、CI環境上にチェックアウトした package.json を更新する |
ncu 実行時、更新対象パッケージを aws-cdk 関連のものに限定する (jest など、package.json 内の他のパッケージは対象としない) |
該当リポジトリに対してPull Requestを作成する |
Pull Request作成後、 npm run build でパッケージのビルドをトライする |
ビルドの実行結果に関わらず、作成したPull Requestのコミットに対して結果をコメントする |
利用方法
- 手持ちのGithub Actionsの
workflow
から以下のように利用する
name: "Create PR to update AWS CDK"
on:
push:
branches:
- main
jobs:
create-aws-cdk-update-pr:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Create AWS CDK Update PR
uses: otajisan/aws-cdk-update-pr-builder-ts@v0.0.4
with:
token: ${{ secrets.CR_PAT }} # Github Personal Access Token
assignees: otajisan
reviewers: otajisan
- 上記の例では、
main
ブランチにpushされたタイミングで実行するものとしているが、cron
で定義すれば定期実行も可能なはず(未検証)
実行結果
- 以下のようになる
自動作成されたPull Request
変更差分
ビルド結果のコメント
- (私の場合、Personal Access Tokenを利用したため、一人でやっているように見えてならないが。。。)
- 一応、CIから実行し、上記の作業を自動で実行してくれる
コード解説
- action.ymlの内容を解説する
composite action
- composite actionを採用した
-
step
という単位で、複数の処理を定義できる - shell実行もできれば、他のActionを呼び出すこともできるため、workflow作成時の手間を減らせる
runs:
using: "composite"
steps:
-
step
の中身を順次説明する
日付を変数に格納
- PRに記載する日付などを管理するために、
date
という変数に20220107
といった日付を設定する
- name: Set current date
id: date
run: echo "::set-output name=date::$(date +'%Y%m%d')"
shell: sh
- ここで設定した変数は、後続の
step
より${{ steps.<step名>.outputs.<変数名> }}
のように参照できる
# outputs変数を参照する例
steps:
- run: echo random-number ${{ steps.foo.outputs.random-number }}
shell: bash
実行環境のセットアップ
- TypeScriptで作成したCDKパッケージをビルドするため、Nodeの環境を作成する
- パッケージアップグレードに利用する
npm-check-updates
はglobal
でインストールする
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: ${{ inputs.node-version }}
- name: Install npm-check-updates
run: npm i -g npm-check-updates
shell: sh
パッケージのアップデート
-
ncu
を利用してパッケージをアップグレードする - ncu後にcleanインストールし、最後に
git status
で、CIコンソールから差分を確認できるようにする(デバッグ用途にも)
- name: Run npm-check-updates for AWS CDK
run: |
ncu -u \
$(cat package.json | grep aws-cdk | awk '!/^#/{n=split($0, ary, ":"); print(ary[1])}' | tr -d "\"")
shell: sh
- name: Update packages.json
run: npm ci
shell: sh
- name: Check git status
run: git status
shell: sh
-
ncu
のコマンド箇所はshell芸。ちょっと分解して説明する。まず、整形すると以下のようになる
cat package.json |\
grep aws-cdk |\
awk '!/^#/{n=split($0, ary, ":"); print(ary[1])}' |\
tr -d "\""
-
cat package.json
でpackage.json
の内容を持ってくる -
grep
でaws-cdk
関連のパッケージを抽出 -
awk
xsplit
で"@aws-cdk/assert": "2.4.0",
のような文字列を:
で分割し、"@aws-cdk/assert"
の箇所のみを抽出する -
tr
で"
をトリム
- 以上、実行すると以下のように
aws-cdk
関連のパッケージのみを抽出できるため、これをncu
に食わせてアップデート対象とする(お手元のpackage.json
でもお試しいただけると思います)
cat package.json |\
grep aws-cdk |\
awk '!/^#/{n=split($0, ary, ":"); print(ary[1])}' |\
tr -d "\""
@aws-cdk/assert
@aws-cdk/aws-ec2
@aws-cdk/aws-eks
@aws-cdk/aws-iam
aws-cdk
@aws-cdk/core
Pull Requestの作成
- 前述の通り、
step
内ではshellのみでなく、他のGithub Actionsも統合(再利用)できる - Pull RequestはこちらのActionが利用しやすそうだったため採用
- name: Create Pull Request
id: create-pr
uses: peter-evans/create-pull-request@v3
with:
token: ${{ inputs.token }}
commit-message: ${{ steps.date.outputs.date }} Upgrade AWS CDK
signoff: false
base: ${{ inputs.base-branch }}
branch: upgrade/aws-cdk-${{ steps.date.outputs.date }}
delete-branch: true
title: ${{ steps.date.outputs.date }} Upgrade AWS CDK
body: " - [Release Notes](https://github.com/aws/aws-cdk/releases)"
labels: |
upgrade-aws-cdk
assignees: ${{ inputs.assignees }}
reviewers: ${{ inputs.reviewers }}
draft: false
- 内容から直感的に理解できると思うが、コミットメッセージやラベル、PRのブランチ名などを適宜指定している
- Pull Requestを作成するまでならばこれで終了でも良い
コメントの作成
- ここは少しこだわったところ
- とはいえ、「面倒なのでshell芸でなんとかする」という作りとしているので綺麗なコードとは言えない。。。
- 何をやっているかを説明する
- name: Try to build package and output to tmp file.
run: |
touch comment-${{ steps.date.outputs.date }}.md
echo "# AWS CDK build info" >> comment-${{ steps.date.outputs.date }}.md
echo "*Build result*" >> comment-${{ steps.date.outputs.date }}.md
echo "- please confirm build result and fix if error." >> comment-${{ steps.date.outputs.date }}.md
echo "\`\`\`node" >> comment-${{ steps.date.outputs.date }}.md
npm run build >> comment-${{ steps.date.outputs.date }}.md || true
echo "\`\`\`" >> comment-${{ steps.date.outputs.date }}.md
sed -i -z 's/\n/\\n/g' comment-${{ steps.date.outputs.date }}.md
shell: sh
- name: Preview comment
run: cat comment-${{ steps.date.outputs.date }}.md
shell: sh
- まず、冒頭で投稿用のコメントを一時保存するファイルを作成する
touch comment-${{ steps.date.outputs.date }}.md
- このあたりは単に装飾。
echo "# AWS CDK build info" >> comment-${{ steps.date.outputs.date }}.md
echo "*Build result*" >> comment-${{ steps.date.outputs.date }}.md
echo "- please confirm build result and fix if error." >> comment-${{ steps.date.outputs.date }}.md
- ここで、
npm run build
を実行し、結果をコメントに追記している- ポイントは
|| true
の箇所で、npm run build
の結果の成否に関わらず、後続の処理を継続できるようにしている
- ポイントは
echo "\`\`\`node" >> comment-${{ steps.date.outputs.date }}.md
npm run build >> comment-${{ steps.date.outputs.date }}.md || true
echo "\`\`\`" >> comment-${{ steps.date.outputs.date }}.md
-
echo
で出力した結果は改行コードを含んでしまうため、最後にsed
で改行コードを置換(Github APIにJSONで送信する際に扱いやすいよう)
sed -i -z 's/\n/\\n/g' comment-${{ steps.date.outputs.date }}.md
- ファイルの作成後、一応コメントをプレビューするため
cat
で開くようにしている- 展開した結果は以下のようなワンライナーの文字列となる
# AWS CDK build info\n*Build result*\n- please confirm build result and fix if error.\n```node\n\n> eks-cdk@0.1.0 build\n> tsc\n\nlib/eks-cdk-stack.ts(12,32): error TS2345: Argument of type 'this' is not assignable to parameter of type 'Construct'.\n Type 'EksCdkStack' is not assignable to type 'Construct'.\n Property 'onValidate' is protected but type 'Construct' is not a class derived from 'Construct'.\nlib/eks-cdk-stack.ts(30,33): error TS2345: Argument of type 'this' is not assignable to parameter of type 'Construct'.\n```\n
コメントの投稿
- 最後に
curl
でコメントをポストする
- name: Post build result comment to PR
env:
GITHUB_TOKEN: ${{ inputs.token }}
COMMIT_COMMENT_URL: https://api.github.com/repos/${{ github.repository }}/commits/${{ steps.create-pr.outputs.pull-request-head-sha }}/comments
COMMENT_FILE: comment-${{ steps.date.outputs.date }}.md
run: |
curl -s -X POST \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Content-Type: application/vnd.github.v3+json" \
-H "Accept: application/vnd.github.v3+json" \
${COMMIT_COMMENT_URL} \
-d "{\"body\": \"$(cat ${COMMENT_FILE})\"}"
shell: sh
- name: Remove comment file
run: rm -f comment-${{ steps.date.outputs.date }}.md
shell: sh
- ここのポイントは以下
env:
COMMIT_COMMENT_URL: https://api.github.com/repos/${{ github.repository }}/commits/${{ steps.create-pr.outputs.pull-request-head-sha }}/comments
-
GithubのPull RequestのコメントはPulls APIを利用するようなのだが、結構細かくパラメータを設定する必要があり面倒
-
このため、Commits API#create-a-commit-commentを利用した
-
こちらはPR作成時のコミット番号のハッシュ値が分かれば利用できる
-
前述のpeter-evans/create-pull-requestアクションは、
outputs
にpull-request-head-sha
というコミット番号のハッシュ値を携えているため、これを利用すれば良い -
あとは作成したコメントを
curl
のリクエストボディにcat
で開いて投入すれば良い
curl -s -X POST \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Content-Type: application/vnd.github.v3+json" \
-H "Accept: application/vnd.github.v3+json" \
${COMMIT_COMMENT_URL} \
-d "{\"body\": \"$(cat ${COMMENT_FILE})\"}"
- 成功するとステータスコード
201
が返り、コメントを投稿できる
I/O
- I/O設計は正直だいぶ甘い
- ひとまず最低限需要がありそうなパラメータのみを入力として構えているが、実際には、例えばPR作成時のブランチ名なども変更可能としたほうが親切だろう
-
output
も作成したPull Request番号や、AWS CDKのバージョン情報などを添えたほうが良さそう
作ってみての所感
- そもそもGithub Actionsが、私がだいぶ前に使っていた時と比べて高機能となっており、キャッチアップの題材としては良かった
- そんなこんなで、調べものや他の勉強をしつつだったため丸2日近く潰してしまったが、当初の目的に則ったものを作れたので良かった
- shellを多用する実装はあまり良くなかった。適宜Docker x プライベートActionの構成などに閉じ込めればだいぶスッキリしそう
Discussion