以下のサービスを組み合わせて、ポチポチCIツールを作成します。
- Slack
- GitHub Actions、REST API v3
- AWS Lambda ( Node.js 12.x (@slack/bolt 1.5.0) )
外観図
Slackに対してリクエストすると、SlackとLambdaがやりとりをしてGitHubに命令を出してくれます。
素材
猫の顔: https://kumamine.blogspot.com/2019/12/blog-post_27.html
Slack: Cacoo内の素材
AWS Lambda: Cacoo内の素材
GitHub: Cacoo内の素材
GitHub ActionsのJobを外部から実行する
GitHub Actionsはプルリク作成やコミットプッシュなどの何らかの「トリガー」で走るCIツールです。
GitHubのAPIを利用し、「トリガー」を発生させることで、外部からActionsのJobを実行することができます。
準備
GitHub ActionsをWebAPIから実行するための準備を行います。
- Github Actions用のリポジトリを作成して、ローカルにcloneしておきます。
https://help.github.com/ja/github/getting-started-with-github/create-a-repo - GitHub REST API v3を利用するためにアクセストークンを作成します。権限には
repo
を付加しておきましょう。
https://help.github.com/ja/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line - GitHubからcloneしてきたプロジェクト上に、Github Actionsのワークフロー構文ファイルを作成します。作成したファイルはデフォルトブランチ(いわゆるmasterブランチ)に置きましょう。
# Actionの名前name:Sample# Jobが実行されるトリガー# pushされた場合、もしくはイベント種別が「hoge」のリポジトリディスパッチイベントが作成された場合に実行するon:push:repository_dispatch:types:[hoge]jobs:sample_job:runs-on:ubuntu-latestname:sample_jobsteps:-uses:actions/checkout@v1# ブランチが指定されていた場合は、指定されているブランチを利用する-if:github.event.client_payloaduses:actions/checkout@v1with:ref:${{ github.event.client_payload.ref }}# 「echo_string」要素が指定されていた場合は、その要素の内容を表示する-if:github.event.client_payload.echo_stringname:echo stringrun:echo ${{ toJSON(github.event.client_payload.echo_string) }}
Github ActionsのJobを端末から実行する
準備ができたら、端末からGitHub Actionsに文字列を渡して、Jobのログ上に表示させてみましょう。
# 扱いやすいように変数にアクセストークンを代入するGITHUB_TOKEN={アクセストークン}# 実行ブランチBRANCH={ブランチ名}# Actionの実行
curl -X POST https://api.github.com/repos/:owner/:repo/dispatches \-H"Authorization: token $GITHUB_TOKEN"\-H"Accept: application/vnd.github.everest-preview+json"\--data"{\"event_type\": \"hoge\", \"client_payload\": {\"ref\": \"$BRANCH\", \"echo_string\": \"hugahuga\"}}"
curl
コマンドの--data
の、event_type
には、ワークフロー構文ファイルのon.repository_dispatch.types
の文字列を指定します。client_payload.ref
には、実行するブランチ名称を指定します。client_payload.echo_string
には、表示する文字列を指定します。
curlで実行した場合の結果
下図はGithub Actions上の実行結果です。curl
コマンドの--data
で指定したclient_payload.echo_string
の文字列が表示されます。
pushした場合の結果
ブランチの切り替えも文字列の表示も行いません。
pushされたブランチで処理が行われます。
ここまでの作業で察した方もいらっしゃると思いますが、デフォルトブランチ上にワークフロー構文ファイルが存在しないと、Jobが実行されません。理由は、APIがデフォルトブランチに対してイベントを発生させているためです。ではどこでブランチが切り替わっているのかというと、Github ActionsのJob中で切り替わっています。
ブランチに対してイベントを発生させるAPIも存在するのですが、本来の使い方ではない上に、意味のないログがプルリクなどに残ってしまうため、今回はdispatches
イベントを採用しました。
※ GitHub Actions用のAPIも用意されているようですが、まだパブリックベータ版のようですし、ジョブが実行できるわけではないみたいです。内容が頻繁に変更される可能性があるため、マスターがリリースされ次第、都合がよさそうであれば記事内容を書き換えようかなと思っています。(2020.02.02)
参考
https://developer.github.com/v3/repos/#create-a-repository-dispatch-event
https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows
https://swfz.hatenablog.com/entry/2020/01/23/080000
https://qiita.com/proudust/items/51599abd2b107b708e1e
Slackにモーダルウィンドウを表示して、入力値を取得する
Slackから提供されているWebAPIを組み合わせることで、モーダルウィンドウなどのインターフェースを実装することができます。
今回はそのラッパーであるBoltフレームワークを利用して実装していきます。
準備
- Slackのワークスペースを作成します。
https://slack.com/intl/ja-jp/help/articles/206845317 - SlackのAppsを作成します。https://api.slack.com/appsから
Create New App
を押して、App Name
とDevelopment Slack Workspace
の項目を入力してCreate App
を押せばOKです。(2020.02.02現在)
https://api.slack.com/start/overview - Features > OAuth & Permissions > Scopes > Bot Token Scopes に
app_mentioned:read
、chat:write
、commands
を追加します。
https://api.slack.com/apps/{APP_ID}/oauth - Settings > Install App からアプリをワークスペースにインストールします。
https://api.slack.com/apps/{APP_ID}/install-on-team
アプリをハンドリングするサーバの作成
今回はServerless FrameworkとAWS Lambdaを利用してサーバを作成します。
まずは、端末で以下のコマンドを実行し、プロジェクトを作成します。
# プロジェクトの作成
npm init
# ライブラリのインストール
npm install serverless \
aws-serverless-express \
dotenv \
@slack/bolt \
axios
# サーバーレスフレームワークプロジェクトの作成
npx serverless create --template aws-nodejs
サーバーレスの設定を追加します。
service:sampleprovider:name:awsruntime:nodejs12.xregion:ap-northeast-1functions:events:handler:handler.apptimeout:30events:# Bolt App-http:method:postpath:/slack/events# OAuth Flow-http:method:getpath:/slack/installation-http:method:getpath:/slack/oauth
内部のロジックを作成します。/repos/:owner/:repo/dispatches
の部分は「Github ActionsのJobを外部から実行する」で利用したリポジトリを指定してください。
'use strict';// ------------------------------// AWS Lambda handler// ------------------------------require('dotenv').config();const{App,LogLevel,ExpressReceiver}=require('@slack/bolt');constexpressReceiver=newExpressReceiver({signingSecret:process.env.SLACK_SIGNING_SECRET});constapp=newApp({token:process.env.SLACK_BOT_TOKEN,receiver:expressReceiver,logLevel:LogLevel.DEBUG});constawsServerlessExpress=require('aws-serverless-express');constserver=awsServerlessExpress.createServer(expressReceiver.app);module.exports.app=(event,context)=>{console.log('⚡️ Bolt app is running');console.log(event);awsServerlessExpress.proxy(server,event,context)}app.error(printCompleteJSON);// ------------------------------// Application Logic// ------------------------------// slash commandでモーダルウィンドウを表示するためのロジックapp.command('/build',async({ack,body,context})=>{ack();constresult=app.client.views.open({"token":context.botToken,"trigger_id":body.trigger_id,"view":{"type":"modal","callback_id":"task-modal","private_metadata":JSON.stringify(body),"title":{"type":"plain_text","text":"Github Actions","emoji":true},"submit":{"type":"plain_text","text":"Submit","emoji":true},"close":{"type":"plain_text","text":"Cancel","emoji":true},"blocks":[{"type":"input","block_id":"input-title","element":{"type":"plain_text_input","action_id":"input","initial_value":body.text},"label":{"type":"plain_text","text":"branch","emoji":true},"optional":false},{"type":"input","block_id":"input-description","element":{"type":"plain_text_input","action_id":"input","multiline":true},"label":{"type":"plain_text","text":"echo string","emoji":true},"optional":true}]}});console.log(awaitresult);});// モーダルウィンドウから送信された値を受け取って、処理を行うロジックapp.view('task-modal',async({body,ack,context})=>{// モーダルウィンドウでのデータ送信イベントを確認ack();// ユーザーにメッセージを送信try{app.client.chat.postMessage({token:context.botToken,channel:body['user']['id'],text:'Build request received!'});}catch(error){console.error(error);}// ------------------------------// axios flow// ------------------------------constaxiosBase=require('axios');constaxios=axiosBase.create({baseURL:'https://api.github.com',headers:{"Authorization":`token ${process.env.GITHUB_TOKEN}`,"Accept":"application/vnd.github.everest-preview+json"}});constrequestBody=JSON.stringify({event_type:"hoge",client_payload:{ref:body.view.state.values['input-title'].input.value,echo_string:body.view.state.values['input-description'].input.value}});// GitHubへのリクエスト処理axios.post('/repos/:owner/:repo/dispatches',requestBody).then(response=>(this.info=response)).catch(function(e){console.log(e);});});functionprintCompleteJSON(error){console.log(JSON.stringify(error));}
環境変数ファイルを作成して、センシティブな情報を入力します。
SLACK_SIGNING_SECRET
: Features > OAuth & Permissions > OAuth Tokens & Redirect URLs > Tokens for Your Workspace > Bot User OAuth Access Token ( https://api.slack.com/apps/{APP_ID}/oauth)SLACK_BOT_TOKEN
: Basic Information > App Credentials > Signing SecretGITHUB_TOKEN
: GitHub REST API v3を利用するためにアクセストークン
# .envファイルの作成touch .env
SLACK_SIGNING_SECRET=xxxxxxxxxx
SLACK_BOT_TOKEN=xoxb-xxxxxxxxxx
GITHUB_TOKEN=xxxxxxxxxx
作成し終わったらデプロイします。
$ serverless deploy
:
endpoints:
POST - https://xxx.execute-api.ap-northeast-1.amazonaws.com/dev/slack/events
GET - https://xxx.execute-api.ap-northeast-1.amazonaws.com/dev/slack/installation
GET - https://xxx.execute-api.ap-northeast-1.amazonaws.com/dev/slack/oauth
# => デプロイ後のURLをメモしておく
中身はこれで完成です。
Slackのコマンドの設定を行う
- Features > Interactive Components > Interactivity を有効化します。また、Request URL にデプロイ後のPOST
URL(
https://xxx.execute-api.ap-northeast-1.amazonaws.com/dev/slack/events
)を貼り付けて保存します。
https://api.slack.com/apps/{APP_ID}/interactive-messages - Features > Slash Commands で
/build
というスラッシュコマンドを作成します。URLにはhttps://xxx.execute-api.ap-northeast-1.amazonaws.com/dev/slack/events
を設定します。
https://api.slack.com/apps/{APP_ID}/slash-commands - Features > Event Subscriptions へアクセスし、有効化します。Enable Events > Request URL には
https://xxx.execute-api.ap-northeast-1.amazonaws.com/dev/slack/events
を設定します。Subscribe to bot events にはapp_mention
を追加します。
https://api.slack.com/apps/{APP_ID}/event-subscriptions - アプリを再インストールします。
https://api.slack.com/apps/{APP_ID}/install-on-team
ここまでの設定は「参考」の1つ目のseratch
さんの手順書と、2つ目のチュートリアルがとても参考になります。
また、開発のノウハウも書かれていますので一読をオススメします。
SlackからGitHub Actionsにパラメータを渡して実行する
- ワークスペースの適当なチャンネルにアプリを追加します。
- テキストボックスに
/build
と打ち込み、送信します。 - モーダルウィンドウが現れるので、項目を入力し、Submitボタンを押します。
- うまく設定できていれば、GitHub ActionsのJobが実行されます。
以上です。おつかれさまでした。
参考
https://github.com/seratch/bolt-starter
https://slack.dev/bolt/ja-jp/tutorial/getting-started
https://github.com/okimurak/slack-bolt-sample-app-aws
https://api.slack.com/dialogs
https://api.slack.com/surfaces/modals/using
https://dev.classmethod.jp/tool/slack-multistep-modal-example/
おわりに
うまく組み込めば、スマホから手軽に緊急対応できるようになっていいかもしれませんね。
それと、GitHub ActionsのJob終了時に、Slackに通知を送るようにすると色々と捗るかも。
また、時間がある時に少しいじってみようと思います。