Github Actionsを使ってRuby製のAWS Lambda関数を更新する

# モチベーション
AWSコンソールを開いたり、AWS CLIを使わずに、gitの操作だけでAWS Lambda関数のデプロイを完了します。


近年、[Github Acitons](https://github.com/features/actions)というGithub純正のCI/CDツールがリリースされました。
これを使って、Githubで管理しているAWS Lambda関数の変更を、AWSに反映します。

# 前提条件

今回はRubyで作成されたAWS Lambda関数を更新します。
Lambda関数とデプロイスクリプトが同じ言語だとメンテナンスが楽なので、デプロイにもRubyを使います。

## 先駆者

検索すると、AWS Lambda関数をデプロイするGithub Acitonsがいくつか公開されているのが見つかります。[^1]

[^1]: https://github.com/marketplace?utf8=%E2%9C%93&type=actions&query=lambda

例えば [Deploy AWS Lambda function](https://github.com/marketplace/actions/deploy-aws-lambda-function)  の[ソースコード](https://github.com/yvesgurcan/deploy-lambda-function)を見てみましょう。`aws-sdk`を使っているのがわかります。[^2]
その中でも`update_function_code`メソッドを使っています。[^3]

引数にはZipファイルを指定しています。
AWSコンソールでAWS Lambda関数を更新するときと同様に、zipファイルを作って更新すれば良さそうです。

Ruby用の[aws-sdk-lambda](https://rubygems.org/gems/aws-sdk-lambda) gemを使います。


[^2]: https://github.com/yvesgurcan/deploy-lambda-function/blob/master/index.js#L4

[^3]: https://github.com/yvesgurcan/deploy-lambda-function/blob/master/index.js#L34

# 準備

## IAMユーザー

万が一、AWSのIDとパスワードが漏れたときに、該アカウントだけを停止できるように、AWS上にdeploy専用のIAMユーザーを作成します。
必要なIAMポリシーは次のとおりです。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "lambda:UpdateFunctionCode",
            "Resource": "arn:aws:lambda:ap-northeast-1:XXXXXXXX:function:tange"
        }
    ]
}
```

`Action`には今回使いたい`update_function_code`メソッドを表す`lambda:UpdateFunctionCode`を指定します。
`Resource`には、更新対象のLambda関数の`ARN`の値を指定します。ここでは
`arn:aws:lambda:ap-northeast-1:XXXXXXX:function:tange`を指定しました。

## Github Secrets
Github Actionsには、リポジトリに設定されたパスワードのような秘密情報を参照する機能があります。
IAMの`access_key_id`と`secret_access_key`をGithub Actionsから参照するのに使います。

手順は[暗号化されたシークレットの作成と利用 - GitHub ヘルプ](https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)を参照してください。

# Github Actions
## 完成形

最初に完成形を示します。これをLambda関数を管理しているリポジトリに追加すると、Github Acitonsは動作します。

```yaml:.github/workflows/deploy_lambda.yml
name: Ruby

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1

    - name: Ruby 2.5の環境を作る
      uses: actions/setup-ruby@v1
      with:
        ruby-version: 2.5.x

    - name: 依存gemをダウンロード
      run: |
        gem install bundler
        bundle config set deployment 'true'
        bundle install

    - name: zip圧縮
      run: |
        zip deploy_package lambda_function.rb -r vendor

    - name: AWSにアップロード
      env:
        access_key_id: ${{ secrets.access_key_id }}
        secret_access_key: ${{ secrets.secret_access_key }}
      run: |
        gem install aws-sdk-lambda
        bin/deploy
```


## 各ジョブの解説

### リポジトリへのpushに応じてGithub Actionsを起動する

```yml
on: [push]
```

onには色々なトリガーが指定できます。
詳しくは[ワークフローをトリガーするイベント - GitHub ヘルプ](https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows)を見てください。

### ソースコードをチェックアウトする

```yml
- uses: actions/checkout@v1
```

### Ruby 2.5の環境を作る

AWS Lambdaはruby 2.5をサポートしています。[^4]

[^4]: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html


```yml
- name: Ruby 2.5の環境を作る
  uses: actions/setup-ruby@v1
  with:
    ruby-version: 2.5.x
```

### 依存gemをダウンロード
```yaml
- name: bundle install
  run: |
    gem install bundler
    bundle config set deployment 'true'
    bundle install
```

AWS Lambdaでは、関数は実行可能は状態でアップロードする必要があります。
つまりアップロードzipファイルには依存ライブラリも入れる必要があります。
zipファイルに入れやすくするため、依存gemをローカルディレクトリにインストールします。

`bundle config set deployment 'true'`を設定すると、依存ライブラリは`vendor`ディレクトリ配下に格納されます。


### zip圧縮

zipコマンドで必要なファイルを1つのzipファイルに纏めます。

```yaml
- name: Zip
  run: |
    zip deploy_package lambda_function.rb -r vendor
```

`deploy_package`は作成するzipファイル名です。

`lambda_function.rb`はzipファイルに含めるファイル名です。
AWS LambdaでRubyランタイムを使うときのデフォルトファイル名が`lambda_function.rb`です。
他に必要なファイルがあれば、空白で区切って続けて書いてください。


`-r vendor`は`bundle install`コマンドで保存した依存ライブラリをzipファイルに入れるためにつけています。
他に必要なディレクトリがあれば追加してください。

### AWSにアップロード
```yaml
- name: Deploy
  env:
    access_key_id: ${{ secrets.access_key_id }}
    secret_access_key: ${{ secrets.secret_access_key }}
  run: |
    gem install aws-sdk-lambda
    bin/deploy
```

Github Secretsに設定した秘密情報を読み込みます。

```yaml
env:
  access_key_id: ${{ secrets.access_key_id }}
  secret_access_key: ${{ secrets.secret_access_key }}
```

`${{ secrets.access_key_id }}` がGithub Secretsを参照している箇所です。

デプロイに必要な`aws-sdk-lambda` gemをインストールして、デプロイを実行します。

```yaml
run: |
  gem install aws-sdk-lambda
  bin/deploy
```

`bin/deploy`はRubyスクリプトです。


```rb:bin/deploy
#!/usr/bin/env ruby
require "aws-sdk-lambda"

client = Aws::Lambda::Client.new region: "ap-northeast-1",
                                 access_key_id: ENV["access_key_id"],
                                 secret_access_key: ENV["secret_access_key"]

zip_file = File.open "deploy_package.zip", "r"
client.update_function_code function_name: "tange", zip_file: zip_file
```

[Aws::Lambda::Client#update_function_code](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Lambda/Client.html#update_function_code-instance_method)を使って、AWS Lambda関数を更新します。