Repository: serverless/serverless (JavaScript) — Bug Fix

Issue:
 A valid undefined to satisfy the declaration
Serverless allows for default variable references, e.g.

    ${self:custom.site.FOO, self:custom.common.FOO}

But SLS complains "A valid undefined to satisfy the declaration" when variables don't appear in the first dict, even though they are correctly resolved by the second.

What's the point of having default variables if it complains in this way?


Discussion:
https://github.com/serverless/serverless/blob/d817ce08d41c8a2554da8c819cfeaef04a886df6/lib/classes/Variables.js#L818-L819

In severless 1.36.1, The message is shown when right-side default value (`self:custom.common.FOO` for this time) is null, undefined or empty plain object.

`${varType}` in the message is the infered type of variable syntax of the left-side specified value.
For your case, it supposed to be `service attribute` since the specified value matches `self:...` syntax.
However, something went wrong for your case.

To reproduce and investigate further, the following are required.

* serverless.yml 
* severless command you run
* Serverless Framework Version you're using
* Operating System
> In severless 1.36.1, The message is shown when right-side default value (self:custom.common.FOO for this time) is null, undefined or empty plain object.

Well it also displays in my case when the RHS is not NULL, but the LHS is NULL.

E.g.

    custom:
        site: ${file(../site.yml)}
        common: ${file(../common.yml)}

    provider:
      name: aws
      runtime: python3.6
      region: ${self:custom.site.REGION, self:custom.common.REGION}

Make site.yml an empty file (or put other vars in there), and set REGION to something in common.yml, and in my case I get the warning, even though provider.region does get set to the correct value.


I am using sls deploy and 1.36.1 on RHEL7.
Sounds similar https://github.com/serverless/serverless/issues/5353
@cyberfox1 
Could you share full content of `common.yml` to reprodue?
The issue could be due to invalid format of the file.

Also, could you share actual warning without omitting any words ?
When I tried, the following is shown. 


common.yml
```yml
REGION: yay # intended to fail deploy
```

```
$ sls deploy
 
  A valid file to satisfy the declaration 'file(site.yml)' could not be found.
 
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Recoverable error occurred (Inaccessible host: `cloudformation.yay.amazonaws.com'. This service may not be available in the `yay' region.), sleeping for 5 seconds. Try 1 of 4
Serverless: Recoverable error occurred (Inaccessible host: `cloudformation.yay.amazonaws.com'. This service may not be available in the `yay' region.), sleeping for 5 seconds. Try 2 of 4
Serverless: Recoverable error occurred (Inaccessible host: `cloudformation.yay.amazonaws.com'. This service may not be available in the `yay' region.), sleeping for 5 seconds. Try 3 of 4
Serverless: Recoverable error occurred (Inaccessible host: `cloudformation.yay.amazonaws.com'. This service may not be available in the `yay' region.), sleeping for 5 seconds. Try 4 of 4
 
  Serverless Error ---------------------------------------
 
  ServerlessError: Inaccessible host: `cloudformation.yay.amazonaws.com'. This service may not be available in the `yay' region.
 
  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com
 
  Your Environment Information -----------------------------
     OS:                     linux
     Node Version:           8.10.0
     Serverless Version:     1.36.1
```

```
$ sls print

 Serverless Warning --------------------------------------
 
  A valid file to satisfy the declaration 'file(site.yml)' could not be found.
 
 
 Serverless Warning --------------------------------------
 
  A valid file to satisfy the declaration 'file(site.yml)' could not be found.
 
service: test-service
custom:
  common:
    REGION: yay
provider:
  region: yay
  name: aws
  runtime: nodejs8.10
functions:
  hello:
    handler: handler.hello
```




Resolving patch:
diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js
index 940b44b4f1c..c65866f3bdc 100644
--- a/lib/classes/Variables.js
+++ b/lib/classes/Variables.js
@@ -815,8 +815,10 @@ class Variables {
       } else if (variableString.match(this.ssmRefSyntax)) {
         varType = 'SSM parameter';
       }
-      logWarning(`A valid ${varType} to satisfy the declaration '${
-        variableString}' could not be found.`);
+      if (!_.isUndefined(varType)) {
+        logWarning(`A valid ${varType} to satisfy the declaration '${
+          variableString}' could not be found.`);
+      }
     }
     return valueToPopulate;
   }


Test patch:
diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js
index 456d6187d48..f8af35f59b2 100644
--- a/lib/classes/Variables.test.js
+++ b/lib/classes/Variables.test.js
@@ -1854,6 +1854,21 @@ module.exports = {
       varProxy.warnIfNotFound('self:service', 'a-valid-value');
       expect(logWarningSpy).to.not.have.been.calledOnce;
     });
+    describe('when variable string does not match any of syntax', () => {
+      // These situation happen when deep variable population fails
+      it('should do nothing if variable has null value.', () => {
+        varProxy.warnIfNotFound('', null);
+        expect(logWarningSpy).to.not.have.been.calledOnce;
+      });
+      it('should do nothing if variable has undefined value.', () => {
+        varProxy.warnIfNotFound('', undefined);
+        expect(logWarningSpy).to.not.have.been.calledOnce;
+      });
+      it('should do nothing if variable has empty object value.', () => {
+        varProxy.warnIfNotFound('', {});
+        expect(logWarningSpy).to.not.have.been.calledOnce;
+      });
+    });
     it('should log if variable has null value.', () => {
       varProxy.warnIfNotFound('self:service', null);
       expect(logWarningSpy).to.have.been.calledOnce;