🦢

【AWS SAM CLI】sam validate、sam syncの動作とManifestを理解する。

2022/11/05に公開

前回のSAM CLIでinit、build、deploy、deleteを試してみました。
https://zenn.dev/mjxo/articles/004b3e0346718c

今回はその続きとなります。

sam validate、sam syncの2つについて調べてみたいと思います。

sam validateについて

sam validate

AWS SAM テンプレートファイルが有効かどうかを検証します。

ローカルの任意のディレクトリでsam initをして生成された、
sam-appというプロジェクトのフォルダに移動し、sam validateを実行してみます。

$ cd sam-app
$ sam validate
2022-11-05 12:45:11 Loading policies from IAM...
2022-11-05 12:45:13 Finished loading policies from IAM.
/Users/[Your Directory]/sam-app/template.yaml is a valid SAM Template

「template.yamlは有効な SAM テンプレートです。」との事です。

ではテンプレート内のGlobals:セクションのsを取り Global:状態にする、というミスを仕込んでもう一度試してみます。

$ sam validate
2022-11-05 12:56:05 Loading policies from IAM...
2022-11-05 12:56:08 Finished loading policies from IAM.
2022-11-05 12:56:08 Template schema validation reported the following errors: [.] Additional properties are not allowed ('Global' was unexpected)
/Users/[Your Local Directory]/sam-app/template.yaml is a valid SAM Template

