Help us understand the problem. What is going on with this article?

aws-sam-local 改め aws-sam-cli の新機能 sam init を試す

2020/7/23 追記

こちらの記事の内容はかなり古くなってしまったので、以下を参照いただければと思います。

AWS SAM CLI 再入門 2020.07
https://qiita.com/hayao_k/items/7827c3778a23c514e196

はじめに

aws-sam-local が バージョンアップされ、aws-sam-cli となりました。
https://aws.amazon.com/about-aws/whats-new/2018/04/aws-sam-cli-releases-new-init-command/

また、新しいコマンド sam init が追加され、
より簡単にサンプルアプリケーションが作れるようになりました。
SAM Localの復習もかねてサンプルアプリケーションの作成からデプロイまで行ってみました。

※2018/9/3
version 0.6.0 のgenerate-events コマンドの構文変更について追記しました。
※2018/11/8
version 0.6.2 で追加された Homebrew/MSIによるインストールについて追記しました。

インストール

後述するinvokeやstart-apiコマンドを利用してローカルでテストする場合は
前提としてDocker のインストールが必要ですが、手順は割愛します。
SAM CLI の古いバージョンがインストールされている場合はアンインストールしておきます。

$ sam --version
A newer version of the AWS SAM CLI is available!
Your version:   0.2.11
Latest version: 0.3.0
See https://github.com/awslabs/aws-sam-local for upgrade instructions

$ npm uninstall -g aws-sam-local

新しい SAM CLI は python で実装されているため、pip でインストールします。
バージョンが 0.3.0 以上であることを確認します。

$ pip install --user aws-sam-cli

$ sam --version
SAM CLI, version 0.3.0

できました!

※2018/11/8時点の最新版は 0.6.2 です。
このバージョンからHomebrewおよびMSIによるインストールが可能になっています。

masOS

$ brew tap aws/tap
$ brew install aws-sam-cli

Windows

※ versionおよびビット数により、URLが変わります。

https://github.com/awslabs/aws-sam-cli/releases/download/v0.6.2/AWS_SAM_CLI_64_PY3.msi

sam init を試す

サンプルアプリケーションの作成

sam init コマンドで Node.js v8.10 のサンプルアプリケーションを作成してみます。

$ sam init --runtime nodejs
[+] Initializing project structure...
[SUCCESS] - Read sam-app/README.md for further instructions on how to proceed
[*] Project initialization is now complete

$ cd sam-app/
$ ls -l
total 16
drwxrwxr-x 3 ec2-user ec2-user 4096 May  9 16:48 hello_world
-rw-rw-r-- 1 ec2-user ec2-user 4625 May  9 16:48 README.md
-rw-rw-r-- 1 ec2-user ec2-user 1684 May  9 16:48 template.yaml

テストコードを含むサンプルコード と SAM テンプレートが作成されていることがわかります。

hello_wolrd/app.js
const axios = require('axios')
const url = 'http://checkip.amazonaws.com/';
let response;


exports.lambda_handler = async (event, context, callback) => {
    try {
        const ret = await axios(url);
        response = {
            'statusCode': 200,
            'body': JSON.stringify({
                message: 'hello world',
                location: ret.data.trim()
            })
        }
    }
    catch (err) {
        console.log(err);
        callback(err, null);
    }

    callback(null, response)
};

hello-world/tests/unit/test_handler.js
'use strict';

const app = require('../../app.js');
const chai = require('chai');
const expect = chai.expect;
var event, context;


describe('Tests index', function () {
    it('verifies successful response', async () => {
        const result = await app.lambda_handler(event, context, (err, result) => {
            expect(result).to.be.an('object');
            expect(result.statusCode).to.equal(200);
            expect(result.body).to.be.an('string');

            let response = JSON.parse(result.body);

            expect(response).to.be.an('object');
            expect(response.message).to.be.equal("hello world");
            expect(response.location).to.be.an("string");
        });
    });
});
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
    sam-app

    Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
    Function:
        Timeout: 3


