AWS Lambda function から S3 へアップロードする [Serverless Framework 編]

Serverless Framework で AWS Lambda へ deploy した function から S3 へアップロードする方法をご紹介します。

Serverless Framework

前提

本サイトの記事 Serverless Framework で AWS Lambda へ deploy する | CodeNote を元にセットアップしているとします。

初期状態だと InvalidAccessKeyId エラー

Serverless Framework のデフォルト設定のままだと Lambda function から S3 を使おうとすると InvalidAccessKeyId: The AWS Access Key Id you provided does not exist in our records. というエラーが発生してしまいます。

{
  InvalidAccessKeyId: The AWS Access Key Id you provided does not exist in our records.
    at Request.extractError (/var/task/node_modules/aws-sdk/lib/services/s3.js:585:35)
    at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
    at Request.emit (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
    at Request.emit (/var/task/node_modules/aws-sdk/lib/request.js:683:14)
    at Request.transition (/var/task/node_modules/aws-sdk/lib/request.js:22:10)
    at AcceptorStateMachine.runTo (/var/task/node_modules/aws-sdk/lib/state_machine.js:14:12)
    at /var/task/node_modules/aws-sdk/lib/state_machine.js:26:10
    at Request.<anonymous> (/var/task/node_modules/aws-sdk/lib/request.js:38:9)
    at Request.<anonymous> (/var/task/node_modules/aws-sdk/lib/request.js:685:12)
    at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:116:18)
  message: 'The AWS Access Key Id you provided does not exist in our records.',
  code: 'InvalidAccessKeyId',
  region: null,
  time: 2018-12-22T15:40:48.422Z,
  requestId: 'A7BA76D679AA0908',
  extendedRequestId: 'BgpkYpdh88xEFW1IOdb6E1hKS484X3ZVcsymi0TL2S/x5nTF0CNsZQ18MkIE5yYn/6WjJz2KaSc=',
  cfId: undefined,
  statusCode: 403,
  retryable: false,
  retryDelay: 41.81325093666113
}

AWS Lambda から S3 を利用する手順

私が開発してる Set up AWS S3 / Crawling the target site and save as HTML to S3 · Pull Request #4 に沿って、手順をご紹介します。

npm install aws-sdk

npm install --save aws-sdk

S3 bucket を環境変数へ定義

serverless.yml の provider: に environment: を以下のように定義することで、serverless.yml からは ${self:provider.environment.S3_BUCKET} というように環境変数を利用できます。

provider:
# you can define service wide environment variables here
  environment:
    S3_BUCKET: your-bucket-name-${opt:stage, self:provider.stage}

S3 bucket 作成

S3 Management Console から S3 bucket を作成するのでもいいですが、せっかく Serverless Framework を利用してるなら serverless.yml に以下のような設定を追加して CloudFormation 経由で S3 bucket を作成するのがおすすめです。

# you can add CloudFormation resource templates here
resources:
  Resources:
    S3BucketResource:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:provider.environment.S3_BUCKET}
  Outputs:
     S3BucketOutput:
       Description: "Description for the output"
       Value: S3BucketResource

S3 bucket への IAM 権限付与

上記までだと S3 bucket 作成しただけで、Lambda function から作成した S3 bucket への権限がなにもありません。

serverless.yml の provider: iamRoleStatements: を以下のように修正すると、Lambda function から S3 へアクセス

provider:
# you can add statements to the Lambda function's IAM Role here
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:ListBucket"
        - "s3:GetObject"
        - "s3:PutObject"
        - "s3:DeleteObject"
      Resource:
        Fn::Join:
          - ""
          - - "arn:aws:s3:::"
            - ${self:provider.environment.S3_BUCKET}
            - "/*"

Lambda から S3 へ upload

最後に、上記で作成、IAM 権限付与した S3 bucket へ Lambda function から HTML ファイルをアップロードするサンプルコードをご紹介します。

'use strict';
const AWS = require('aws-sdk');
 
module.exports.putS3 = async (event, context) => {
  try {
    const html = '<h1>Hello world</h1>';
 
    AWS.config.update({
      accessKeyId: process.env.AWS_ACCESS_KEY_ID,
      secretAccessKey: process.env.AWS_SEECRET_ACCESS_KEY,
      region: process.env.AWS_REGION
    });
    const s3 = new AWS.S3();
    const response = await s3.putObject({
      Bucket: process.env.S3_BUCKET,
      Key: `${Date.now()}.html`,
      ContentType: 'text/html',
      Body: html
    }).promise();
 
    console.log({
      result: 'OK',
      s3response: response
    });
  } catch (err) {
    console.error(err);
    console.log({
      result: 'NG'
    });
  }
};

以上、Serverless Framework で deploy した AWS Lambda function から S3 を操作したい、現場からお送りしました。

参考情報