「テンプレートスキーマの検証で以下のエラーが報告されました。[追加のプロパティは許可されません('Global'は予期されません)。」というエラーを検出して報告してくれていますが、「有効な SAM テンプレートです。」というのは変わらないようです。


これは通常build前のテンプレートをvalidateする事に使うものなのか。

使い所と、旨味がいまいちわかっていません。

「cloudformation validate-template」に触れてみた際に
https://zenn.dev/mjxo/articles/69998312515bcd
は「スタック失敗してロールバックしてくれるし、VSCODEにもcfn-lintがあるし、使う必要あるかな」という疑問は結局拭えませんでした。

が、SAMの場合は「こういう時便利」という旨味があるのかな。と探しましたが、
この話題に限らず、機能の説明より先にある「どう有用か」を網羅した記事は毎度見つかりにくいです。

一旦、今回は「出来る出来ない」だけに限定して確認してみる事にします。


という事で、
先程仕込んだGlobal"s"を元に戻してsam buildします。

ルートディレクトリに.aws-samサブフォルダが出来上がり、中にはbuildというフォルダ、その中にはtemplate.yamlがある状態です。

中に移動してこちらでも同じように試してみます。

$ cd .aws-sam/build
$ sam validate
2022-11-05 13:02:42 Loading policies from IAM...
2022-11-05 13:02:49 Finished loading policies from IAM.
/Users/[Your Directory]/sam-app/.aws-sam/build/template.yaml is a valid SAM Template

どちらのテンプレートも以下のように、

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
[以下略]

Transform宣言をしてsamテンプレートの様式に則っていますから、どこのフォルダ内に存在するかという事はこの機能の関心事ではないようです。
(プロジェクトのルートディレクトリの外でも試しましたが動作しました。)

ただしファイル名がtemplate.yaml、またはtemplate.ymlでない場合は以下エラーがおきます。

$ sam validate
SAM Template Not Found
Error: Template at /Users/[Your Directory]/sam-app/template.yml is not found

ちなみに「作業ディレクトリ内にtemplate.yamlとtemplate.ymlが両方存在した場合どうなるか」

ですが、試してみた所、
結果としては、template.yamlで動作して、template.ymlはvalidateされませんでした。

拡張子が.yamlの二文字目のaの方が.ymlのmより先になるので、先にHitして実行され二番目の.ymlファイルまで到達する予定もなくfinishするのだと思います。

その他、デプロイ先のリージョンを指定してvalidateするように指定するオプションなどもあるようです。詳しくはsam validateを確認ください。

sam syncについて

sam sync

AWS SAM CLIの syncコマンドは、ローカルでの変更をAWSクラウドにデプロイします。アプリケーションを反復処理するときに、syncを使用して、開発環境への変更をビルド、パッケージ化、デプロイします。

とあります。早速実行してみます。

実行結果(畳んでいます)
$ sam sync --stack-name sam-app

The SAM CLI will use the AWS Lambda, Amazon API Gateway, and AWS StepFunctions APIs to upload your code without
performing a CloudFormation deployment. This will cause drift in your CloudFormation stack.
**The sync command should only be used against a development stack**.

Confirm that you are synchronizing a development stack.

Enter Y to proceed with the command, or enter N to cancel:
 [Y/n]: Y
	Creating the required resources...
	Successfully created!
Your template contains a resource with logical ID "ServerlessRestApi", which is a reserved logical ID in AWS SAM. It could result in unexpected behaviors and is not recommended.
Manifest file is changed (new hash: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) or dependency folder (.aws-sam/deps/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) is missing for (HelloWorldFunction), downloading dependencies and copying/building source
Building codeuri: /Users/[Your Local Directory]/sam-app/hello_world runtime: python3.9 metadata: {} architecture: x86_64 functions: HelloWorldFunction
Running PythonPipBuilder:CleanUp
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Successfully packaged artifacts and wrote output template to file /var/folders/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/x/xxxxxxxxxxx.
Execute the following command to deploy the packaged template
sam deploy --template-file /var/folders/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/x/xxxxxxxxxxx --stack-name <YOUR STACK NAME>


	Deploying with following values
	===============================
	Stack name                   : sam-app
	Region                       : us-east-1
	Disable rollback             : False
	Deployment s3 bucket         : aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxxxxx
	Capabilities                 : ["CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"]
	Parameter overrides          : {}
	Signing Profiles             : null

Initiating deployment
=====================

2022-11-05 13:41:12 - Waiting for stack create/update to complete

CloudFormation events from stack operations (refresh every 0.5 seconds)
-------------------------------------------------------------------------------------------------
ResourceStatus           ResourceType             LogicalResourceId        ResourceStatusReason
-------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS       AWS::CloudFormation::S   sam-app                  Transformation
                         tack                                              succeeded
CREATE_IN_PROGRESS       AWS::IAM::Role           HelloWorldFunctionRole   -
CREATE_IN_PROGRESS       AWS::CloudFormation::S   AwsSamAutoDependencyLa   -
                         tack                     yerNestedStack
CREATE_IN_PROGRESS       AWS::IAM::Role           HelloWorldFunctionRole   Resource creation
                                                                           Initiated
CREATE_IN_PROGRESS       AWS::CloudFormation::S   AwsSamAutoDependencyLa   Resource creation
                         tack                     yerNestedStack           Initiated
CREATE_COMPLETE          AWS::IAM::Role           HelloWorldFunctionRole   -
CREATE_COMPLETE          AWS::CloudFormation::S   AwsSamAutoDependencyLa   -
                         tack                     yerNestedStack
CREATE_IN_PROGRESS       AWS::Lambda::Function    HelloWorldFunction       -
CREATE_IN_PROGRESS       AWS::Lambda::Function    HelloWorldFunction       Resource creation
                                                                           Initiated
CREATE_COMPLETE          AWS::Lambda::Function    HelloWorldFunction       -
CREATE_IN_PROGRESS       AWS::ApiGateway::RestA   ServerlessRestApi        -
                         pi
CREATE_IN_PROGRESS       AWS::ApiGateway::RestA   ServerlessRestApi        Resource creation
                         pi                                                Initiated
CREATE_COMPLETE          AWS::ApiGateway::RestA   ServerlessRestApi        -
                         pi
CREATE_IN_PROGRESS       AWS::ApiGateway::Deplo   ServerlessRestApiDeplo   -
                         yment                    xxxxxxxxxxxxxxx
CREATE_IN_PROGRESS       AWS::Lambda::Permissio   HelloWorldFunctionHell   -
                         n                        oWorldPermissionProd
CREATE_IN_PROGRESS       AWS::Lambda::Permissio   HelloWorldFunctionHell   Resource creation
                         n                        oWorldPermissionProd     Initiated
CREATE_IN_PROGRESS       AWS::ApiGateway::Deplo   ServerlessRestApiDeplo   Resource creation
                         yment                    xxxxxxxxxxxxxxx          Initiated
CREATE_COMPLETE          AWS::ApiGateway::Deplo   ServerlessRestApiDeplo   -
                         yment                    xxxxxxxxxxxxxxx
CREATE_IN_PROGRESS       AWS::ApiGateway::Stage   ServerlessRestApiProdS   -
                                                  tage
CREATE_IN_PROGRESS       AWS::ApiGateway::Stage   ServerlessRestApiProdS   Resource creation
                                                  tage                     Initiated
CREATE_COMPLETE          AWS::ApiGateway::Stage   ServerlessRestApiProdS   -
                                                  tage
CREATE_COMPLETE          AWS::Lambda::Permissio   HelloWorldFunctionHell   -
                         n                        oWorldPermissionProd
CREATE_COMPLETE          AWS::CloudFormation::S   sam-app                  -
                         tack
-------------------------------------------------------------------------------------------------
CloudFormation outputs from deployed stack
-------------------------------------------------------------------------------------------------
Outputs
-------------------------------------------------------------------------------------------------
Key                 HelloWorldFunctionIamRole
Description         Implicit IAM Role created for Hello World function
Value               arn:aws:iam::[Your Account Id]:role/sam-app-HelloWorldFunctionRole-xxxxxxxxxxxxx

Key                 HelloWorldApi
Description         API Gateway endpoint URL for Prod stage for Hello World function
Value               https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/Prod/hello/

Key                 HelloWorldFunction
Description         Hello World Lambda Function ARN
Value               arn:aws:lambda:us-east-1:[Your Account Id]:function:sam-app-HelloWorldFunction-
xxxxxxxxxxxx
-------------------------------------------------------------------------------------------------

Stack creation succeeded. Sync infra completed.

数箇所だけ振り返ってみます。
まず冒頭の文を翻訳したものです。

SAM CLIは、AWS Lambda、Amazon API Gateway、AWS StepFunctionsの各APIを使用して、CloudFormationのデプロイを行わずにコードをアップロードすることができます。
CloudFormationのデプロイを行わずにコードをアップロードします。これは、CloudFormationスタックにドリフトを発生させます。
**syncコマンドは、開発スタックに対してのみ使用する必要があります**。

開発スタックを同期していることを確認します。

コマンドを続行する場合はYを、キャンセルする場合はNを入力してください。:
 [Y/n]: Y
 
 	必要なリソースを作成する...
	作成に成功しました

この部分でBuildもしてくれている事がわかります。

Manifest file is changed (new hash: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) or dependency folder (.aws-sam/deps/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) is missing for (HelloWorldFunction), downloading dependencies and copying/building source
Building codeuri: /Users/[Your Local Directory]/sam-app/hello_world runtime: python3.9 metadata: {} architecture: x86_64 functions: HelloWorldFunction
Running PythonPipBuilder:CleanUp
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

そのままPakageしてDeployをしてくれています。

Successfully packaged artifacts and wrote output template to file /var/folders/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/x/xxxxxxxxxxx.
Execute the following command to deploy the packaged template
sam deploy --template-file /var/folders/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/x/xxxxxxxxxxx --stack-name <YOUR STACK NAME>
[中略]
Stack creation succeeded. Sync infra completed.

コンソールに結果を見に行きます。

前回、
sam init

sam build

sam deploy --guided
でスタックを作成した時には存在しなかった「sam-app-AwsSamAutoDependencyLayerNestedStack-xxxxxxxxxxxx」スタックが作成されています。

リソースを見ると中身は「AWS::Lambda::LayerVersion」のみである事が確認出来ます。

ローカルのプロジェクトルートディレクトリに戻り.aws-samの中を見ると、
新しく見慣れないフォルダやファイルが生成されています。

sync.tomlの中身(.toml拡張子=設定ファイル)です。

# This file is auto generated by SAM CLI sync command

[sync_state]
dependency_layer = true

ちなみに前回の
sam init

sam build

sam deploy --guided
では、build.toml内の以下部分は""だったのに対して、

manifest_hash = ""

今回は、

Manifest file is changed (new hash: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) [以降省略]

が反映され、

manifest_hash = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

となっている事も確認できました。

manifestについて

私はこのあたりは浅学なのですが、sam build内に、

依存関係は、アプリケーションに含めるマニフェストファイル (Python 関数の場合は requirements.txt、または Node.js 関数の場合は package.json など) で指定します。

という一文を見つけ、現在のステップでも以下2箇所
【1】/Users/[Your Directory]/sam-app/.aws-sam/build/HelloWorldFunction/requirements.txt
【2】/Users/[Your Directory]/sam-app/.aws-sam/auto-dependency-layer/HelloWorldFunction/requirements.txt

に同名ファイルがある事も確認出来ました。

中身はいずれも

requests

の一行のみです。

このrequestsが何かという事ですが、「Python requestsを理解して活用しよう」という記事によると、

requestsを使うとWebデータの操作ができます。 特に使われるのはWebスクレイピングやWebAPIを使ったデータの取得などです。 WebサイトにはHTMLやXMLが使われており、そのようなデータの取り扱いを容易にするライブラリです。

とあります。

そしてsam buildのあるオプションについての説明文に少し長いですが、理解の助けになりそうな箇所がありました。

--cached | --no-cached キャッシュされたビルドを有効または無効にします。このオプションは、以前のビルドから変更されていないビルドアーティファクトを再利用するために使用します。

AWS SAMは、プロジェクトディレクトリ内のファイルが変更されたかどうかを評価します。
デフォルトでは、ビルドはキャッシュされません。

この --no-cached オプションが呼び出されると、samcofig.tomlの cached = true 設定が上書きされます。

注意: AWS SAM は、特定のバージョンが提供されていない場合、プロジェクトが依存するサードパーティーモジュールが変更されたかどうかを評価しません。
例えば、Python 関数に requests=1.x エントリがある requirements.txt ファイルが含まれていて、リクエストモジュールの最新バージョンが 1.1 から 1.2 に変更される場合、AWS SAM はキャッシュされていないビルドが実行されるまで最新バージョンをプルしません。

一旦は、sam syncという機能がこのマニフェストの変更有無により動作を変える、という事だけを持って次の章に進んでみます。

sam sync の「--watch」オプションを試してみる。

--watch | ローカルアプリケーションの変更を監視し、その変更を AWSクラウドに自動的に同期するプロセスを開始します。デフォルトでは、このオプションを指定すると、更新時に、AWS SAMはアプリケーション内のすべてのリソースを同期します。このオプションを指定すると、AWS SAMはAWS CloudFormationの初期デプロイを実行します。次に、AWS SAMはAWSサービスAPIを使用してコードリソースを更新します。AWS SAM テンプレートを更新すると、AWS SAMはAWS CloudFormation を使用してインフラストラクチャリソースを更新します。

要約すると「ローカルに変更が発生したら自動でデプロイして反映するよ」です。

実行してみました。

$ sam sync --stack-name sam-app --watch

[先程と同じ冒頭文/省略]

**The sync command should only be used against a development stack**.

Queued infra sync. Waiting for in progress code syncs to complete...
Starting infra sync.
Your template contains a resource with ...
[中略]

Stack creation succeeded. Sync infra completed.

Infra sync completed.
CodeTrigger not created as CodeUri or DefinitionUri is missing for ServerlessRestApi.

「ServerlessRestApiのCodeUriまたはDefinitionUriがないため、CodeTriggerが作成されない。」とあります。変更検知にかかわる部分ではと思いつつ、

念の為、実際に /Users/[Your Directory]/sam-app/hello_world/app.py
の"hello world"に"2"を追加する変更をして保存してみます。

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world 2",
            # "location": ip.text.replace("\n", "")
        }),
    }

