From 8e2dcf4c152a64d60cfebc65dbb54b2f99f42f1f Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Wed, 8 Jun 2016 15:51:18 +0200 Subject: [PATCH] Add implementation for awsCompileS3Events plugin --- lib/plugins/Plugins.json | 1 + .../awsCompileS3Events/awsCompileS3Events.js | 99 +++++++ .../tests/awsCompileS3Events.js | 253 ++++++++++++++++++ lib/templates/serverless.yaml | 19 +- tests/all.js | 1 + 5 files changed, 365 insertions(+), 8 deletions(-) create mode 100644 lib/plugins/awsCompileS3Events/awsCompileS3Events.js create mode 100644 lib/plugins/awsCompileS3Events/tests/awsCompileS3Events.js diff --git a/lib/plugins/Plugins.json b/lib/plugins/Plugins.json index 45bafdaa9..321679777 100644 --- a/lib/plugins/Plugins.json +++ b/lib/plugins/Plugins.json @@ -5,6 +5,7 @@ "./invoke/invoke.js", "./remove/remove.js", "./awsCompileFunctions/awsCompileFunctions.js", + "./awsCompileS3Events/awsCompileS3Events.js", "./awsDeploy/awsDeploy.js", "./awsInvoke/awsInvoke.js", "./awsRemoveResources/awsRemoveResources.js", diff --git a/lib/plugins/awsCompileS3Events/awsCompileS3Events.js b/lib/plugins/awsCompileS3Events/awsCompileS3Events.js new file mode 100644 index 000000000..be90ec276 --- /dev/null +++ b/lib/plugins/awsCompileS3Events/awsCompileS3Events.js @@ -0,0 +1,99 @@ +'use strict'; + +class AwsCompileS3Events { + constructor(serverless) { + this.serverless = serverless; + + this.hooks = { + 'deploy:compileEvents': this.compileS3Events.bind(this), + }; + } + + compileS3Events(options) { + this.options = options; + + if (!options.stage) { + throw new this.serverless.Error('Please provide a stage'); + } + + if (!options.region) { + throw new this.serverless.Error('Please provide a region'); + } + + this.compiledS3EventResources = []; + + const bucketTemplate = ` + { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": "BucketName", + "NotificationConfiguration": "NotificationConfiguration" + } + } + `; + + const permissionTemplate = ` + { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": "FunctionName", + "Action": "lambda:InvokeFunction", + "Principal": "s3.amazonaws.com" + } + } + `; + + // iterate over all defined functions + this.serverless.service.getAllFunctions().forEach((functionName) => { + const s3BucketObject = this.serverless.service.getFunction(functionName); + + // iterate over all defined buckets + s3BucketObject.events.aws.s3.forEach((bucketName) => { + // create the S3 bucket with the corresponding notification + const newS3Bucket = JSON.parse(bucketTemplate); + newS3Bucket.Properties.BucketName = `${this.serverless.service.service}-${bucketName}-${ + this.options.stage}-${this.options.region}`; + newS3Bucket.Properties.NotificationConfiguration = { + LambdaConfigurations: [ + { + Event: 's3:ObjectCreated:*', + Function: { + 'Fn::GetAtt': [ + functionName, + 'Arn', + ], + }, + }, + ], + }; + + const bucketResourceKey = bucketName.replace(/-/g, ''); + + const newBucketObject = { + [bucketResourceKey]: newS3Bucket, + }; + + this.compiledS3EventResources.push(newBucketObject); + + // create the corresponding Lambda permissions + const newPermission = JSON.parse(permissionTemplate); + newPermission.Properties.FunctionName = { + 'Fn::GetAtt': [ + functionName, + 'Arn', + ], + }; + + const newPermissionObject = { + [`${bucketResourceKey}Permission`]: newPermission, + }; + + this.compiledS3EventResources.push(newPermissionObject); + }); + }); + + this.serverless.service.compiledS3EventResources = this.compiledS3EventResources; + } +} + +module.exports = AwsCompileS3Events; diff --git a/lib/plugins/awsCompileS3Events/tests/awsCompileS3Events.js b/lib/plugins/awsCompileS3Events/tests/awsCompileS3Events.js new file mode 100644 index 000000000..b28d9a5a8 --- /dev/null +++ b/lib/plugins/awsCompileS3Events/tests/awsCompileS3Events.js @@ -0,0 +1,253 @@ +'use strict'; + +const expect = require('chai').expect; +const AwsCompileS3Events = require('../awsCompileS3Events'); +const Serverless = require('../../../Serverless'); + +describe('awsCompileS3Events', () => { + let serverless; + let awsCompileS3Events; + + const functionsObjectMock = { + first: { + events: { + aws: { + s3: [ + 'first-function-bucket1', + 'first-function-bucket2', + ], + }, + }, + }, + second: { + events: { + aws: { + s3: [ + 'second-function-bucket1', + 'second-function-bucket2', + ], + }, + }, + }, + }; + + const compiledS3EventResourcesArrayMock = [ + { + firstfunctionbucket1: { + Type: 'AWS::S3::Bucket', + Properties: { + BucketName: 'new-service-first-function-bucket1-dev-us-east-1', + NotificationConfiguration: { + LambdaConfigurations: [ + { + Event: 's3:ObjectCreated:*', + Function: { + 'Fn::GetAtt': [ + 'first', + 'Arn', + ], + }, + }, + ], + }, + }, + }, + }, + { + firstfunctionbucket2: { + Type: 'AWS::S3::Bucket', + Properties: { + BucketName: 'new-service-first-function-bucket2-dev-us-east-1', + NotificationConfiguration: { + LambdaConfigurations: [ + { + Event: 's3:ObjectCreated:*', + Function: { + 'Fn::GetAtt': [ + 'first', + 'Arn', + ], + }, + }, + ], + }, + }, + }, + }, + { + secondfunctionbucket1: { + Type: 'AWS::S3::Bucket', + Properties: { + BucketName: 'new-service-second-function-bucket1-dev-us-east-1', + NotificationConfiguration: { + LambdaConfigurations: [ + { + Event: 's3:ObjectCreated:*', + Function: { + 'Fn::GetAtt': [ + 'second', + 'Arn', + ], + }, + }, + ], + }, + }, + }, + }, + { + secondfunctionbucket2: { + Type: 'AWS::S3::Bucket', + Properties: { + BucketName: 'new-service-second-function-bucket2-dev-us-east-1', + NotificationConfiguration: { + LambdaConfigurations: [ + { + Event: 's3:ObjectCreated:*', + Function: { + 'Fn::GetAtt': [ + 'second', + 'Arn', + ], + }, + }, + ], + }, + }, + }, + }, + ]; + + const compiledPermissionResourcesArrayMock = [ + { + firstfunctionbucket1Permission: { + Type: 'AWS::Lambda::Permission', + Properties: { + FunctionName: { + 'Fn::GetAtt': [ + 'first', + 'Arn', + ], + }, + Action: 'lambda:InvokeFunction', + Principal: 's3.amazonaws.com', + }, + }, + }, + { + firstfunctionbucket2Permission: { + Type: 'AWS::Lambda::Permission', + Properties: { + FunctionName: { + 'Fn::GetAtt': [ + 'first', + 'Arn', + ], + }, + Action: 'lambda:InvokeFunction', + Principal: 's3.amazonaws.com', + }, + }, + }, + { + secondfunctionbucket1Permission: { + Type: 'AWS::Lambda::Permission', + Properties: { + FunctionName: { + 'Fn::GetAtt': [ + 'second', + 'Arn', + ], + }, + Action: 'lambda:InvokeFunction', + Principal: 's3.amazonaws.com', + }, + }, + }, + { + secondfunctionbucket2Permission: { + Type: 'AWS::Lambda::Permission', + Properties: { + FunctionName: { + 'Fn::GetAtt': [ + 'second', + 'Arn', + ], + }, + Action: 'lambda:InvokeFunction', + Principal: 's3.amazonaws.com', + }, + }, + }, + ]; + + beforeEach(() => { + serverless = new Serverless(); + serverless.init(); + awsCompileS3Events = new AwsCompileS3Events(serverless); + awsCompileS3Events.serverless.service.service = 'new-service'; + awsCompileS3Events.serverless.service.functions = functionsObjectMock; + }); + + describe('#compileS3Events()', () => { + const options = { stage: 'dev', region: 'us-east-1' }; + + it('should throw an error if the stage option is not given', () => { + expect(() => awsCompileS3Events.compileS3Events()).to.throw(Error); + }); + + it('should throw an error if the region option is not given', () => { + expect(() => awsCompileS3Events.compileS3Events()).to.throw(Error); + }); + + it('should create corresponding S3 bucket resources', () => { + awsCompileS3Events.compileS3Events(options); + + expect( + JSON.stringify(awsCompileS3Events.serverless.service.compiledS3EventResources[0]) + ).to.equal( + JSON.stringify(compiledS3EventResourcesArrayMock[0]) + ); + expect( + JSON.stringify(awsCompileS3Events.serverless.service.compiledS3EventResources[2]) + ).to.equal( + JSON.stringify(compiledS3EventResourcesArrayMock[1]) + ); + expect( + JSON.stringify(awsCompileS3Events.serverless.service.compiledS3EventResources[4]) + ).to.equal( + JSON.stringify(compiledS3EventResourcesArrayMock[2]) + ); + expect( + JSON.stringify(awsCompileS3Events.serverless.service.compiledS3EventResources[6]) + ).to.equal( + JSON.stringify(compiledS3EventResourcesArrayMock[3]) + ); + }); + + it('should create corresponding permission resources', () => { + awsCompileS3Events.compileS3Events(options); + + expect( + JSON.stringify(awsCompileS3Events.serverless.service.compiledS3EventResources[1]) + ).to.equal( + JSON.stringify(compiledPermissionResourcesArrayMock[0]) + ); + expect( + JSON.stringify(awsCompileS3Events.serverless.service.compiledS3EventResources[3]) + ).to.equal( + JSON.stringify(compiledPermissionResourcesArrayMock[1]) + ); + expect( + JSON.stringify(awsCompileS3Events.serverless.service.compiledS3EventResources[5]) + ).to.equal( + JSON.stringify(compiledPermissionResourcesArrayMock[2]) + ); + expect( + JSON.stringify(awsCompileS3Events.serverless.service.compiledS3EventResources[7]) + ).to.equal( + JSON.stringify(compiledPermissionResourcesArrayMock[3]) + ); + }); + }); +}); diff --git a/lib/templates/serverless.yaml b/lib/templates/serverless.yaml index 89d937c51..4cac78864 100644 --- a/lib/templates/serverless.yaml +++ b/lib/templates/serverless.yaml @@ -27,13 +27,16 @@ functions: # if this gets too big, you can always use JSON-REF provider: <<: *default_providers events: - - aws_s3: first_bucket - - aws_http_endpoint: - post: users/create - - aws_scheduled: 5 * * * * - - azure_http_endpoint: - direction: in - name: req + aws: + s3: + - first-bucket + http_endpoint: + post: users/create + scheduled: 5 * * * * + azure: + http_endpoint: + direction: in + name: req resources: aws_name_template: ${stage}-${service}-${name} # "name" references the resource name, service the whole service name @@ -45,4 +48,4 @@ resources: azure_functions: $ref: ../azure_resources.json # you can use JSON-REF to ref other JSON files google: - $ref: ../google_resources.yaml # you can use JSON-REF to ref other YAML files \ No newline at end of file + $ref: ../google_resources.yaml # you can use JSON-REF to ref other YAML files diff --git a/tests/all.js b/tests/all.js index 83470463d..a41624b7c 100644 --- a/tests/all.js +++ b/tests/all.js @@ -21,3 +21,4 @@ require('../lib/plugins/awsDeploy/tests/all'); require('../lib/plugins/awsRemoveResources/tests/all'); require('../lib/plugins/awsInvoke/tests/awsInvoke'); require('../lib/plugins/awsCompileFunctions/tests/awsCompileFunctions'); +require('../lib/plugins/awsCompileS3Events/awsCompileS3Events');