kyohei's blog

AWS AmplifyをSlackからデプロイする

AWS Amplifyは通常コミット時に自動的にデプロイのスクリプトが走りますが、 Gitで管理していないデータを取り込んだりする際には、 以下のように amplify-cli のコマンドをローカルから叩いて実行する必要があります。

$ export AWS_PROFILE=deploy-profile
$ amplify push

シンプルにはこれでよいのですが、

  • amplifyコマンドを実行するときにローカルのawsユーザを切り変えたりするのが面倒
  • amplify-cliが入っていない別のPCでは実行できないといった問題が発生する

上記のような問題から以下のフローのようにSlackからdeployを実行できるようにしたいと思います。

flow

AWS AmplifyのIncoming webhooksの設定

AWS AmplifyにはZapierやJenkinsなど外部のサービス・ツールからでもビルドをトリガするためのwebhookが用意されています。 documentはこちらIncoming Webhooks

AWS Amplifyのコンソールからビルドの設定の下部のIncoming webhooksを新規に作成します。 生成されたCommandをを実行するとビルドを実行できることがわかります。

$ curl -X POST -d {} \
  "https://webhooks.amplify.ap-northeast-1.amazonaws.com/prod/webhooks?id=xxxx&token=xxxxxx&operation=startbuild"\
  -H "Content-Type:application/json"

> {"SendMessageResponse":{"ResponseMetadata":{"RequestId":"...

AWS Lambdaからビルドを実行する

SlackのOutgoing WebHooksでこのURLを呼び出せるといいのですが、 Outgoing WebHooksからPOSTするとコンテンツの形式がJSONでなくform-urlencodedで送られてしまうので、うまく送信できないようです 😓

なので、上記のコマンドをLambdaで起動できるようにスクリプトを作成します。

AWS Lambdaを開き、以下のようにnode.jsの関数を作成します。(POSTのリクエストができればいいので、正直言語は何でもいいです)

以下のような、シンプルなPOSTリクエストの関数を作成します。

const https = require('https');
// AWS Amplifyから生成されたもの
const amplifyId = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
const token = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

const options = {
  hostname: 'webhooks.amplify.ap-northeast-1.amazonaws.com',
  port: 443,
  path: `/prod/webhooks?id=${amplifyId}&token=${token}&operation=startbuild`,
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  }
};

exports.handler = (event) => {
  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      console.log(`statusCode: ${res.statusCode}`);

      res.on('data', (d) => {
        // レスポンスに`text`を返すことでそれをslackに表示できる
        const response = {
          text: "[AWS Amplify] build starting..."
        };
        resolve(response);
      });
    });

    req.on('error', (error) => {
      console.error(error);
      reject(error);
    });

    req.end();
  });
};

AWS Lambda上で「テスト」を実行し、成功したらOKです。

lambda_image

Slackへのメッセージのカスタマイズは Formatting messages | Slack を参照ください。

AWS API Gatewayの設定

AWS API GatewayからLambdaを起動するようにします。

まずはAPI Gatewayで新しいRESTのAPIを作成します。 create api gateway1

POSTアクションを作成し、上記で作成したLambda関数を指定します。 create api gateway2

クライアントの「テスト」を実行し、正常に動作していることを確認します。(現状引数はなくてもよいので指定する必要はないです) create api gateway3

SlackのOutgoing WebHooksから送られてくるPOSTリクエストは本文のコンテンツの形式(Content-Type)がapplication/x-www-form-urlencodedになっているので、それをJSONとして扱えるように変換する必要があります。

リクエストのマッピングテンプレートにapplication/x-www-form-urlencodedを追加し、 マッピングテンプレートは以下のようにします。 create api gateway3_5

## POSTで送られてきたデータ変数に入れる
#if ($context.httpMethod == "POST")
 #set($rawAPIData = $input.path('$'))
#else
 #set($rawAPIData = "")
#end


## &で区切られたKey-Valueをセットにする
#set($tokenisedAmpersand = $rawAPIData.split("&"))
#set($tokenisedEquals = [])

#foreach( $kvPair in $tokenisedAmpersand )
 #set($countEquals = $kvPair.length() - $kvPair.replace("=", "").length())
 #if ($countEquals == 1)
  #set($kvTokenised = $kvPair.split("="))
  #if ($kvTokenised[0].length() > 0)
   ## we found a valid key value pair. add it to the list.
   #set($devNull = $tokenisedEquals.add($kvPair))
  #end
 #end
#end

## "{" と "}"で括り、出力する
{
#foreach( $kvPair in $tokenisedEquals )
  #set($kvTokenised = $kvPair.split("="))
 "$util.urlDecode($kvTokenised[0])" : #if($kvTokenised[1].length() > 0)"$util.urlDecode($kvTokenised[1])"#{else}""#end#if( $foreach.hasNext ),#end
#end
}

次に外側から呼び出すために「ステージ」が必要なので、 アクションから APIのデプロイ を選択し、新しいステージを作成します。

new stage

ステージにデプロイされたPOSTリクエストを確認します。

check stage

$ curl -X POST \
  https://xxxx.execute-api.ap-northeast-1.amazonaws.com/prod/ \
  -H "Content-Type:application/x-www-form-urlencoded"

> {"text": "..."}

Lambda関数で定義した戻り値が表示されていることを確認できます。

slackからAPIを呼び出す

slackのapps設定ページ(https://[YOUR_ORG].slack.com/apps)からoutgoing webhookを追加し、 “Add Configuration”から新規のwebhookを追加する。

slack outgoing

例として、以下のように設定する。

項目
Channel #general
Trigger Word(s) amplify-build
URL(s) https://xxxx.execute-api.ap-northeast-1.amazonaws.com/prod (API Gatewayで生成されたURL)
Token そのまま

Descriptive Label、Customize Name、IconはなんでもOKです。

slackでamplify-buildと発言するとLambdaが動作し、amplifyのビルドが実行されていることが確認できました。 引数等も調整すればいろんなブランチへのデプロイも指示できそうですね。

slack

 ⚠️ 2019/07 の段階でOutgoing WebhooksはLegacy扱いとなり、SlackAppへの移行が推奨されています。そのあたりは次回書きたいと思います。

Kyohei Tsukuda
Written by Kyohei Tsukuda who lives and works in Tokyo building useful things.