AWS ChatbotでSlackからLambda関数(Go)を呼び出す　

## はじめに
CICDの一環でGoのLambdaをSlackから呼び出す仕組みを構築したのでその備忘録を残す。
（Goは初見だったので少しGoを触った所も書き残した。）

![Slack-AWS Chatbot Lambda実行.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/a177a0ba-ba3d-a1fc-375f-0711b8e30638.png)

https://github.com/yuta-katayama-23/Go

## Goを使ってみる
### install
https://golang.org/doc/install からDowloadしてくる。
`go version`でGoのバージョンが表示されればOK。

```
C:\Users\user>go version
go version go1.16.7 windows/amd64
```

### hello world
[はじめてのGo](https://github.com/yuta-katayama-23/Go/commit/a24dbd81df6ebc32e0bb739b9b50a54f7c91e136)

```golang:hello
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
```

実行結果は以下のようになる。
CIでGoを動かすのに使ったのは[これ](https://github.com/actions/setup-go)。

```
# Execute Go code
go run .
Hello, World!
```

https://github.com/yuta-katayama-23/Go/runs/3287079425?check_suite_focus=true

### hello world②（他のパッケージを呼び出す）
[別のmoduleを利用する](https://github.com/yuta-katayama-23/Go/commit/6e6a4fa175974222200a2228638a3f871ac19713)

```golang:hello
package main

import "fmt"
import "rsc.io/quote"

func main() {
    fmt.Println("Hello, World!")
	fmt.Println(quote.Go())
}
```
```
# Execute Go code
go run .
Hello, World!
```

https://github.com/yuta-katayama-23/Go/runs/3287131512?check_suite_focus=true

### Goの言語仕様について
以下のサイトが分かりやすく参考にさせて頂いた

- [他言語プログラマが golang の基本を押さえる為のまとめ](https://qiita.com/tfrcm/items/e2a3d7ce7ab8868e37f7)

#### `:=`
変数の宣言＋代入を同時に実行する（変数の宣言を省略している）

#### `&`
変数の値そのものではなくポインターを渡すために使う

Goには変数の型にポインタータイプ（`Pointer types`）というものがあり、他の言語でいう参照型になる変数が明確に型として定義されている
ので、例えば文字列でも以下のように2パターンを見かける

- string<br>純粋に値そのものが格納された変数（リテラル）
- *string<br>値ではなくstringの変数が可能されているメモリのポインターが格納された変数

具体的には、

```go
import(
   "github.com/aws/aws-sdk-go/aws/session"
)

func aws() {
   region := os.Getenv("REGION")

   mySession, err := session.NewSession(&aws.Config{
      Region: &region,
   })
}
```

のようにAWSのSDKを使いセッションを作る時、`Config`の`Region`は`*string`となっているのでこれはポインターの参照を与える必要がある
この時に、`&`を使って渡すとstringのポインター（*string）を渡す事ができる

・参考：https://github.com/aws/aws-sdk-go/blob/0caab5abe6b25070faba0ecd2b870288d392c5d8/aws/config.go#L33

## GoのLambda関数を作成しDeploy
### 関数の作成
[AWS公式](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-handler.html)をみてまずはコピペでLambda関数を作ってみる。
https://github.com/yuta-katayama-23/Go/commit/725f6887f47693ce3c9c5249fb98737bff017dd6#diff-decea487f8d3f70e6cd009e31ddbd0aacfd2258125f30b612a6c6415b81dce14

このソースをAWS公式に載っている手順でzipを作成しLambda関数をDeployする。

### Deploy（zip作成手順）
[.zip ファイルアーカイブを使用して Go Lambda 関数をデプロイする](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-package.html)に書かれている通りにやれば簡単にできる。

#### 1、build-lambda-zip ツールを GitHub からダウンロード
`go.exe get -u github.com/aws/aws-lambda-go/cmd/build-lambda-zip`を cmd で実行する（初回のみ）。
これでgoのLambdaを作成するためのツールをローカルにダウンロードする。

#### 2、build-lambda-zip ツールで zip を作成
`set GOOS=linux`を cmd で実行し環境変数を設定する。
続いて以下のコマンドを順に実行して zip を作成する。

```
go build -o main main.go
%USERPROFILE%\Go\bin\build-lambda-zip.exe -output main.zip main
```

zip 作成後は AWS Management console から zip を upload して Deploy をする。

#### 【参考】`go build -o main main.go`でエラー『go.mod file not found in current directory or any parent directory; see 'go help modules'』
[^GO111MODULE]: https://stackoverflow.com/questions/66894200/go-go-mod-file-not-found-in-current-directory-or-any-parent-directory-see-go
Goのmodule管理の仕組みである`go.mod`がない時にbuildすると起きるエラー。
これは以下のコマンドを実行する事で解決できる[^GO111MODULE]。

```
go env -w GO111MODULE=off
```

上記のコマンドを実行する事で、`Go Modules`を無効にしGOPATHモード（`go get`したものが`$GOPATH/src`に入るがそれを参照する）でbuildできるので、`go.mod`がないというエラーは起きなくなる。

参考：[モジュール対応モードへの移行を検討する](https://text.baldanders.info/golang/go-module-aware-mode/)
参考：[Go Modules](https://qiita.com/propella/items/e49bccc88f3cc2407745)

#### 【参考】zip 作成時に他の設定ファイルなどを取り込みたい時
以下のように、zip 化する際にその対象のファイルを指定すればよい。

```
C:\Users\user\go\bin\build-lambda-zip.exe -output main.zip main config.yaml
```
参考：https://stackoverflow.com/questions/56607738/cannot-read-json-file-after-uploading-go-package-into-aws-lambda

### Lambdaを実行してみる
AWS公式からコピペしてきたコードでは以下のようにeventオブジェクトを定義しているので、

```golang
type MyEvent struct {
        Name string `json:"name"`
}
```

以下のようなJSONをLambdaのイベントドキュメントに記載して「テスト」をすると、

```json
{"name": "Hoge"}
```

以下の画像のように`"Hello Hoge!"`がLambda関数から返却される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/ee467647-b680-3acb-cde0-c350ff20b40e.png)

## SlackからAWS Chatbotを呼び出しLambdaを実行
SlackからAWS Chatbotを呼び出すための設定の仕方は[これ](https://dev.classmethod.jp/articles/execute-lambda-function-with-slack/#toc-3)が分かりやすいのでそれを参照。

### IAMについて
AWS Chatbot専用のアカウントを作成するのに必要なIAM roleは以下。
後で気づいたが別にAWS Chatbotの設定をするユーザに制限をかける必要はない気がする・・・が一応調べたのでおまけ程度に載せておく。

- chatbot:
 - **chatbot:GetSlackOauthParameters**<br>「チャットクライアントを設定」で「チャットクライアント」を`Slack`に設定後、「クライアント設定」をクリックしてSlackとの連携のための認証を設定するための権限（これないとSlackとの連携のための認証許可のページまで遷移できない）
 - **chatbot:RedeemSlackOauthCode**<br>SlackとAWS Chatbotとの連携の認証をSlackの画面で「許可」した後にSlackの認証情報をAWS側に償還するための権限（Redeemが償還する・請け出すとかいう意味らしい）
 - **chatbot:DescribeSlackWorkspaces**<br>「Slack チャネルを設定」の「Slack チャネル」でSlackのチャンネルIDを取得するために必要なワークスペースの情報をロードするための権限
 - **chatbot:DescribeSlackChannels**<br>「Slack チャネルを設定」の「Slack チャネル」でSlackのパブリックチャンネルのチャンネルIDを取得するための権限
 - **chatbot:CreateSlackChannelConfiguration**<br>「Slack チャネルを設定」の「設定」ボタンをクリックしてAWSチャットボットにSlackチャネル設定を作成するための権限
 - **chatbot:DescribeSlackChannelConfigurations**<br>AWSアカウントのすべてのAWSチャットボットSlackチャネル設定を一覧表示するための権限
- iam:
 - **iam:CreateRole**<br>「Slack チャネルを設定」の「アクセス許可」でIAMロールを作成するための権限
 - **iam:CreatePolicy**<br>「Slack チャネルを設定」の「アクセス許可」でIAMロールを作成する時に必要になるポリシーを作成する権限
 - **iam:AttachRolePolicy**<br>「Slack チャネルを設定」の「アクセス許可」でIAMロールにポリシーをアタッチするための権限
 - **iam:PassRole**<br>サービスにロールを渡す許可を付与するための権限（今回だとAWS Chatbotへ権限を付与する）

インラインポリシーとしては以下のような感じになる。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "chatbot:DescribeSlackWorkspaces",
                "chatbot:RedeemSlackOauthCode",
                "chatbot:CreateSlackChannelConfiguration",
                "chatbot:DescribeSlackChannels",
                "chatbot:DescribeSlackChannelConfigurations",
                "chatbot:GetSlackOauthParameters"
            ],
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "iam:CreatePolicy",
                "iam:PassRole",
                "iam:CreateRole",
                "iam:AttachRolePolicy"
            ],
            "Resource": [
                "arn:aws:iam::XXXXXXXXXX:role/*",
                "arn:aws:iam::XXXXXXXXXX:policy/*"
            ]
        }
    ]
}
```

### Slackからの`@aws`コマンドでLambdaを実行する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/37425575-1143-feae-5ac4-46b566a13202.png)

## 参考文献
- [Go Documentation](https://golang.org/doc/)
- [SlackでAWS Lambdaを実行してみた](https://dev.classmethod.jp/articles/execute-lambda-function-with-slack)
- [AWS ChatbotからのLambda起動をCloudFormationとSlackワークフローで使いやすくする](https://dev.classmethod.jp/articles/chatbot-lambda-cfn/)