-
Notifications
You must be signed in to change notification settings - Fork 5.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added support for Promises in the variable system #3554
Conversation
Sweet @eahefnawy! Thanks! This will be a huge help for anyone who wants to load dynamic config from, for instance, an S3 bucket, a DynamoDB table, an API call, etc, etc! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a really exciting PR! 💯
Here are a few comments / questions:
- Why are native Promises used instead of Bluebird promises (since we use then throughout the whole codebase?)
- Could you update the tests so that
sinon-bluebird
is used (introduced in add sinon-bluebird to make promises in tests a bit cleaner #3000). This way the tests are in sync - Shoud we maybe remove the
traverse
package in this PR? AFAIK there are no other places wheretraverse
is used.
Also tested it locally. So far everything works fine. Will test it more thoroughly the upcoming days!
Below is the serverless.yml
file I've used for testing.
I exported the TESTING=ya
env variable and ran serverless package --stage dev --yahoo hi --region us-east-1 --number 1234
.
To validate it you could look into the Outputs
section in the updated CloudFormation file
service: async-vars
provider:
name: aws
runtime: nodejs6.10
custom:
data: # some data to reference
zero: 0
nooo: false
varA: ${env:TESTING} # env vars
varB: ${opt:stage} # opts vars
varC: ${self:provider.name} # self vars
varD: ${env:TESTING}-${opt:stage} # two vars in a string
varE: ${opt:${env:TESTING}hoo} # nested vars
varF: ${opt:region, opt:stage} # overwrite functionality
varG: ${file(./vars.js):hello} # JS file running sync
varH: ${file(./vars.js):promised} # JS file running async/promised NEW!!!
varI: ${file(./vars.json):hoo.hoo2} # deep JSON file
varJ: ${file(./vars.yml):hee.hee2} # deep YAML file
varK: my stage is ${opt:stage} # vars sub string
varL: your account number is ${opt:number} # number vars as sub string
varM: ${opt:number} # preserving data type
varN: ${self:plugins, self:package, self:service} # multiple overwrites when empty object and undefined
varO: ${self:custom.data.zero, self:service} # shouldn't overwrite 0 values
varP: ${self:custom.data.nooo, self:service} # shouldn't overwrite false values
# varQ: ${self:} # referencing the entire config file
functions:
hello:
handler: handler.hello
resources:
Outputs:
varA:
Value: ${self:custom.varA}
Export:
Name: varA
varB:
Value: ${self:custom.varB}
Export:
Name: varB
varC:
Value: ${self:custom.varC}
Export:
Name: varC
varD:
Value: ${self:custom.varD}
Export:
Name: varD
varE:
Value: ${self:custom.varE}
Export:
Name: varE
varF:
Value: ${self:custom.varF}
Export:
Name: varF
varG:
Value: ${self:custom.varG}
Export:
Name: varG
varH:
Value: ${self:custom.varH}
Export:
Name: varH
varI:
Value: ${self:custom.varI}
Export:
Name: varI
varJ:
Value: ${self:custom.varJ}
Export:
Name: varJ
varK:
Value: ${self:custom.varK}
Export:
Name: varK
varL:
Value: ${self:custom.varL}
Export:
Name: varL
varM:
Value: ${self:custom.varM}
Export:
Name: varM
varN:
Value: ${self:custom.varN}
Export:
Name: varN
varO:
Value: ${self:custom.varO}
Export:
Name: varO
varP:
Value: ${self:custom.varP}
Export:
Name: varP
# varQ:
# Value: ${self:custom.varQ}
# Export:
# Name: varQ
lib/classes/Variables.js
Outdated
} | ||
}); | ||
|
||
this.service.provider.variableSyntax = variableSyntaxProperty; | ||
return this.service; | ||
return Promise.all(populateAll).then(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason why native Promises are used vs. Bluebird promises? (AFAIK Bluebird promises are faster. Not sure if this hold true for Node.js).
lib/classes/Variables.test.js
Outdated
@@ -42,12 +42,12 @@ describe('Variables', () => { | |||
const serverless = new Serverless(); | |||
|
|||
const populatePropertyStub = sinon | |||
.stub(serverless.variables, 'populateProperty'); | |||
.stub(serverless.variables, 'populateProperty').returns(Promise.resolve()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could do .resolves()
since we're now using the sinon-bluebird
package (introduced in #3000)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@@ -96,6 +96,7 @@ | |||
"js-yaml": "^3.6.1", | |||
"json-refs": "^2.1.5", | |||
"lodash": "^4.13.1", | |||
"lodash-deep": "^2.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having this we could npm uninstall
the traverse
package, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@@ -164,6 +164,25 @@ module.exports.schedule = () => { | |||
}; | |||
} | |||
``` | |||
If your use case requires handling with dynamic/async data sources (ie. DynamoDB, API calls...etc), you can also return a promise that would be resolved as the value of the variable: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uppercase 'Promise'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like some old Promise
references are still there.
Will do a follow-up PR and clean things up 👍
if (typeof property === 'string') { | ||
property = that.populateProperty(property, true); | ||
t.update(property); | ||
const populateSingleProperty = new Promise((resolve) => this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be new BbPromise()
?
valueToPopulate = this.overwrite(variableString); | ||
} else { | ||
valueToPopulate = this.getValueFromSource(variableString); | ||
const singleValueToPopulate = new Promise((resolve) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be new BbPromise()
?
serverless.variables.populateVariable.restore(); | ||
serverless.variables.overwrite.restore(); | ||
serverless.variables.populateVariable.restore(); | ||
return Promise.resolve(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return BbPromise.resolve()
serverless.variables.populateVariable.restore(); | ||
serverless.variables.getValueFromSource.restore(); | ||
serverless.variables.populateVariable.restore(); | ||
return Promise.resolve(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return BbPromise.resolve()
@@ -104,7 +105,6 @@ | |||
"semver": "^5.0.3", | |||
"semver-regex": "^1.0.0", | |||
"shelljs": "^0.6.0", | |||
"traverse": "^0.6.6", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs some shrinkwrapping,
What did you implement:
Closes #3073
Added support for async sources in the variable system, beginning with the
file
source. Your JS files can now perform async operations and return a promise that would be resolved by the variable system.How did you implement it:
Previously, the variable system was completely sync, so I had to control the flow of the entire system and it's methods and make it promise-ready. All methods now return a promise to assure everything is running in the correct flow.
I've also ditched the
traverse
module since it doesn't work smoothly with promises and instead I'm using a lodash based implementation for object traversal and updates.How can we verify it:
I've tested 17 different cases for the variable systems that covers pretty much everything the variables system can do, including the new promises in files support. Here's the config I used, but feel free to get crazy creative:
You'll need the
TESTING
env var to be set, and also the following vars files in your service root dir:vars.js
vars.yml
vars.json
You'll need to run the following command against this service for full test coverage:
Todos:
Is this ready for review?: YES
Is it a breaking change?: NO