Resources:

    HelloWorldFunction:
        Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
        Properties:
            CodeUri: hello_world/
            Handler: app.lambda_handler
            Runtime: nodejs8.10
            Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
                Variables:
                    PARAM1: VALUE
            Events:
                HelloWorld:
                    Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
                    Properties:
                        Path: /hello
                        Method: get

Outputs:

    HelloWorldApi:
      Description: "API Gateway endpoint URL for Prod stage for Hello World function"
      Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"

    HelloWorldFunction:
      Description: "Hello World Lambda Function ARN"
      Value: !GetAtt HelloWorldFunction.Arn

    HelloWorldFunctionIamRole:
      Description: "Implicit IAM Role created for Hello World function"
      Value: !GetAtt HelloWorldFunctionRole.Arn

ローカルでテストする

まずはユニットテストを実行します。

$ cd hello_wolrd
$ npm install
npm notice created a lockfile as package-lock.json. You should commit this file.
added 34 packages in 1.611s

$ npm test

> hello_world@1.0.0 test /home/ec2-user/sam-app/hello_world
> mocha tests/unit/



  Tests index
    ? verifies successful response (445ms)


  1 passing (453ms)

成功しました。

generate-event コマンドで 指定したサービスからLambdaに送られる疑似イベントを作成することができます。
複数サービスのペイロードを作成可能ですが、ここではAPI Gatewayのイベントを作成します。
※ verison 0.6.0 から対応サービスが増え、さらにサービスごとに複数のイベントタイプをサポートするようになり、コマンドの構文が変わっています。

$ cd ../
$ sam local generate-event apigateway aws-proxy > event_file.json
# version 0.5.0 以前
$ sam local generate-event api > event_file.json

次にローカルで Lambda を Invoke してみます。
テンプレートファイルを読み取るのでtemplate.yaml があるディレクトリで実行します。
ローカルにdocker lambda のイメージがない場合は最初にダウンロードされるので少し時間がかかります。

$ sam local invoke HelloWorldFunction --event event_file.json
2018-05-09 17:16:26 Invoking app.lambda_handler (nodejs8.10)
2018-05-09 17:16:26 Starting new HTTP connection (1): 169.254.169.254
2018-05-09 17:16:26 Starting new HTTP connection (1): 169.254.169.254

Fetching lambci/lambda:nodejs8.10 Docker container image...................................................................................................
2018-05-09 17:16:59 Mounting /home/ec2-user/sam-app/hello_world as /var/task:ro inside runtime container
START RequestId: c6c1315d-7391-1717-5ab6-6130f1c4461b Version: $LATEST
END RequestId: c6c1315d-7391-1717-5ab6-6130f1c4461b
REPORT RequestId: c6c1315d-7391-1717-5ab6-6130f1c4461b  Duration: 372.69 ms     Billed Duration: 400 ms Memory Size: 128 MB     Max Memory Used: 35 MB

{"statusCode":200,"body":"{\"message\":\"hello world\",\"location\":\"xxx.xxx.xxx.xxx\"}"}

続いてローカルAPI Gateway を起動します。

$ sam local start-api
2018-05-09 17:19:44 Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
2018-05-09 17:19:44 You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2018-05-09 17:19:44  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

表示されたエンドポイントに curl すると、メッセージとIPアドレスが返ってきます。

$ curl http://127.0.0.1:3000/hello
{"message":"hello world","location":"xxx.xxx.xxx.xxx"}

ローカル API Gateway でも HTTP200 が返っていることを確認できます。

2018-05-09 17:24:21 Invoking app.lambda_handler (nodejs8.10)
2018-05-09 17:24:21 Starting new HTTP connection (1): 169.254.169.254

