Serverlessなネタで鉄板なのが、GitHubのPRやIssueなどの通知連携です。日々使っているものなのでついつい。
もちろん過去にもLambda + Node.jsやAzure Functionsで作っています。
では .NET Coreでもやってみましょう。
連携の流れ
Azure Functionsでは、Azure FunctionsのSecretとURLを直接GitHub Webhookに入力して連携しました。ただ入力するだけ、簡単ですね。

AWS Lambdaの場合は、WebhookをAPI Gatewayで受けるようなことはせず、Amazon SNSを使って連携することが定番なようです。Amazon SNSにするとAPI Gatewayと違ってレスポンスの受け口を作ったり管理する必要がほぼないのである意味ではとても楽です。*1

ということで今回もこの構成で行ってみましょう。Amazon SNSではなくAPI Gatewayで作っていたこともあったのですが余り楽しくないので推奨しません。
参考
Node.jsで行われていますが、構成は同じです。
下準備
いきなりコードに行きたいところですが、まずは下準備からです。
Slack で Incoming Webhook を作成
Lambdaの受け口となるWebhookの口をSlackに作成します。通知先のChannelを選んで適当にどうぞ。

これでLambdaが送る先のIncoming Webhook URLが確定しました。URLは後ほどLambdaの環境変数に仕込んでおくことになります。*2
SNSのトピック作成
次にGitHubが投げる先となるSNSの受け口トピックを作成しておきます。作成するAWS Lambdaと同一Regionで作っておくといいでしょう。今回はap-northeast-1で作ります。
SNSからCreate Topicを選んで

他のトピックと区別が付くようにつけておきましょう。

これでトピックを一意に示すTopic ARNが定まります。GitHubで入力するので把握しておきましょう。

今回は仮にarn:aws:sns:ap-northeast-1:123456789012:githublambdawebhookだったとします。
GitHub から SNS に投げる限定権限の IAM 作成
GitHubのApplication Integrationsを使ったAmazon SNSではIAMのaccess key / secret keyを直接入れることになります。

ということで指示に従ってSNSを投げることができるsns:Publish権限だけのIAMユーザーを作成しておきます。
ポリシーのResourceには、先ほどのSns ARN Resourceを指定します。

あとはIAM User作成時、あるいは後ほどでもAccess Key / Secret Keyを取得します。Secret Keyは作成時の画面でしか確認できないのでお気をつけて。

仮に、Access Keyをaccesskey12345、Secret Keyをaccesskey12345と表現します。
GitHub に Amazon SNS との連携を組む
では、GitHubの連携させたいリポジトリのSettings画面に行きましょう。
メニューからIntegrations & servicesを選択します。

サービス一覧からAmazon SNSを選びます。

あとは、AWSで作成したSNSトピックの情報、SNS送信専用IAM UserのAccess Key、Secret Keyを入れます。
| 項目 | 入力する内容 | 入力値の例 |
|---|---|---|
| Aws Key | SNS 専用 IAM User の Access Key | accesskey12345 |
| Sns topic | 作成した Sns の TOPIC Arn | arn:aws:sns:ap-northeast-1:123456789012:githublambdawebhook |
| Sns Region | 作成した Sns の Region。SNS Topicに書いてありますが入れます | ap-northeast-1 |
| Aws secret | SNS 専用 IAM User の Secret Key | secretkey12345 |

Integrations の Event を指定する
GitHubのWebhookを使わないWebhookで連携する場合の最大のめんどくさいポイントはこれです。
GitHub APIのhook.jsonにあるとおり、Amazon SNSのデフォルトEventはPushのみです。
この変更はWeb requestでしかできないのでしかたいのでcURLでやります。*3
対象とするイベントは、Webhooksで適当に決めます。

今回はIssue 処理時、Issue 処理時、Issue 処理時、Issue 処理時、Issue 処理時とします。
https://gist.github.com/guitarrapc/4494fc11ffdfd85a7dfe11a918b328ee
API経由で変更をかける際に利用するApplication Tokenは、GitHubユーザー プロファイルから作成できます。admin:repo_hookやadmin:repo_hookを有効にしておけばいいでしょう。

WebhookのIDは、URLに表示されています。
![]()
eventsが期待通りに設定されましたか? cURLの設定時のレスポンスで確認できます。

残りはAmazon SNSとLambdaのつなぎこみですが、Visual StudioからLambdaコード反映時にまとめて設定できるので後述します。
お疲れさまでした、長い下準備もおしまいです!
C#でGitHub Webhookを受ける
Azure Functionsで書いていたコードを使って書いてみましょう。
サンプルJSON
今回サンプルで利用した、SNSから送ってくるJSONは次のフォーマットです。これはIssueのサンプルなので、コードもIssueを想定したものとします。
https://gist.github.com/guitarrapc/817728780ce5f4055ba0b02cc53b4cc1
C#コード例
Issueが書かれたらSlackに飛ばすサンプルです。ほぼAzure Functionsのコードと違いはありません。
違いは2点のみです。
- フィールドにAWS Lambdaの環境変数からSlackWebhookUrlというキーを探してURLをstringとして取得する。パスワードなどはLambdaの環境変数でKMS使って暗号化もあり
- SNS Responseのデシリアライズ処理が入っている
当たり前の注意点だけ
- X-GitHub-Eventがプロパティ名に利用できないのでJSONPropertyとして指定する
- ただし、GitHubのイベントJSONはissueだったりPRだったりでフォーマットが変わるので、そこはAzureFunctiosn同様にdynamicで受ける
https://gist.github.com/guitarrapc/3c4efb06a3f27811e6c74d0b2218223e
今回はIssueに限定しましたが、適当に他のイベント対応もswitchに達せばいいでしょう。*4
xUnit テスト
ごく単純な実行テストです。様々なイベントに応じたJSONパターンをここでテストするといいでしょう。
https://gist.github.com/guitarrapc/1a26571ff916ce0db3d988a40df0239a
ローカル実行
ローカル実行する場合は、buildOptionsにemitEntryPointをtrueと設定してemitEntryPointを適当に書きましょう。
"buildOptions": {
"emitEntryPoint": true
},
https://gist.github.com/guitarrapc/09a2b184ecc95e7bb0152a82c2429cbf
これで普通のコンソールアプリケーションとして検証できます。
アップロード と 環境変数 と イベントソース設定
そろそろCIほしいですが、まだいったんVisual Studioでやります。適当にアップロード先Functionを作成します。

冒頭で作成したSlack Incoming WebhookのURLを環境変数に入れるのもここでできます。便利。

ついでに、Amazon SNSを起点にLambdaを実行するので、Event Sourcesも設定してしまいます。

もちろんWeb Console画面からやってもokです。


テスト実行
ローカル実行やテストする場合は、環境変数にLambdaで設定する環境変数と同じものを仕込んでおきます。
Isssueを適当に立ててコメントしてみます。

うまくSlackに通知されましたね。

テスト中の実行グラフも出ています。

失敗や成功のログもCloud Watch Logsに出力されています。

まとめ
Azure Functionsと比べるとSNSが増えてるだけなのに手間が大きく違いますね! シカタナイとは言え、GitHubとのつなぎこみに特化対応しているAzure Functionsはこの面で圧倒的に便利です。ただ、.csxでやり続けるのとどっちがいいかというと、どうでしょうかねぇ。
さて、今回のサンプルも挙げておきます。参考になれば幸いです。