AWS sam pipelineを使うとLambdaをデプロイするCI/CDパイプラインをどうやって作ればいいか分かりそうになる

## 背景
Lambdaをgit管理して単体テストに通ったらデプロイってどうやるんだろうみたいなことができる。

## 概要
AWS SAMには`sam pipeline`というコマンドがあり、これを使うことでパイプラインを自動で作ってくれる。
出来上がるステージとしては
CodeCommit
 -> CloudFormation (パイプライン自体の設定デプロイっぽい)
 -> CodeBuild テスト (オプション)
 -> CodeBuild デプロイ(sam deploy)

`sam pipeline`ではmainブランチに対してはデプロイ環境が二つできてしまう(上記の`sam deploy`を行うステージが2回ある)が、作るべきパイプラインの参考にはなる。
featureブランチに対しては上記のように`sam deploy`は1回のみ

## 手順参考
公式でしっかり説明してくれているのが以下くらいしか見当たらないが、これを参考にすればなんとかできる。
[コンテナ Lambda の CI/CD パイプラインを SAM Pipeline で作ろう !](https://aws.amazon.com/jp/builders-flash/202110/new-lambda-container-development-6/?awsf.filter-name=*all)

## やってみる
### 環境
Cloud9
sam --version 1.71.0

### パイプラインを使わずLambdaの単体テストをできるようにするまで
`sam init`でHelloWorld関数のみを作成。python選択。
pytestというライブラリでテストができそうなディレクトリができるので、これを使ってテストする。
なお、pytestはtest_から始まるファイルを実行してしまうようで、今回はAPI Gatewayは使わないので`tests/integration/test_apigw.py`は消しておく。

```
pip install -r tests/requirements.txt
pytest tests # 後のCodeBuild上では失敗したためpython -m pytest testsにしたほうがいいかも
```

testsディレクトリ内にある初期のテストコードが実行される。
とりあえずここで`sam deploy --guided`をしておくとデプロイできる。

### sam pipeline 初期状態(単体テストなしでデプロイのみ)
`sam pipeline bootstrap`を2回実行。
参考手順の通り、名前はdev, prodにする。質問はほとんどデフォルトで答えるとIAMロールも自動で作ってくれるので任せる。作成されたリソースは記事末の参考記事で説明がある。

2回実行するのは、そういうテンプレートしか今のところなさそうだから。。。

2回bootstrapしたら`sam pipeline init`実行し、参考手順の記事を見ながら質問に答えていく。

ここで以下のコマンドでいよいよ`sam deploy`。`codepipeline.yaml`はCodePipelineなどを定義したテンプレート。
```
sam deploy --guided \
 --template codepipeline.yaml \
 --stack-name php-lambda-app-pipeline-stack \
 --capabilities=CAPABILITY_IAM \
 --parameter-overrides="FeatureGitBranch=feature"
```

パイプライン自体のデプロイは完了する。完了後、featureブランチへの`git push`でLambdaをデプロイするためのパイプラインが開始される。
しかし以下のエラーでCodeBuildのデプロイステージが失敗していた。
```
Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: sam deploy --stack-name $(echo ${FEATURE_BRANCH_NAME} | tr -cd '[a-zA-Z0-9-]') --capabilities CAPABILITY_IAM --region ${TESTING_REGION} --s3-bucket ${TESTING_ARTIFACT_BUCKET} --no-fail-on-empty-changeset --role-arn ${TESTING_CLOUDFORMATION_EXECUTION_ROLE}. Reason: exit status 1
```

`Deploy this changeset? [y/N]: `の直後にエラーになっていることから、この質問にyesが押せていないだけに見える。

上記は`buildspec_feature.yml`で記述されて実行されているコマンドだが、ここの`sam deploy`コマンドに`-no-confirm-changeset`オプションを足して`git push`したらビルドできた。

これで単体テストなしで、`git push`によりLambda関数がデプロイできるパイプラインができた。

なお、mainブランチにpushすると多分2つのステージでデプロイされる。`codepipeline.yaml`を見ると、先ほどのFeatureGitBranchパラメータの値によって余分にリソースを作成しそうな箇所があった。

### パイプラインに単体テストを追加する
`codepipeline.yaml`をCodeBuildProjectUnitTestで文字列検索して、uncommentしろと書いてある箇所を全てコメントアウトする(3ヶ所)。
Uncomment and modify the following step for running the unit-tests

追加で作成されたCodeBuildのステージでは`buildspec_unit_test.yml`をbuildspecとして使うので、このファイルに`pytest`インストールと`pytest`実行を追加する。
```yaml
  install:
    runtime-versions:
      python: 3.8
    commands:
      - pip install -r tests/requirements.txt
  build:
    commands:
      # trigger the unit tests here
      - echo 'Running unit tests'
      - python -m pytest tests
```

なお、`python -m pytest tests`じゃなくてローカルと同じように`pytest tests`とすると以下のエラーになった
```
_________________ ERROR collecting tests/unit/test_handler.py __________________
ImportError while importing test module '/codebuild/output/src000000000/src/tests/unit/test_handler.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/root/.pyenv/versions/3.8.13/lib/python3.8/importlib/__init__.py:127: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
tests/unit/test_handler.py:5: in <module>
    from hello_world import app
E   ModuleNotFoundError: No module named 'hello_world'
```

[この記事](https://qiita.com/kiyo27/items/442e1b3a36491c67b196)のおかげで直った。

## 作成されたスタックを確認してみる
参考手順通りの命名になってしまっている。
Lambda自体のデプロイに使用されるfeatureスタック(Lambdaが作成される)の他は以下の3つ。
### php-lambda-app-pipeline-stack
ここで作成されたartifactのS3バケットにはcodepipeline.yamlやassume-role.sh, hello_worldディレクトリなど必要な資材が一式入っているようだ。これはCodePipelineのUpdatePipelineステージでCloudFormation deployにより指定されていたので、パイプライン自体の設定変更があればこのスタックに反映されそう

### aws-sam-cli-managed-dev-pipeline-resources
ここで作成されたartifactのS3バケットにはdev環境のlambdaのsam template, さらにその中で参照されているCodeUri(ただしこちらのコードはなぜか中身は壊れていて見えない

### aws-sam-cli-managed-prod-pipeline-resources
ここで作成されたartifactのS3バケットはまだ空。prod環境へのデプロイを行なっていないからだと思われる

## 参考
[SAM Pipelineで作成されるリソースを眺めてみる](https://dev.classmethod.jp/articles/sam-pipeline-resource/)
[AWS SAM Pipelineを試す](https://qiita.com/a_b_/items/a0db6047c964fdc00f24)