Repository: serverless/serverless (JavaScript)
Date: 2019-11-04 18:01:47+00:00
Task type: Bug Fix

Issue:
Unable to use cloudfront origin-response eventType if memorySize or timeout exceeds 128 and 5
# Bug Report

## Description
### What did you do?
In setting up an image resize lambda function in the origin-request of a cloudformation distribution with AWS.Rekognition and sharp. This function consumes more than 128Mb and takes more than 5 seconds on every case. 
My `serverless.yml` file looks something like this:
```yml
...
functions:
  originResponseFunction:
    handler: originResponseFunction/index.handler
    timeout: 30
    memorySize: 896
    events:
      - cloudFront:
          eventType: origin-response
          origin: ${self:custom.origins.${self:provider.stage}}
...
```

### What happened?
When I run the command `sls deploy` I get the follwing error message:
```
  Error --------------------------------------------------
 
  Error: "originResponseFunction" memorySize is greater than 128 which is not supported by Lambda@Edge functions
      at serverless.service.getAllFunctions.forEach.functionName (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/lib/plugins/aws/package/compile/events/cloudFront/index.js:46:17)
      at Array.forEach (<anonymous>)
      at AwsCompileCloudFrontEvents.validate (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/lib/plugins/aws/package/compile/events/cloudFront/index.js:42:47)
      at BbPromise.reduce (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/lib/classes/PluginManager.js:505:55)
      at tryCatcher (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/node_modules/bluebird/js/release/util.js:16:23)
      at Object.gotValue (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/node_modules/bluebird/js/release/reduce.js:168:18)
      at Object.gotAccum (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/node_modules/bluebird/js/release/reduce.js:155:25)
      at Object.tryCatcher (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/node_modules/bluebird/js/release/util.js:16:23)
      at Promise._settlePromiseFromHandler (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:547:31)
      at Promise._settlePromise (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:604:18)
      at Promise._settlePromise0 (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:649:10)
      at Promise._settlePromises (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:729:18)
      at _drainQueueStep (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:93:12)
      at _drainQueue (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:86:9)
      at Async._drainQueues (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:102:5)
      at Immediate.Async.drainQueues [as _onImmediate] (/home/winstonwp/.nvm/versions/node/v10.16.0/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:15:14)
      at runCallback (timers.js:705:18)
      at tryOnImmediate (timers.js:676:5)
      at processImmediate (timers.js:658:5)
      at process.topLevelDomainCallback (domain.js:126:23)
```

### What should've happened?
For the time being I'm setting the lambda function manually in my AWS CloudFront Distribution, and I'm able to configure the `originResponseFunction` with a memorySize of `896Mb` and `30s` timeout.
Running `sls deploy` should allow me to deploy an `eventType: origin-response` lambda function with greater `memorySize` and `timeout` values.

