How can we use serverless.yml to create an AWS S3 bucket and add a file to it?

25,768

Solution 1

There is no official AWS CloudFormation resource that will manage (add/delete) an individual S3 Object within a Bucket, but you can create one with a Custom Resource that uses a Lambda function to call the PUT Object/DELETE Object APIs using the AWS SDK for NodeJS.

Here's a complete example CloudFormation template:

Launch Stack

Description: Create an S3 Object using a Custom Resource.
Parameters:
  BucketName:
    Description: S3 Bucket Name (must not already exist)
    Type: String
  Key:
    Description: S3 Object Key
    Type: String
  Body:
    Description: S3 Object Body
    Type: String
Resources:
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref BucketName
  S3Object:
    Type: Custom::S3Object
    Properties:
      ServiceToken: !GetAtt S3ObjectFunction.Arn
      Bucket: !Ref Bucket
      Key: !Ref Key
      Body: !Ref Body
  S3ObjectFunction:
    Type: AWS::Lambda::Function
    Properties:
      Description: S3 Object Custom Resource
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          var response = require('cfn-response');
          var AWS = require('aws-sdk');
          var s3 = new AWS.S3();
          exports.handler = function(event, context) {
            var respond = (e) => response.send(event, context, e ? response.FAILED : response.SUCCESS, e ? e : {});
            var params = event.ResourceProperties;
            delete params.ServiceToken;
            if (event.RequestType == 'Create' || event.RequestType == 'Update') {
              s3.putObject(params).promise()
                .then((data)=>respond())
                .catch((e)=>respond(e));
            } else if (event.RequestType == 'Delete') {
              delete params.Body;
              s3.deleteObject(params).promise()
                .then((data)=>respond())
                .catch((e)=>respond(e));
            } else {
              respond({Error: 'Invalid request type'});
            }
          };
      Timeout: 30
      Runtime: nodejs4.3
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal: {Service: [lambda.amazonaws.com]}
          Action: ['sts:AssumeRole']
      Path: /
      ManagedPolicyArns:
      - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      Policies:
      - PolicyName: S3Policy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - 's3:PutObject'
                - 'S3:DeleteObject'
              Resource: !Sub "arn:aws:s3:::${BucketName}/${Key}"

You should be able to also use these resources within a serverless.yml configuration file, though I'm not positive about how exactly Serverless integrates with CloudFormation resources/parameters.

Solution 2

Have you looked into the serverless-s3-sync plugin. It allows you to upload a folder to S3 from your local machine as part of the deployment.

plugins:
  - serverless-s3-sync
custom:
  s3Sync:
    - bucketName: ${self:custom.s3.bucket} # required
      bucketPrefix: assets/ # optional
      localDir: dist/assets # required

Solution 3

If you're doing this to deploy a website, you can use serverless-finch and it will create the bucket for you automatically if it doesn't already exist.

Share:
25,768
Pedro Baptista Afonso
Author by

Pedro Baptista Afonso

Updated on December 07, 2021

Comments

  • Pedro Baptista Afonso
    Pedro Baptista Afonso over 2 years

    I'm wondering if it's possible to leverage serverless.yml to create a bucket and add a specific file to it during the deploy process of serverless-framework.

    So far, I've been able to add the S3 resource that creates the bucket, but not sure how to add a specific file.

    resources:
      Resources:
        UploadBucket:
          Type: AWS::S3::Bucket
          Properties:
            BucketName: ${self:custom.s3.bucket}
            AccessControl: Private
            CorsConfiguration:
              CorsRules:
              - AllowedMethods:
                - GET
                - PUT
                - POST
                - HEAD
                AllowedOrigins:
                - "*"
                AllowedHeaders:
                - "*"
    

    Not sure if it's possible, or how to leverage the serverless.yml to upload a default file during the deploy process if it's not there yet.