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

AWS, amplify, Slack, Lambda, API Gateway
2019-07-08
⚠️ 2019/07 の段階でOutgoing WebhooksはLegacy扱いとなり、
   SlackAppへの移行が推奨されています。

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

$ export AWS_PROFILE=deploy-profile
$ amplify push

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

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

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

An image from Notion

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です。

An image from Notion

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

AWS API Gatewayの設定

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

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

An image from Notion

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

An image from Notion

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

An image from Notion

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

リクエストのマッピングテンプレートに

application/x-www-form-urlencoded

を追加し、 マッピングテンプレートは以下のようにします。

An image from Notion
## 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のデプロイ を選択し、新しいステージを作成します。

An image from Notion

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

An image from Notion
$ 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を追加する。

An image from Notion

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

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

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

An image from Notion
Written by Kyohei Tsukuda who lives and works in Tokyo 🇯🇵 , building useful things 🔧.