I've identified a possible solution for this issue and will be happy to submit a Pull Request, but I'm not certain of what approach should I use, a `disableValidation` flag or an exception for `eventType: origin-response` when running the  [validate](https://github.com/serverless/serverless/blob/dc7413cee4392946253b153e6bf4d32d074bd8b5/lib/plugins/aws/package/compile/events/cloudFront/index.js#L41) function.


Issue discussion:
I followed [this example from AWS blog](https://aws.amazon.com/blogs/networking-and-content-delivery/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/) to write the image resize function.

Solution patch:
diff --git a/docs/providers/aws/events/cloudfront.md b/docs/providers/aws/events/cloudfront.md
index 2b9137f7daa..5c397a16200 100644
--- a/docs/providers/aws/events/cloudfront.md
+++ b/docs/providers/aws/events/cloudfront.md
@@ -33,6 +33,8 @@ Lambda@Edge has four options when the Lambda function is triggered
 
 **IMPORTANT:** Due to current [Lambda@Edge limitations](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-delete-replicas.html) it's necessary to set the `DeletionPolicy` to `Retain` for AWS Lambda functions which use the `cloudFront` event. The Serverless Framework will do this automatically for you. However bear in mind that **you have to delete those AWS Lambda functions manually** once you've removed the service via `serverless remove`.
 
+**MEMORY AND TIMEOUT LIMITS:** According to [AWS Limits on Lambda@Edge](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-lambda-at-edge) the limits for viewer-request and viewer-response are 128MB memory and 5 seconds timeout and for origin-request and origin-response are 3008MB memory and 30 seconds timeout.
+
 ## Simple event definition
 
 This will enable your Lambda@Edge function to be called by a CloudFront.
diff --git a/lib/plugins/aws/package/compile/events/cloudFront/index.js b/lib/plugins/aws/package/compile/events/cloudFront/index.js
index 2700863f2d0..856dbd59781 100644
--- a/lib/plugins/aws/package/compile/events/cloudFront/index.js
+++ b/lib/plugins/aws/package/compile/events/cloudFront/index.js
@@ -5,11 +5,21 @@ const url = require('url');
 const chalk = require('chalk');
 const { ServerlessError } = require('../../../../../../classes/Error');
 
+const originLimits = { maxTimeout: 30, maxMemorySize: 3008 };
+const viewerLimits = { maxTimeout: 5, maxMemorySize: 128 };
+
 class AwsCompileCloudFrontEvents {
   constructor(serverless, options) {
     this.serverless = serverless;
     this.options = options;
     this.provider = this.serverless.getProvider('aws');
+    this.lambdaEdgeLimits = {
+      'origin-request': originLimits,
+      'origin-response': originLimits,
+      'viewer-request': viewerLimits,
+      'viewer-response': viewerLimits,
+      'default': viewerLimits,
+    };
     this.hooks = {
       'package:initialize': this.validate.bind(this),
       'before:package:compileFunctions': this.prepareFunctions.bind(this),
@@ -41,18 +51,21 @@ class AwsCompileCloudFrontEvents {
   validate() {
     this.serverless.service.getAllFunctions().forEach(functionName => {
       const functionObj = this.serverless.service.getFunction(functionName);
-      if (functionObj.events.find(event => event.cloudFront)) {
-        if (functionObj.memorySize && functionObj.memorySize > 128) {
+      functionObj.events.forEach(({ cloudFront }) => {
+        if (!cloudFront) return;
+        const { eventType = 'default' } = cloudFront;
+        const { maxMemorySize, maxTimeout } = this.lambdaEdgeLimits[eventType];
+        if (functionObj.memorySize && functionObj.memorySize > maxMemorySize) {
           throw new Error(
-            `"${functionName}" memorySize is greater than 128 which is not supported by Lambda@Edge functions`
+            `"${functionName}" memorySize is greater than ${maxMemorySize} which is not supported by Lambda@Edge functions of type "${eventType}"`
           );
         }
-        if (functionObj.timeout && functionObj.timeout > 5) {
+        if (functionObj.timeout && functionObj.timeout > maxTimeout) {
           throw new Error(
-            `"${functionName}" timeout is greater than 5 which is not supported by Lambda@Edge functions`
+            `"${functionName}" timeout is greater than ${maxTimeout} which is not supported by Lambda@Edge functions of type "${eventType}"`
           );
         }
-      }
+      });
     });
   }
 


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/cloudFront/index.test.js b/lib/plugins/aws/package/compile/events/cloudFront/index.test.js
index 61bd3b25931..12922ab7b6f 100644
--- a/lib/plugins/aws/package/compile/events/cloudFront/index.test.js
+++ b/lib/plugins/aws/package/compile/events/cloudFront/index.test.js
@@ -126,7 +126,7 @@ describe('AwsCompileCloudFrontEvents', () => {
   });
 
   describe('#validate()', () => {
-    it('should throw if memorySize is greater than 128', () => {
+    it('should throw if memorySize is greater than 128 for viewer-request or viewer-response functions', () => {
       awsCompileCloudFrontEvents.serverless.service.functions = {
         first: {
           memorySize: 129,
@@ -144,9 +144,27 @@ describe('AwsCompileCloudFrontEvents', () => {
       expect(() => {
         awsCompileCloudFrontEvents.validate();
       }).to.throw(Error, 'greater than 128');
+
+      awsCompileCloudFrontEvents.serverless.service.functions = {
+        first: {
+          memorySize: 129,
+          events: [
+            {
+              cloudFront: {
+                eventType: 'viewer-response',
+                origin: 's3://bucketname.s3.amazonaws.com/files',
+              },
+            },
+          ],
+        },
+      };
+
+      expect(() => {
+        awsCompileCloudFrontEvents.validate();
+      }).to.throw(Error, 'greater than 128');
     });
 
-    it('should throw if timeout is greater than 5', () => {
+    it('should throw if timeout is greater than 5 for viewer-request or viewer response functions', () => {
       awsCompileCloudFrontEvents.serverless.service.functions = {
         first: {
           timeout: 6,
@@ -164,6 +182,98 @@ describe('AwsCompileCloudFrontEvents', () => {
       expect(() => {
         awsCompileCloudFrontEvents.validate();
       }).to.throw(Error, 'greater than 5');
+      awsCompileCloudFrontEvents.serverless.service.functions = {
+        first: {
+          timeout: 6,
+          events: [
+            {
+              cloudFront: {
+                eventType: 'viewer-response',
+                origin: 's3://bucketname.s3.amazonaws.com/files',
+              },
+            },
+          ],
+        },
+      };
+
+      expect(() => {
+        awsCompileCloudFrontEvents.validate();
+      }).to.throw(Error, 'greater than 5');
+    });
+
+    it('should throw if memorySize is greater than 3008 for origin-request or origin-response functions', () => {
+      awsCompileCloudFrontEvents.serverless.service.functions = {
+        first: {
+          memorySize: 3009,
+          events: [
+            {
+              cloudFront: {
+                eventType: 'origin-request',
+                origin: 's3://bucketname.s3.amazonaws.com/files',
+              },
+            },
+          ],
+        },
+      };
+
+      expect(() => {
+        awsCompileCloudFrontEvents.validate();
+      }).to.throw(Error, 'greater than 3008');
+
+      awsCompileCloudFrontEvents.serverless.service.functions = {
+        first: {
+          memorySize: 3009,
+          events: [
+            {
+              cloudFront: {
+                eventType: 'origin-response',
+                origin: 's3://bucketname.s3.amazonaws.com/files',
+              },
+            },
+          ],
+        },
+      };
+
+      expect(() => {
+        awsCompileCloudFrontEvents.validate();
+      }).to.throw(Error, 'greater than 3008');
+    });
+
+    it('should throw if timeout is greater than 30 for origin-request or origin response functions', () => {
+      awsCompileCloudFrontEvents.serverless.service.functions = {
+        first: {
+          timeout: 31,
+          events: [
+            {
+              cloudFront: {
+                eventType: 'origin-request',
+                origin: 's3://bucketname.s3.amazonaws.com/files',
+              },
+            },
+          ],
+        },
+      };
+
+      expect(() => {
+        awsCompileCloudFrontEvents.validate();
+      }).to.throw(Error, 'greater than 30');
+      awsCompileCloudFrontEvents.serverless.service.functions = {
+        first: {
+          timeout: 31,
+          events: [
+            {
+              cloudFront: {
+                eventType: 'origin-response',
+                origin: 's3://bucketname.s3.amazonaws.com/files',
+              },
+            },
+          ],
+        },
+      };
+
+      expect(() => {
+        awsCompileCloudFrontEvents.validate();
+      }).to.throw(Error, 'greater than 30');
     });
   });