すると、すぐに変更をキャッチしてターミナル上で同期が走り始めました。

Syncing Lambda Function HelloWorldFunction...
Manifest is not changed for (HelloWorldFunction), running incremental build
Building codeuri: /Users/[Your Directory]/sam-app/hello_world runtime: python3.9 metadata: {} architecture: x86_64 functions: HelloWorldFunction
Running PythonPipBuilder:CopySource

「(HelloWorldFunction)のマニフェストが変更されていないので増分ビルドを実行している」と言われています。

言葉の裏を返せば「マニフェストの変更が検知された場合は、全体をビルドする」とも取れます。

"増分"である今回のビルドでは変更は反映されるでしょうか。

出力されていたAPI Gateway の endpoint URLをリロードしてみたところ、

"2"はしっかり反映されてしまいました。

とはいえ、気持ちが悪いので改めて、CodeTriggerについて調べてみます。

CodeTriggerについて

「AWS SAMリソースとプロパティのリファレンス」を見返します。先程のCodeTriggerに関する文章中にあった、

「CodeUri」プロパティが存在するのが、
AWS::Serverless::Function

「DefinitionUri」が存在するのが、
AWS::Serverless::Api
AWS::Serverless::HttpApi
AWS::Serverless::StateMachine

でした。

いずれのプロパティの説明でも、「AmazonS3URI、ローカルファイルパス、(または場所オブジェクト)」を指定すると書いてあります。

今回もtemplate.yaml内に

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/

があり、

Building codeuri: /Users/[Your Directory]/sam-app/hello_world [以下略]

として動作しているような気はします。
ここは割と時間を使ったのですが当該エラー文から納得に至る答えに残念ながら辿り着けていません。(悔しい)

課題を残した状態で恐縮ですが、判明次第追記させていただきたいと思います。

以上でした。

お読みいただきありがとうございました。

Discussion