ローカルでビルドしたものを package するときに --template-file オプションを付けるタイミング

# TL; DR

`sam build` でビルド先ディレクトリの出力先を指定した場合は、 `sam package` で `--template-file` を指定しましょう。
そうでない場合（殆どが指定しないと思います）は、指定しない方が無難です。



# はじめに

下記のような記事がありました。
私も最初は `sam package` に `--template-file` オプションを付けて苦しめられていたため、参考になりました。

[ローカルでビルドしたものをpackageするときは --template-file オプションはつけない - Qiita](https://qiita.com/billthelizard/items/96a8a0a213a776b84d8c)

では、逆に `--template-file` を指定するのはいつなのか、調べてみました。



# フローの種類

`sam build` と `sam package` の挙動を知るには、ビルド手順を追うと分かりやすいです。
そこで、本章ではアプリケーションビルド手順を順次追っていきます。

SAM-CLIによるアプリケーションビルド手法は、次の4種類があります。

1. deploy
2. build -> deploy
3. build -> package -> deploy
4. build -> package -> publish

各フローについて、それぞれ説明します。

今回は、Node.jsによるLambdaアプリケーションのSAMテンプレートファイルを用いて説明します。
そのため、一部に npm コマンドの比喩が登場します。
Node.js固有の問題は無いと思うので、LambdaランタイムがPythonの場合はpipと読替えるなど、よしなに読んでください。


## deploy

直接デプロイする手法です。

デプロイとは、テンプレートファイルで指定したディレクトリを直接SAMアプリケーション（CloudFormationスタック）として生成することです。

そのため、 `--template-file` で指定したテンプレートファイルの中身にアプリケーションリソースのURLを指定している必要があります。

```bash:直接デプロイする際のコマンド
sam deploy --template-file template.yaml
```

```text:直接デプロイする際のディレクトリ構造
application/
 |- sample-app/
 |   |- node_modules/*
 |   |- package.json
 |   |- index.js
 |
 |- template.yaml
```

```yaml:直接デプロイする際のtemplate.yaml（抜粋）
Resources:
  DirectlyDeploymentFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./sample-app/
      Handler: index.handler
      Runtime: nodejs12.x
      Description: ローカルの sample-app/index.js ファイルにおける index.handler をAWS Lambdaに展開
```

直接デプロイする際は、テンプレートファイルの `CodeUri` で指定しているディレクトリ内のソースコードを実行できる必要があります（`npm run` などの実行スクリプトが動作可能な状態である必要がある、という意味です）。
依存ライブラリなどがある場合は、事前に `npm install` などでインストールしておきましょう。

[sam deploy - AWS サーバーレスアプリケーションモデル](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-deploy.html)


## build -> deploy

ビルドしてからデプロイする手法です。

ビルドとは、 テンプレートファイル自身、および、テンプレートファイルの `CodeUri` で指定しているディレクトリをコピーした「ビルド先ディレクトリ」の2つを生成し、生成先ディレクトリ内の `package.json` や `requirement.txt` を再帰的に探し、 `npm install` を実施することです。
既定では、 `.aws-sam/build/` ディレクトリ内にコピーしますが、 `--build-dir` オプションで出力先を変更できます。

デプロイ時のテンプレートファイルは、既定では `.aws-sam/build/` ディレクトリ内の `template.yaml` または `template.yml` ファイルを取込みます。
そのため、 `--template-file` での指定は必要ありません。
しかも、 `.aws-sam/build/` ディレクトリ内のテンプレートファイルに書いてある `CodeUri` は、自動で書換えられます。

```bash:ビルドしてからデプロイする際のコマンド
sam build
sam deploy
```

```text:ビルド後のディレクトリ構造
application/
 |- sample-app/
 |   |- package.json
 |   |- index.js
 |
 |- .aws-sam/
 |   |- build/
 |       |- DeploymentBuildedFunction/
 |       |   |- node_modules/*
 |       |   |- package.json
 |       |   |- index.js
 |       |
 |       |- template.yaml
 |
 |- template.yaml
```

```yaml:ビルド後の.aws-sam/build/template.yaml（抜粋）
Resources:
  DeploymentBuildedFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: DeploymentBuildedFunction # 書換えられている
      Handler: index.handler
      Runtime: nodejs12.x
      Description: ローカルの .aws-sam/build/index.js ファイルにおける index.handler をAWS Lambdaに展開
```

コピー先ディレクトリ `.aws-sam/build/` 内で `npm install` をするため、 `sample-app/` ディレクトリ内での `npm install` は必要ありません。

[sam build - AWS サーバーレスアプリケーションモデル](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-build.html)


## build -> package -> deploy

ビルド／パッケージングしてデプロイする手法です。

パッケージングとは、作業中PC（SAM-CLIを実行しているPCのこと）のソースコードを指定したS3バケットにパッケージングしてアップロードすることです。
そして、指定したS3バケットのURLを示すように `CodeUri` を書換えたテンプレートを出力します。
なぜか既定では標準出力に出力しますが、 `--output-template-file` オプションでファイル出力ができます。

デプロイする際は、パッケージングで出力されたテンプレートファイルを指定します。

```bash:ビルド／パッケージングしてデプロイする際のコマンド
sam build
sam package \
    --s3-bucket UploadedApplication \
    --output-template-file output-template.yaml # 出力ファイルを指定する
sam deploy --template-file output-template.yaml # sam packageコマンドで出力されたファイルを指定する
```

```text:ビルド／パッケージング後のディレクトリ構造
application/
 |- sample-app/
 |   |- package.json
 |   |- index.js
 |
 |- .aws-sam/
 |   |- build/
 |       |- DeploymentPackagedFunction/
 |       |   |- node_modules/*
 |       |   |- package.json
 |       |   |- index.js
 |       |
 |       |- template.yaml
 |
 |- output-template.yaml
 |- template.yaml
```

```yaml:ビルド／パッケージング後のoutput-template.yaml（抜粋）
Resources:
  DeploymentPackagedFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: s3://UpploadedApplication/xxxxxxxx # --s3-bucket オプションで指定したディレクトリ先のランダムな名前のパッケージファイル
      Handler: index.handler
      Runtime: nodejs12.x
      Description: S3バケットのパッケージファイルにおける index.handler をAWS Lambdaに展開
```

前節を読んだ方はお気づきかと思いますが、パッケージングする際の入力テンプレートファイルは、 `.aws-sam/build/` ディレクトリ内のテンプレートファイルです。
また、パッケージング対象は、上記で（明示的または暗黙的に）指定したテンプレートファイルの `CodeUri` で指定しているディレクトリです。
パッケージファイルにテンプレートファイル自身は含まれません。

[sam package - AWS サーバーレスアプリケーションモデル](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-package.html)


## build -> package -> publish

ビルド／パッケージングして発行（パブリッシュ）する手順です。

発行（パブリッシュ）とは、SAR (Serverless Application Repository) と呼ばれる公開可能なアプリケーションリポジトリにテンプレートファイルを登録することです。
`--template-file` オプションで指定したテンプレートファイルを、AWSのクラウドサービスに登録するため、ローカルパスを指定しているパッケージング前のテンプレートファイルではなく、S3バケットを指定しているパッケージング後のテンプレートファイルを指定する必要があります。


```bash:ビルド／パッケージングして発行する際のコマンド
sam build
sam package \
    --s3-bucket UploadedApplication \
    --output-template-file output-template.yaml
sam publish --template-file output-template.yaml
```

- [sam publish - AWS サーバーレスアプリケーションモデル](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-publish.html)
- [AWS Serverless Application Repository（サーバーレスアプリを検索、デプロイ、公開）| AWS](https://aws.amazon.com/jp/serverless/serverlessrepo/)
- [AWS Serverless Application Repository とは - AWS Serverless Application Repository](https://docs.aws.amazon.com/ja_jp/serverlessrepo/latest/devguide/what-is-serverlessrepo.html)

なお、2020年6月時点では、SARに発行したアプリケーションをデプロイする場合、AWSマネジメントコンソール上で手動デプロイするか、AWS-CLIを使う必要があります。
`sam publish` した際にアプリケーションARNを出力してくれるため、 `aws serverlessrepo` コマンドでデプロイすれば良いです。
本記事では話がズレてしまうため、詳細は省略します。

[アプリケーションをデプロイする方法 - AWS Serverless Application Repository](https://docs.aws.amazon.com/ja_jp/serverlessrepo/latest/devguide/serverlessrepo-how-to-consume.html#consuming-applications-cli)



# ローカルでビルドしたものを package するときに `--template-file` オプションを付けるタイミング

本題です。

結論を先に言ってしまうと、ローカルでビルドしたものを package するときに `--template-file` オプションを付けるタイミングとは、__ビルド先のディレクトリを変更した場合__です。
ビルドの振舞いが理解できれば、パッケージングの振舞いも理解できると思います。

`sam build` は、既定ではテンプレートファイルの CodeUri で指定しているディレクトリを `.aws-sam/build/` ディレクトリにコピーすると書きました。
また、 `sam package` は、既定では `.aws-sam/build/` ディレクトリ内のテンプレートファイルを呼出してパッケージファイルをS3にアップロードすると書きました。

つまり、 `sam build` でビルド先（コピー先）ディレクトリを `--build-dir` オプションで書換えた場合、 `sam package` でテンプレートファイルを `--template-file` で指定する必要があります。

```bash:ビルド先ディレクトリを指定した場合のビルド／パッケージングしてデプロイする際のコマンド
sam build \
    --build-dir builded-sample-app/ # ここで出力先を指定すると
sam package \
    --s3-bucket UploadedApplication \
    --template-file builded-sample-app/template.yaml \ # ここで入力先を指定する必要がある
    --output-template-file output-template.yaml
sam deploy --template-file output-template.yaml
```

```text:ビルド先ディレクトリを指定した場合のビルド／パッケージング後ディレクトリ構造
application/
 |- sample-app/
 |   |- package.json
 |   |- index.js
 |
 |- builded-sample-app/
 |   |- DeploymentPackagedFunction/
 |   |   |- node_modules/*
 |   |   |- package.json
 |   |   |- index.js
 |   |
 |   |- template.yaml
 |
 |- output-template.yaml
 |- template.yaml
```

次のような場合に使えると思います。

- 複数のアプリケーションを1つのディレクトリ内でデプロイしたい
- ネステッドスタックを作りたい（ビルドを2回する必要がある）



# おわりに

SAM-CLIの挙動は、チュートリアルからでは全く理解できませんが、少しずつ理解していくと意外とシンプルです。

Serverlessの時代、SAMを使ってAWSゾッコンになっていきましょう！


# 参考

[AWS サーバーレスアプリケーションモデル (AWS SAM) とは - AWS サーバーレスアプリケーションモデル](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/what-is-sam.html)