Fetching lambci/lambda:nodejs8.10 Docker container 
2018-05-09 17:24:57 Mounting /home/ec2-user/sam-app/hello_world as /var/task:ro inside runtime container
START RequestId: 36fdf7c7-5885-1873-ce3f-ff9c4c04f0d7 Version: $LATEST
END RequestId: 36fdf7c7-5885-1873-ce3f-ff9c4c04f0d7
REPORT RequestId: 36fdf7c7-5885-1873-ce3f-ff9c4c04f0d7  Duration: 478.00 ms     Billed Duration: 500 ms Memory Size: 128 MB     Max Memory Used: 35 MB
2018-05-09 17:24:58 No Content-Type given. Defaulting to 'application/json'.
2018-05-09 17:24:58 127.0.0.1 - - [09/May/2018 17:24:58] "GET /hello HTTP/1.1" 200 -

デプロイ

ローカル上で動作確認できたので、実際にCloudFormationでデプロイしてみます。
validate コマンドで テンプレートがSAMの仕様に沿っているか検証できます。

$ sam validate
2018-05-09 17:45:35 Starting new HTTP connection (1): 169.254.169.254
2018-05-09 17:45:35 Starting new HTTP connection (1): 169.254.169.254
2018-05-09 17:45:35 Starting new HTTPS connection (1): iam.amazonaws.com
/home/ec2-user/sam-app/template.yaml is a valid SAM Template

$ sam package --template-file template.yaml --s3-bucket <bucketname> --output-template-file packaged.yaml

Successfully packaged artifacts and wrote output template to file packaged.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /home/ec2-user/sam-app/packaged.yaml --stack-name <YOUR STACK NAME>

$ sam deploy --template-file packaged.yaml --stack-name mystack --capabilities CAPABILITY_IAM
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - mystack

デプロイが成功したら、Stack の Outputs に API Gateway の URL が出力されているため確認します。

$ aws cloudformation describe-stacks --stack-name mystack --query 'Stacks[].Outputs[1]'
[
    {
        "Description": "API Gateway endpoint URL for Prod stage for Hello World function",
        "OutputKey": "HelloWorldApi",
        "OutputValue": "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/"
    }
]

動作確認は省略します。

IAMポリシー周りの補足(7/19追記)

sam packge や sam deploy では 実体としてはCloudFormationが動いていますので
CloudFormation関係の権限が必要です。
あとは実際に作られるリソースの作成権限などが必要ですので、
今回のサンプルアプリケーション(Lambda/API Gateway/IAMロール)の場合、
一例ですが、こんな感じでしょうか。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:getObject",
                "s3:PutObject",
                "cloudformation:Describe*",
                "cloudformation:EstimateTemplateCost",
                "cloudformation:Get*",
                "cloudformation:List*",
                "cloudformation:ValidateTemplate",
                "cloudformation:CreateChangeSet",
                "cloudformation:ExecuteChangeSet",
                "iam:CreateRole",
                "iam:DeleteRole",
                "iam:AttachRolePolicy",
                "iam:DetachRolePolicy",
                "iam:GetRole",
                "iam:PassRole",
                "lambda:CreateFunction",
                "lambda:DeleteFunction",
                "lambda:GetFunctionConfiguration",
                "lambda:AddPermission",
                "lambda:RemovePermission",
                "apigateway:POST",
                "apigateway:PATCH",
                "apigateway:DELETE",
                "apigateway:GET"
            ],
            "Resource": "*"
        }
    ]
}

リソースの作成権限を直接持たせたくない場合は、サービスロールに切り出して
deploy 実行時に --role-arn を指定するのも良いかと思います。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-iam-servicerole.html

sam deploy --template-file packaged.yaml --stack-name mystack --capabilities CAPABILITY_IAM --role-arn arn:aws:iam::0123456789012:role/rolename

以上です。
参考になれば幸いです。

hayao_k
2019 & 2020 APN AWS Top Engineers / AWS Community Builder に選出いただきました。 掲載内容は個人の見解であり、所属する企業を代表するものではありません。
saison_information_systems
モード1(守りのIT)・モード2(攻めのIT)を兼ね備えたバイモーダル・インテグレーターとしてデータ連携プラットフォームのHULFTシリーズ, リンケージサービス, 流通ITサービス, フィナンシャルITサービスを提供します。
https://home.saison.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away