Repository: serverless/serverless (JavaScript)
Date: 2018-12-05 11:06:38+00:00
Task type: Bug Fix

Issue:
Regression: Serverless 1.33 does not properly support integers as external values
# This is a Bug Report

## Description

* What went wrong? 
A `logRetentionInDays should be an integer over 0` error interrupts deployment when the logRetentionInDays value is provided externally (in my case via a shell command)
* What did you expect should have happened? 
Externally provided integer is properly interpreted and deployment completes successfully.
* What was the config you used? 
Trying to deploy a 60-day expiration log retention lambda to AWS.
* What stacktrace or error message from your provider did you see?
`logRetentionInDays should be an integer over 0`
Node version is 8.10.0

Similar or dependent issues:
[This forum post](https://forum.serverless.com/t/variables-from-external-javascript-file-seem-to-be-internally-cast-to-string/5792)
[This stackoverflow post](https://stackoverflow.com/questions/49645453/using-ssm-parameter-to-set-logretentionindays-in-serverless-yml?rq=1)
Not mine, but assuming same issue

## Additional Data

* ***Serverless Framework Version you're using***: 1.33.[0,1,2] (Error occurs in all three versions. 1.32 and earlier work as expected.)
* ***Operating System***: Linux (Xubuntu 18.04)
* ***Stack Trace***: 
* ***Provider Error messages***:  `logRetentionInDays should be an integer over 0`



Issue discussion:
Could you provide the relevant parts of your `serverless.yml` please?
```yml
service: "issue"
provider:
  name: "aws"
  stage: "dev"
  logRetentionInDays: "${opt:logRetentionInDays, self:custom.defaultLogRetentionInDays}"
  runtime: "nodejs8.10"
functions:
  hello:
    handler: "handler.hello"
    events: []
plugins: []
package:
  artifact: "../../target/lambda.zip"
custom:
  defaultLogRetentionInDays: 14
```
Ah.. What if you do:
```
  logRetentionInDays: ${opt:logRetentionInDays, self:custom.defaultLogRetentionInDays}
```
(remove the quotes)
Tho I wouldn't expect `${opt:logRetentionInDays}` to work :disappointed: I'm not sure how this is dealt with for other options that must be integers. IMO because of the `${opt:adsflajdf}` feature (and other variable sources like SSM) it makes sense to attempt to cast values to the right type. Thoughts @pmuens @eahefnawy @horike37?
Confirming that removing the quotes does not change the outcome.

Was the `${opt:adsflajdf}` feature modified in `1.33`?
does `logRetentionInDays: ${self:custom.defaultLogRetentionInDays}` alone with an integer at `custom.defaultLogRetentionInDays` work? I don't think the opt option has ever coerced values to different types :/
Yes, `${self:custom.defaultLogRetentionInDays}` alone does work (as does the full line when the var isn't provided).
Since the original serverless.yml did work as-is in previous versions, the variables were coerced somehow at some point :\ 
Thanks for the details @Jaystified 

Solution patch:
diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.js b/lib/plugins/aws/package/lib/mergeIamTemplates.js
index 0f28c782619..cb74e820d85 100644
--- a/lib/plugins/aws/package/lib/mergeIamTemplates.js
+++ b/lib/plugins/aws/package/lib/mergeIamTemplates.js
@@ -32,12 +32,13 @@ module.exports = {
       };
 
       if (_.has(this.serverless.service.provider, 'logRetentionInDays')) {
-        if (_.isInteger(this.serverless.service.provider.logRetentionInDays) &&
-          this.serverless.service.provider.logRetentionInDays > 0) {
-          newLogGroup[logGroupLogicalId].Properties.RetentionInDays
-            = this.serverless.service.provider.logRetentionInDays;
+        const rawRetentionInDays = this.serverless.service.provider.logRetentionInDays;
+        const retentionInDays = parseInt(rawRetentionInDays, 10);
+        if (_.isInteger(retentionInDays) && retentionInDays > 0) {
+          newLogGroup[logGroupLogicalId].Properties.RetentionInDays = retentionInDays;
         } else {
-          const errorMessage = 'logRetentionInDays should be an integer over 0';
+          const errorMessage =
+            `logRetentionInDays should be an integer over 0 but ${rawRetentionInDays}`;
           throw new this.serverless.classes.Error(errorMessage);
         }
       }


Test patch:
diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js
index 1a236a74311..49e6ea18ef6 100644
--- a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js
+++ b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js
@@ -296,20 +296,22 @@ describe('#mergeIamTemplates()', () => {
 
   it('should add RetentionInDays to a CloudWatch LogGroup resource if logRetentionInDays is given'
     , () => {
-      awsPackage.serverless.service.provider.logRetentionInDays = 5;
-      const normalizedName = awsPackage.provider.naming.getLogGroupLogicalId(functionName);
-      return awsPackage.mergeIamTemplates().then(() => {
-        expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate
-          .Resources[normalizedName]
-        ).to.deep.equal(
-          {
-            Type: 'AWS::Logs::LogGroup',
-            Properties: {
-              LogGroupName: awsPackage.provider.naming.getLogGroupName(functionName),
-              RetentionInDays: 5,
-            },
-          }
+      [5, '5'].forEach((logRetentionInDays) => {
+        awsPackage.serverless.service.provider.logRetentionInDays = logRetentionInDays;
+        const normalizedName = awsPackage.provider.naming.getLogGroupLogicalId(functionName);
+        return awsPackage.mergeIamTemplates().then(() => {
+          expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate
+            .Resources[normalizedName]
+          ).to.deep.equal(
+            {
+              Type: 'AWS::Logs::LogGroup',
+              Properties: {
+                LogGroupName: awsPackage.provider.naming.getLogGroupName(functionName),
+                RetentionInDays: 5,
+              },
+            }
           );
+        });
       });
     });