diff --git a/lib/plugins/Plugins.json b/lib/plugins/Plugins.json index d33d90ca3..49f56bd49 100644 --- a/lib/plugins/Plugins.json +++ b/lib/plugins/Plugins.json @@ -48,6 +48,7 @@ "./aws/package/compile/events/cognitoUserPool/index.js", "./aws/package/compile/events/eventBridge/index.js", "./aws/package/compile/events/sqs/index.js", + "./aws/package/compile/events/cloudFront/index.js", "./aws/deployFunction/index.js", "./aws/deployList/index.js", "./aws/invokeLocal/index.js" diff --git a/lib/plugins/aws/package/compile/events/cloudFront/index.js b/lib/plugins/aws/package/compile/events/cloudFront/index.js new file mode 100644 index 000000000..cae99cdd5 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/cloudFront/index.js @@ -0,0 +1,134 @@ +'use strict'; + +const _ = require('lodash'); + +class AwsCompileCloudFrontEvents { + constructor(serverless) { + this.serverless = serverless; + this.provider = this.serverless.getProvider('aws'); + this.region = + this.serverless.processedInput.options.region || this.serverless.service.provider.region; + + this.hooks = { + 'package:compileEvents': this.compileCloudFrontEvents.bind(this), + }; + } + + compileCloudFrontEvents() { + const lambdaAtEdgeFunctions = []; + const { Resources, Outputs } = this.serverless.service.provider.compiledCloudFormationTemplate; + + this.serverless.service.getAllFunctions().forEach((functionName) => { + const functionObj = this.serverless.service.getFunction(functionName); + if (functionObj.events) { + functionObj.events.forEach(event => { + if (event.cloudFront) { + // console.log(event.cloudFront); + const lambdaVersionLogicalId = _.findKey(Resources, { + Type: 'AWS::Lambda::Version', + Properties: { + FunctionName: { + Ref: this.provider.naming.getLambdaLogicalId(functionName), + }, + }, + }); + + const pathPattern = + typeof event.cloudFront.pathPattern === 'string' + ? event.cloudFront.pathPattern + : undefined; + + const behavior = { + ViewerProtocolPolicy: 'allow-all', + TargetOriginId: this.provider.naming.getNormalizedFunctionName(functionName), + ForwardedValues: { + QueryString: false, + }, + LambdaFunctionAssociations: [{ + EventType: event.cloudFront.eventType, + LambdaFunctionARN: { + Ref: lambdaVersionLogicalId, + }, + }], + }; + + if (typeof pathPattern !== undefined) { + _.merge(behavior, { PathPattern: pathPattern }); + } + + const domainName = + typeof event.cloudFront.origin === 'string' + ? event.cloudFront.origin + : event.cloudFront.origin.domainName; + + const origin = { + Id: this.provider.naming.getNormalizedFunctionName(functionName), + DomainName: domainName, + CustomOriginConfig: { + OriginProtocolPolicy: 'match-viewer', + }, + }; + + lambdaAtEdgeFunctions.push(_.merge({ + cloudFront: { origin, behavior } }, + functionObj)); + } + }); + } + }); + + if (lambdaAtEdgeFunctions.length > 0) { + if (this.region !== 'us-east-1') { + throw new Error('CloudFront triggered functions has to be deployed to us-east-1 region.'); + } + + Resources + .IamRoleLambdaExecution + .Properties + .AssumeRolePolicyDocument + .Statement.push({ + Effect: 'Allow', + Principal: { + Service: ['edgelambda.amazonaws.com'], + }, + Action: ['sts:AssumeRole'], + }); + + const CacheBehaviors = + lambdaAtEdgeFunctions + .filter(({ cloudFront }) => !!cloudFront.behavior.PathPattern) + .map(({ cloudFront }) => cloudFront.behavior); + + const CloudFrontDistribution = { + Type: 'AWS::CloudFront::Distribution', + Properties: { + DistributionConfig: { + Enabled: true, + DefaultCacheBehavior: + lambdaAtEdgeFunctions + .filter(({ cloudFront }) => !cloudFront.behavior.PathPattern) + .map(({ cloudFront }) => cloudFront.behavior)[0], + Origins: lambdaAtEdgeFunctions.map(({ cloudFront }) => cloudFront.origin), + }, + }, + }; + + if (typeof CacheBehaviors !== 'undefined') { + _.merge(CloudFrontDistribution.Properties.DistributionConfig, { CacheBehaviors }); + } + + _.merge(Resources, { CloudFrontDistribution }); + + _.merge(Outputs, { + CloudFrontDistributionDomainName: { + Description: 'CloudFront distribution domain name', + Value: { + 'Fn::GetAtt': ['CloudFrontDistribution', 'DomainName'], + }, + }, + }); + } + } +} + +module.exports = AwsCompileCloudFrontEvents; diff --git a/lib/plugins/aws/package/compile/events/cloudFront/index.test.js b/lib/plugins/aws/package/compile/events/cloudFront/index.test.js new file mode 100644 index 000000000..e69de29bb