diff --git a/lib/Serverless.js b/lib/Serverless.js index 53d7f6615..379709039 100644 --- a/lib/Serverless.js +++ b/lib/Serverless.js @@ -54,7 +54,7 @@ class Serverless { .then(() => { // set the provider of the service (so that the PluginManager takes care to // execute the correct provider specific plugins) - this.pluginManager.setProvider(this.service.provider.name || this.service.provider); + this.pluginManager.setProvider(this.service.provider.name); // load all plugins this.pluginManager.loadAllPlugins(this.service.plugins); @@ -62,6 +62,9 @@ class Serverless { // give the CLI the plugins so that it can print out plugin information // such as options when the user enters --help this.cli.setLoadedPlugins(this.pluginManager.getPlugins()); + + // populate variables after processing options + return this.service.populate(this.pluginManager.cliOptions); }); } diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 74d27ae1a..bb1b02691 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -18,7 +18,7 @@ class Service { this.defaults = { stage: 'dev', region: 'us-east-1', - variableSyntax: null, + variableSyntax: '\\${([a-zA-Z0-9._\\-\\/\\(\\)]+?)}', }; this.custom = {}; this.plugins = []; @@ -30,9 +30,9 @@ class Service { if (data) this.update(data); } - load(opts) { + load(rawOptions) { const that = this; - const options = opts || {}; + const options = rawOptions || {}; options.stage = options.stage || options.s; options.region = options.region || options.r; const servicePath = that.serverless.config.servicePath; @@ -50,279 +50,321 @@ class Service { .join(this.serverless.config.servicePath, 'serverless.yaml'); } - let serverlessEnvYmlPath = path.join(servicePath, 'serverless.env.yml'); - // change to serverless.env.yaml if the file could not be found - if (!this.serverless.utils.fileExistsSync(serverlessEnvYmlPath)) { - serverlessEnvYmlPath = path - .join(this.serverless.config.servicePath, 'serverless.env.yaml'); - } - return that.serverless.yamlParser .parse(serverlessYmlPath) - .then((serverlessYmlParam) => { - const serverlessYml = serverlessYmlParam; + .then((serverlessFileParam) => { + const serverlessFile = serverlessFileParam; // basic service level validation - if (!serverlessYml.service) { + if (!serverlessFile.service) { throw new SError('"service" property is missing in serverless.yml'); } - if (!serverlessYml.provider) { + if (!serverlessFile.provider) { throw new SError('"provider" property is missing in serverless.yml'); } - if (!serverlessYml.functions) { + if (!serverlessFile.functions) { throw new SError('"functions" property is missing in serverless.yml'); } - if (typeof serverlessYml.provider !== 'object') { - const providerName = serverlessYml.provider; - serverlessYml.provider = { + if (typeof serverlessFile.provider !== 'object') { + const providerName = serverlessFile.provider; + serverlessFile.provider = { name: providerName, }; } - if (['aws', 'azure', 'google', 'ibm'].indexOf(serverlessYml.provider.name)) { + if (['aws', 'azure', 'google', 'ibm'].indexOf(serverlessFile.provider.name)) { const errorMessage = [ - `Provider "${serverlessYml.provider.name}" is not supported.`, + `Provider "${serverlessFile.provider.name}" is not supported.`, ' Valid values for provider are: aws, azure, google, ibm.', ' Please provide one of those values to the "provider" property in serverless.yml.', ].join(''); throw new SError(errorMessage); } - that.service = serverlessYml.service; - that.provider = serverlessYml.provider; - that.custom = serverlessYml.custom; - that.plugins = serverlessYml.plugins; - that.resources = serverlessYml.resources; - that.functions = serverlessYml.functions; - - _.forEach(that.functions, (functionObj, index) => { - if (!functionObj.events) { - that.functions[index].events = []; - } - }); - - if (serverlessYml.package && serverlessYml.package.artifact) { - that.package.artifact = serverlessYml.package.artifact; - } - if (serverlessYml.package && serverlessYml.package.exclude) { - that.package.exclude = serverlessYml.package.exclude; - } - if (serverlessYml.package && serverlessYml.package.include) { - that.package.include = serverlessYml.package.include; - } - - if (serverlessYml.defaults && serverlessYml.defaults.stage) { - this.defaults.stage = serverlessYml.defaults.stage; - } - if (serverlessYml.defaults && serverlessYml.defaults.region) { - this.defaults.region = serverlessYml.defaults.region; - } - if (serverlessYml.defaults && serverlessYml.defaults.variableSyntax) { - this.defaults.variableSyntax = serverlessYml.defaults.variableSyntax; - } - }) - .then(() => that.serverless.yamlParser - .parse(serverlessEnvYmlPath)) - .then((serverlessEnvYmlParam) => { - const serverlessEnvYml = serverlessEnvYmlParam; - - // safely load serverless.env.yml while avoiding - // reference errors - serverlessEnvYml.vars = serverlessEnvYml.vars || {}; - serverlessEnvYml.stages = serverlessEnvYml.stages || {}; - Object.keys(serverlessEnvYml.stages).forEach(stage => { - serverlessEnvYml.stages[stage] = serverlessEnvYml.stages[stage] || {}; - serverlessEnvYml.stages[stage].vars = serverlessEnvYml.stages[stage].vars || {}; - serverlessEnvYml.stages[stage].regions = serverlessEnvYml.stages[stage].regions || {}; - Object.keys(serverlessEnvYml.stages[stage].regions).forEach(region => { - serverlessEnvYml.stages[stage].regions[region] = - serverlessEnvYml.stages[stage].regions[region] || {}; - serverlessEnvYml.stages[stage].regions[region].vars = - serverlessEnvYml.stages[stage].regions[region].vars || {}; - }); - }); - - that.environment = serverlessEnvYml; - - return BbPromise.resolve(that); - }) - .then(() => { - if (!options.stage) { - options.stage = this.defaults.stage; - } - - if (!options.region) { - options.region = this.defaults.region; - } - - // Validate: Check stage exists - this.getStage(options.stage); - - // Validate: Check region exists in stage - this.getRegionInStage(options.stage, options.region); + that.service = serverlessFile.service; + that.provider = serverlessFile.provider; + that.custom = serverlessFile.custom; + that.plugins = serverlessFile.plugins; + that.resources = serverlessFile.resources; + that.functions = serverlessFile.functions; // setup function.name property _.forEach(that.functions, (functionObj, functionName) => { + if (!functionObj.events) { + that.functions[functionName].events = []; + } + if (!functionObj.name) { that.functions[functionName].name = `${that.service}-${options.stage}-${functionName}`; } }); - let varTemplateSyntax = /\${([\s\S]+?)}/g; - - if (this.defaults && this.defaults.variableSyntax) { - varTemplateSyntax = RegExp(this.defaults.variableSyntax, 'g'); - - // temporally remove variable syntax from service otherwise it'll match - this.defaults.variableSyntax = true; + if (serverlessFile.package && serverlessFile.package.artifact) { + that.package.artifact = serverlessFile.package.artifact; + } + if (serverlessFile.package && serverlessFile.package.exclude) { + that.package.exclude = serverlessFile.package.exclude; + } + if (serverlessFile.package && serverlessFile.package.include) { + that.package.include = serverlessFile.package.include; } - const commonVars = this.getVariables(); - const stageVars = this.getVariables(options.stage); - const regionVars = this.getVariables(options.stage, options.region); + if (serverlessFile.defaults && serverlessFile.defaults.stage) { + this.defaults.stage = serverlessFile.defaults.stage; + } + if (serverlessFile.defaults && serverlessFile.defaults.region) { + this.defaults.region = serverlessFile.defaults.region; + } + if (serverlessFile.defaults && serverlessFile.defaults.variableSyntax) { + this.defaults.variableSyntax = serverlessFile.defaults.variableSyntax; + } - // temporally remove environment obj. Doesn't make sense to - // populate environment (stages, regions, vars) - const environment = _.cloneDeep(this.environment); - this.environment = null; + if (serverlessFile.defaults) { + const warningMessage = [ + 'Deprecation Notice: the "defaults" property in serverless.yml', + ' is deprecated. The "stage", "region" & "variableSyntax" properties', + ' has been moved to the "provider" property instead. Please update', + ' your serverless.yml file asap. For more info, you can check our docs.', + ].join(''); + this.serverless.cli.log(warningMessage); - /* - * we can't use an arrow function in this case cause that would - * change the lexical scoping required by the traverse module - */ - traverse(this).forEach(function (valParam) { - const t = this; - let val = valParam; - - // check if the current string is a variable - if (typeof (val) === 'string' && val.match(varTemplateSyntax)) { - // get all ${variable} in the string - val.match(varTemplateSyntax).forEach((variableSyntax) => { - const variableString = variableSyntax - .replace(varTemplateSyntax, (match, varName) => varName.trim()); - - const variableName = (variableString - .split('.').length > 1) ? variableString - .split('.')[0] : variableString; - - let value; - - /* - * we will manipulate the value later - * so we gotta clone otherwise we will - * corrupt the passed-by-reference variables object - */ - if (variableName in commonVars) { - value = _.cloneDeep(commonVars[variableName]); - } - - if (variableName in stageVars) { - value = _.cloneDeep(stageVars[variableName]); - } - - if (variableName in regionVars) { - value = _.cloneDeep(regionVars[variableName]); - } - - // Populate - if (typeof value === 'undefined') { - const errorMessage = [ - `Variable "${variableName}" doesn't exist in serverless.env.yml.`, - ' Please add it to serverless.env.yml.', - ].join(''); - throw new that.serverless.classes - .Error(errorMessage); - } else if (typeof value === 'string') { - if (variableString.split('.').length > 1) { - const errorMessage = [ - `Trying to access sub properties of a string variable "${variableName}".`, - ' Please make sure the variable in serverless.env.yml', - ' is an object, otherwise you cannot use the', - ' dot notation for that variable in serverless.yml', - ].join(''); - throw new that.serverless.classes - .Error(errorMessage); - } - // for string variables, we use replaceall in case the user - // includes the variable as a substring (ie. "hello ${name}") - val = replaceall(variableSyntax, value, val); - } else { - // populate objects recursively - /* eslint no-lonely-if: "off" */ - if (typeof value === 'object') { - const subProperties = variableString.split('.'); - // remove first element. It's the variableName - subProperties.splice(0, 1); - subProperties.forEach(subProperty => { - if (!value[subProperty]) { - const errorMessage = [ - `Variable "${variableName}" doesn't have sub property "${subProperty}".`, - ' Please make sure the variable is', - ' the intended object in serverless.env.yml,', - ' or reference the correct sub property in serverless.yml', - ].join(''); - throw new that.serverless.classes - .Error(errorMessage); - } - value = value[subProperty]; - }); - - if (typeof value === 'string') { - val = replaceall(variableSyntax, value, val); - } else { - if (val !== variableSyntax) { - const errorMessage = [ - 'Trying to populate non string variables into', - ` a string for variable "${variableName}".`, - ' Please make sure the variable value in', - ' serverless.env.yml is a string', - ].join(''); - throw new that.serverless.classes - .Error(errorMessage); - } - val = value; - } - } else if (variableString.split('.').length > 1) { - const errorMessage = [ - `Trying to access sub properties of a non-object variable "${variableName}"`, - ' Please make sure the variable is an object in serverless.env.yml,', - ' otherwise, you cannot use the dot notation', - ' for that variable in serverless.yml', - ].join(''); - throw new that.serverless.classes - .Error(errorMessage); - } else { - if (val !== variableSyntax) { - const errorMessage = [ - 'Trying to populate non string variables', - ` into a string for variable "${variableName}".`, - ' Please make sure the variable value in serverless.env.yml is a string', - ].join(''); - throw new that.serverless.classes - .Error(errorMessage); - } - val = value; // not string nor object - } - } - }); - - // Replace - t.update(val); + if (serverlessFile.defaults.stage) { + this.defaults.stage = serverlessFile.defaults.stage; } - }); + if (serverlessFile.defaults.region) { + this.defaults.region = serverlessFile.defaults.region; + } + if (serverlessFile.defaults.variableSyntax) { + this.defaults.variableSyntax = serverlessFile.defaults.variableSyntax; + } + } - // put back environment that we temporally removed earlier - this.environment = environment; - - // put back variable syntax if we removed it for processing - if (this.defaults && this.defaults.variableSyntax) { - this.defaults.variableSyntax = varTemplateSyntax; + // Moving defaults into provider obj + if (serverlessFile.provider.stage) { + this.defaults.stage = serverlessFile.provider.stage; + } + if (serverlessFile.provider.region) { + this.defaults.region = serverlessFile.provider.region; + } + if (serverlessFile.provider.variableSyntax) { + this.defaults.variableSyntax = serverlessFile.provider.variableSyntax; } return this; }); } + populate(processedOptions) { + const that = this; + const options = processedOptions || {}; + const variableSyntaxProperty = this.defaults.variableSyntax; + const variableSyntax = RegExp(variableSyntaxProperty, 'g'); + const fileRefSyntax = RegExp(/^file\(([a-zA-Z0-9._\-\/]+?)\)/g); + // const fileRefSyntax = RegExp('^file\\(([a-zA-Z0-9._\\-\\/]+?)\\)', 'g'); + + // temporally remove variable syntax from service otherwise it'll match + this.defaults.variableSyntax = true; + this.serverless.service.defaults.variableSyntax = true; + + + /* + * we can't use an arrow function in this case cause that would + * change the lexical scoping required by the traverse module + */ + traverse(this).forEach(function (property) { + const t = this; + + if (typeof property === 'string') { + const nestedPopulate = (updatedPropertyParam) => { + let updatedProperty = updatedPropertyParam; + if (typeof updatedProperty === 'string' && updatedProperty.match(variableSyntax)) { + updatedProperty.match(variableSyntax).forEach((matchedString) => { + const variableString = matchedString + .replace(variableSyntax, (match, varName) => varName.trim()); + + /* + * File Reference + */ + if (variableString.match(fileRefSyntax)) { + const matchedFileRefString = variableString.match(fileRefSyntax)[0]; + const referencedFileRelativePath = matchedFileRefString + .replace(fileRefSyntax, (match, varName) => varName.trim()); + const referencedFileFullPath = path.join(that.serverless.config.servicePath, + referencedFileRelativePath); + + let value = that.serverless.utils.readFileSync(referencedFileFullPath); + if (matchedFileRefString !== variableString) { + let deepProperties = variableString + .replace(matchedFileRefString, ''); + if (deepProperties.substring(0, 1) !== '.') { + const errorMessage = [ + 'Invalid variable syntax when referencing', + ` file "${referencedFileRelativePath}"`, + ' Please use valid dot notation when referencing sub properties.', + ].join(''); + throw new that.serverless.classes + .Error(errorMessage); + } + deepProperties = deepProperties.slice(1); + const selfSubProperties = deepProperties.split('.'); + selfSubProperties.forEach(selfSubProperty => { + if (!value[selfSubProperty]) { + const errorMessage = [ + `file "${referencedFileRelativePath}" doesn't`, + ` have sub property "${selfSubProperty}".`, + ' Please make sure you are referencing the correct sub property', + ].join(''); + throw new that.serverless.classes + .Error(errorMessage); + } + value = value[selfSubProperty]; + }); + } + + if (typeof value === 'string') { + updatedProperty = replaceall(matchedString, value, updatedProperty); + } else { + if (updatedProperty !== matchedString) { + const errorMessage = [ + 'Trying to populate non string value into', + ` a string when referencing file "${referencedFileRelativePath}".`, + ' Please make sure the value of the property', + ' is a string', + ].join(''); + throw new that.serverless.classes + .Error(errorMessage); + } + updatedProperty = value; + } + /* + * Env Var Reference + */ + } else if (variableString.split('.')[0] === 'env') { + if (variableString.split('.').length !== 2) { + const errorMessage = [ + 'Trying to access sub properties of environment', + ' variable strings, or trying to reference all environment variable.', + ].join(''); + throw new SError(errorMessage); + } + const requestedEnvVar = variableString.split('.')[1]; + const propertyValue = process.env[requestedEnvVar]; + if (typeof propertyValue === 'undefined') { + const errorMessage = [ + `Environment variable ${requestedEnvVar} is not set on your machine.`, + ' Please set this env var before referencing it as a variable.', + ].join(''); + throw new SError(errorMessage); + } + updatedProperty = replaceall(matchedString, propertyValue, updatedProperty); + + /* + * Options Reference + */ + } else if (variableString.split('.')[0] === 'opt') { + if (variableString.split('.').length === 1) { + // load all options object + if (updatedProperty === matchedString) { + updatedProperty = options; + } else { + const errorMessage = [ + 'Trying to reference all options object as a substring.', + ' Please make sure the string referencing the variable', + ' Does not contain any other sub-strings,', + ' or reference a specific option string.', + ].join(''); + throw new SError(errorMessage); + } + } else if (variableString.split('.').length === 2) { + // load specific option + const requestedOption = variableString.split('.')[1]; + const propertyValue = options[requestedOption]; + if (typeof propertyValue === 'undefined') { + const errorMessage = [ + `Option ${requestedOption} was not passed in the CLI.`, + ' Please pass this variable in the CLI to use in serverless.yml.', + ].join(''); + throw new SError(errorMessage); + } + updatedProperty = replaceall(matchedString, propertyValue, updatedProperty); + } else { + const errorMessage = [ + 'Trying to reference a specific option sub properties.', + ' Each passed option can only be a string, not objects.', + ' Please make sure you only reference the option string', + ' without any other dot notation.', + ].join(''); + throw new SError(errorMessage); + } + + /* + * Self Reference + */ + } else if (variableString.split('.')[0] === 'self') { + if (variableString.split('.').length === 1) { + const errorMessage = [ + 'You can\'t reference the entire "self" serverless.yml file.', + ' Please reference a sub property with ${self.subProp}', + ].join(''); + throw new that.serverless.classes + .Error(errorMessage); + } + let value = _.cloneDeep(that); + const selfSubProperties = variableString.split('.'); + // remove first element. It's the "self" keyword + selfSubProperties.splice(0, 1); + selfSubProperties.forEach(selfSubProperty => { + if (!value[selfSubProperty]) { + const errorMessage = [ + `serverless.yml doesn't have sub property "${selfSubProperty}".`, + ' Please make sure you are referencing the correct sub property', + ].join(''); + throw new that.serverless.classes + .Error(errorMessage); + } + value = value[selfSubProperty]; + }); + + if (typeof value === 'string') { + updatedProperty = replaceall(matchedString, value, updatedProperty); + } else { + if (updatedProperty !== matchedString) { + const errorMessage = [ + 'Trying to populate non string value into', + ' a string when referencing "self".', + ' Please make sure the value of the property', + ' is a string', + ].join(''); + throw new that.serverless.classes + .Error(errorMessage); + } + updatedProperty = value; + } + } else { + const errorMessage = [ + `Invalid variable reference syntax for variable ${matchedString}.`, + ' You can only reference env vars, options, & files.', + ' You can check our docs for more info.', + ].join(''); + throw new SError(errorMessage); + } + }); + + return nestedPopulate(updatedProperty); + } + return updatedProperty; + }; + const updatedProperty = nestedPopulate(property); + t.update(updatedProperty); + } + }); + + // put back variable syntax that we removed earlier + this.defaults.variableSyntax = variableSyntaxProperty; + this.serverless.service.defaults.variableSyntax = variableSyntaxProperty; + return this; + } + update(data) { return _.merge(this, data); } @@ -348,37 +390,6 @@ class Service { getAllEventsInFunction(functionName) { return Object.keys(this.getFunction(functionName).events); } - - getStage(stageName) { - if (stageName in this.environment.stages) { - return this.environment.stages[stageName]; - } - throw new SError(`Stage "${stageName}" doesn't exist in this service.`); - } - - getAllStages() { - return Object.keys(this.environment.stages); - } - - getRegionInStage(stageName, regionName) { - if (regionName in this.getStage(stageName).regions) { - return this.getStage(stageName).regions[regionName]; - } - throw new SError(`Region "${regionName}" doesn't exist in stage "${stageName}"`); - } - - getAllRegionsInStage(stageName) { - return Object.keys(this.getStage(stageName).regions); - } - - getVariables(stageName, regionName) { - if (stageName && regionName) { - return this.getRegionInStage(stageName, regionName).vars || {}; - } else if (stageName) { - return this.getStage(stageName).vars || {}; - } - return this.environment.vars || {}; - } } module.exports = Service; diff --git a/lib/plugins/aws/index.js b/lib/plugins/aws/index.js index 55e2b1b88..a36003138 100644 --- a/lib/plugins/aws/index.js +++ b/lib/plugins/aws/index.js @@ -89,8 +89,7 @@ class SDK { prefix = 'AWS'; } - const profile = process.env[`${prefix}_PROFILE`] - || this.serverless.service.getVariables(stage).profile; + const profile = process.env[`${prefix}_PROFILE`]; credentials.profile = profile; diff --git a/lib/plugins/aws/lib/validate.js b/lib/plugins/aws/lib/validate.js index 4a81e17b7..f3ab9d19f 100644 --- a/lib/plugins/aws/lib/validate.js +++ b/lib/plugins/aws/lib/validate.js @@ -9,6 +9,8 @@ module.exports = { .Error('This command can only be run inside a service directory'); } + console.log(this.serverless.service.custom) + this.options.stage = this.options.stage || (this.serverless.service.defaults && this.serverless.service.defaults.stage) || 'dev'; @@ -16,10 +18,6 @@ module.exports = { || (this.serverless.service.defaults && this.serverless.service.defaults.region) || 'us-east-1'; - // validate stage / region exists in service - this.serverless.service.getStage(this.options.stage); - this.serverless.service.getRegionInStage(this.options.stage, this.options.region); - return BbPromise.resolve(); }, }; diff --git a/lib/plugins/aws/tests/index.js b/lib/plugins/aws/tests/index.js index 5de061ccc..484b8dcf6 100644 --- a/lib/plugins/aws/tests/index.js +++ b/lib/plugins/aws/tests/index.js @@ -87,46 +87,20 @@ describe('AWS SDK', () => { describe('#getCredentials()', () => { it('should get credentials', () => { const serverless = new Serverless(); - serverless.service.environment = { - vars: { - profile: 'default', - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverless.service.environment.stages.dev.regions['us-east-1'] = { - vars: {}, - }; + process.env.AWS_PROFILE = 'default'; const awsSdk = new AwsSdk(serverless); const credentials = awsSdk.getCredentials(); expect(credentials.profile).to.equal('default'); + delete process.env.AWS_PROFILE; }); it('should get stage credentials', () => { const serverless = new Serverless(); - serverless.service.environment = { - vars: {}, - stages: { - dev: { - vars: { - profile: 'default', - }, - regions: {}, - }, - }, - }; - - serverless.service.environment.stages.dev.regions['us-east-1'] = { - vars: {}, - }; + process.env.AWS_DEV_PROFILE = 'default'; const awsSdk = new AwsSdk(serverless); const credentials = awsSdk.getCredentials('dev'); expect(credentials.profile).to.deep.equal('default'); + delete process.env.AWS_DEV_PROFILE; }); }); diff --git a/lib/plugins/aws/tests/validate.js b/lib/plugins/aws/tests/validate.js index 408b362c1..fd7ec5094 100644 --- a/lib/plugins/aws/tests/validate.js +++ b/lib/plugins/aws/tests/validate.js @@ -15,43 +15,11 @@ describe('#validate()', () => { region: 'us-east-1', }; - awsPlugin.serverless.service.environment = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: { - 'us-east-1': { - vars: {}, - }, - }, - }, - }, - }; - awsPlugin.serverless.config.servicePath = true; Object.assign(awsPlugin, validate); }); - it('should succeed if region exists in service', () => { - expect(() => awsPlugin.validate()).to.not.throw(Error); - }); - - it('should throw error if region does not exist in service', () => { - awsPlugin.options.region = 'us-west-2'; - expect(() => awsPlugin.validate()).to.throw(Error); - }); - - it('should succeed if stage exists in service', () => { - expect(() => awsPlugin.validate()).to.not.throw(Error); - }); - - it('should throw error if stage does not exist in service', () => { - awsPlugin.options.stage = 'prod'; - expect(() => awsPlugin.validate()).to.throw(Error); - }); - it('should succeed if inside service (servicePath defined)', () => { expect(() => awsPlugin.validate()).to.not.throw(Error); }); @@ -76,20 +44,6 @@ describe('#validate()', () => { stage: 'some-stage', }; - awsPlugin.serverless.service.environment = { - vars: {}, - stages: { - 'some-stage': { - vars: {}, - regions: { - 'us-east-1': { - vars: {}, - }, - }, - }, - }, - }; - return awsPlugin.validate().then(() => { expect(awsPlugin.options.stage).to.equal('some-stage'); }); @@ -108,20 +62,6 @@ describe('#validate()', () => { region: 'some-region', }; - awsPlugin.serverless.service.environment = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: { - 'some-region': { - vars: {}, - }, - }, - }, - }, - }; - return awsPlugin.validate().then(() => { expect(awsPlugin.options.region).to.equal('some-region'); }); diff --git a/lib/plugins/create/templates/aws-java-gradle/serverless.env.yml b/lib/plugins/create/templates/aws-java-gradle/serverless.env.yml deleted file mode 100644 index 64a8bc453..000000000 --- a/lib/plugins/create/templates/aws-java-gradle/serverless.env.yml +++ /dev/null @@ -1,15 +0,0 @@ -# This is the Serverless Environment File -# -# It contains listing of your stages, and their regions -# It also manages serverless variables at 3 levels: -# - common variables: variables that apply to all stages/regions -# - stage variables: variables that apply to a specific stage -# - region variables: variables that apply to a specific region - -vars: -stages: - dev: - vars: - regions: - us-east-1: - vars: diff --git a/lib/plugins/create/templates/aws-java-maven/serverless.env.yml b/lib/plugins/create/templates/aws-java-maven/serverless.env.yml deleted file mode 100644 index 64a8bc453..000000000 --- a/lib/plugins/create/templates/aws-java-maven/serverless.env.yml +++ /dev/null @@ -1,15 +0,0 @@ -# This is the Serverless Environment File -# -# It contains listing of your stages, and their regions -# It also manages serverless variables at 3 levels: -# - common variables: variables that apply to all stages/regions -# - stage variables: variables that apply to a specific stage -# - region variables: variables that apply to a specific region - -vars: -stages: - dev: - vars: - regions: - us-east-1: - vars: diff --git a/lib/plugins/create/templates/aws-java-maven/serverless.yml b/lib/plugins/create/templates/aws-java-maven/serverless.yml index babf43800..0cb0ffd63 100644 --- a/lib/plugins/create/templates/aws-java-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-java-maven/serverless.yml @@ -49,13 +49,13 @@ functions: handler: hello.Handler # you can add any of the following events -# events: -# - http: -# path: users/create -# method: get -# - s3: ${bucket} -# - schedule: rate(10 minutes) -# - sns: greeter-topic +# events: +# - http: +# path: users/create +# method: get +# - s3: ${env.BUCKET} +# - schedule: rate(10 minutes) +# - sns: greeter-topic # you can add CloudFormation resource templates here #resources: diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.env.yml b/lib/plugins/create/templates/aws-nodejs/serverless.env.yml deleted file mode 100644 index 38baba6a5..000000000 --- a/lib/plugins/create/templates/aws-nodejs/serverless.env.yml +++ /dev/null @@ -1,15 +0,0 @@ -# This is the Serverless Environment File -# -# It contains listing of your stages and their regions -# It also manages serverless variables at 3 levels: -# - common variables: variables that apply to all stages/regions -# - stage variables: variables that apply to a specific stage -# - region variables: variables that apply to a specific region - -vars: -stages: - dev: - vars: - regions: - us-east-1: - vars: diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.yml b/lib/plugins/create/templates/aws-nodejs/serverless.yml index ceae041f3..4cd1e8360 100644 --- a/lib/plugins/create/templates/aws-nodejs/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs/serverless.yml @@ -48,14 +48,14 @@ functions: hello: handler: handler.hello -# you can add any of the following events -# events: -# - http: -# path: users/create -# method: get -# - s3: ${bucket} -# - schedule: rate(10 minutes) -# - sns: greeter-topic +# you can add any of the following events +# events: +# - http: +# path: users/create +# method: get +# - s3: ${env.BUCKET} +# - schedule: rate(10 minutes) +# - sns: greeter-topic # you can add CloudFormation resource templates here #resources: diff --git a/lib/plugins/create/templates/aws-python/serverless.env.yml b/lib/plugins/create/templates/aws-python/serverless.env.yml deleted file mode 100644 index 64a8bc453..000000000 --- a/lib/plugins/create/templates/aws-python/serverless.env.yml +++ /dev/null @@ -1,15 +0,0 @@ -# This is the Serverless Environment File -# -# It contains listing of your stages, and their regions -# It also manages serverless variables at 3 levels: -# - common variables: variables that apply to all stages/regions -# - stage variables: variables that apply to a specific stage -# - region variables: variables that apply to a specific region - -vars: -stages: - dev: - vars: - regions: - us-east-1: - vars: diff --git a/lib/plugins/create/templates/aws-python/serverless.yml b/lib/plugins/create/templates/aws-python/serverless.yml index 80903a394..d9a915bf5 100644 --- a/lib/plugins/create/templates/aws-python/serverless.yml +++ b/lib/plugins/create/templates/aws-python/serverless.yml @@ -49,13 +49,13 @@ functions: handler: handler.hello # you can add any of the following events -# events: -# - http: -# path: users/create -# method: get -# - s3: ${bucket} -# - schedule: rate(10 minutes) -# - sns: greeter-topic +# events: +# - http: +# path: users/create +# method: get +# - s3: ${env.BUCKET} +# - schedule: rate(10 minutes) +# - sns: greeter-topic # you can add CloudFormation resource templates here #resources: diff --git a/lib/plugins/create/tests/create.js b/lib/plugins/create/tests/create.js index bb3a8129f..b25949761 100644 --- a/lib/plugins/create/tests/create.js +++ b/lib/plugins/create/tests/create.js @@ -74,8 +74,6 @@ describe('Create', () => { return create.create().then(() => { expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.yml'))) .to.be.equal(true); - expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.env.yml'))) - .to.be.equal(true); expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'handler.js'))) .to.be.equal(true); @@ -92,8 +90,6 @@ describe('Create', () => { return create.create().then(() => { expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.yml'))) .to.be.equal(true); - expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.env.yml'))) - .to.be.equal(true); expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'handler.py'))) .to.be.equal(true); @@ -110,8 +106,6 @@ describe('Create', () => { return create.create().then(() => { expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.yml'))) .to.be.equal(true); - expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.env.yml'))) - .to.be.equal(true); expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'event.json'))) .to.be.equal(true); expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'pom.xml'))) @@ -142,8 +136,6 @@ describe('Create', () => { return create.create().then(() => { expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.yml'))) .to.be.equal(true); - expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.env.yml'))) - .to.be.equal(true); expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'event.json'))) .to.be.equal(true); expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'build.gradle'))) diff --git a/tests/classes/PluginManager.js b/tests/classes/PluginManager.js index 8f89b0209..64b5de0d1 100644 --- a/tests/classes/PluginManager.js +++ b/tests/classes/PluginManager.js @@ -688,8 +688,6 @@ describe('PluginManager', () => { expect(serverlessInstance.utils .fileExistsSync(path.join(tmpDir, 'serverless.yml'))).to.equal(true); - expect(serverlessInstance.utils - .fileExistsSync(path.join(tmpDir, 'serverless.env.yml'))).to.equal(true); expect(serverlessInstance.utils .fileExistsSync(path.join(tmpDir, 'handler.js'))).to.equal(true); diff --git a/tests/classes/Service.js b/tests/classes/Service.js index 53f3d6243..d01cc0e44 100644 --- a/tests/classes/Service.js +++ b/tests/classes/Service.js @@ -1,12 +1,12 @@ 'use strict'; const path = require('path'); +const os = require('os'); const YAML = require('js-yaml'); const expect = require('chai').expect; const Service = require('../../lib/classes/Service'); const Utils = require('../../lib/classes/Utils'); const Serverless = require('../../lib/Serverless'); -const testUtils = require('../../tests/utils'); describe('Service', () => { describe('#constructor()', () => { @@ -25,12 +25,11 @@ describe('Service', () => { expect(serviceInstance.defaults).to.deep.equal({ stage: 'dev', region: 'us-east-1', - variableSyntax: null, + variableSyntax: '\\${([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}', }); expect(serviceInstance.custom).to.deep.equal({}); expect(serviceInstance.plugins).to.deep.equal([]); expect(serviceInstance.functions).to.deep.equal({}); - expect(serviceInstance.environment).to.deep.equal({}); expect(serviceInstance.resources).to.deep.equal({}); expect(serviceInstance.package).to.deep.equal({}); }); @@ -67,7 +66,6 @@ describe('Service', () => { expect(serviceInstance.custom).to.deep.equal({ customProp: 'value' }); expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); expect(serviceInstance.functions).to.deep.equal({ functionA: {} }); - expect(serviceInstance.environment).to.deep.equal({}); expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); expect(serviceInstance.resources.azure).to.deep.equal({}); expect(serviceInstance.resources.google).to.deep.equal({}); @@ -103,123 +101,24 @@ describe('Service', () => { describe('#load()', () => { let serviceInstance; - let tmpDirPath; - beforeEach(() => { - tmpDirPath = testUtils.getTmpDirPath(); - }); - - it('should resolve if no servicePath is found', (done) => { + it('should resolve if no servicePath is found', () => { const serverless = new Serverless(); const noService = new Service(serverless); - noService.load().then(() => done()); + return noService.load(); }); - it('should support Serverless files with a .yaml extension', () => { - const SUtils = new Utils(); - const serverlessYaml = { - service: 'my-service', - provider: 'aws', - functions: { - functionA: {}, - }, - }; - const serverlessEnvYaml = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: { - 'us-east-1': { - vars: {}, - }, - }, - }, - }, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), - YAML.dump(serverlessYaml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yaml'), - YAML.dump(serverlessEnvYaml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load().then((loadedService) => { - const expectedFunc = { - functionA: { - name: 'my-service-dev-functionA', - events: [], - }, - }; - expect(loadedService.service).to.be.equal('my-service'); - expect(loadedService.provider).to.deep.equal({ name: 'aws' }); - expect(loadedService.functions).to.deep.equal(expectedFunc); - expect(serviceInstance.environment.stages.dev.regions['us-east-1'].vars) - .to.deep.equal({}); - }); - }); - - it('should support Serverless files with a .yml extension', () => { + it('should load from filesystem', () => { const SUtils = new Utils(); + const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString()); const serverlessYml = { - service: 'my-service', - provider: 'aws', - functions: { - functionA: {}, - }, - }; - const serverlessEnvYml = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: { - 'us-east-1': { - vars: {}, - }, - }, - }, - }, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load().then((loadedService) => { - const expectedFunc = { - functionA: { - name: 'my-service-dev-functionA', - events: [], - }, - }; - expect(loadedService.service).to.be.equal('my-service'); - expect(loadedService.provider).to.deep.equal({ name: 'aws' }); - expect(loadedService.functions).to.deep.equal(expectedFunc); - expect(serviceInstance.environment.stages.dev.regions['us-east-1'].vars) - .to.deep.equal({}); - }); - }); - - it('should load and populate from filesystem', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: '${testVar}', + service: 'new-service', provider: 'aws', defaults: { stage: 'dev', region: 'us-east-1', - }, - custom: { - digit: '${testDigit}', - substring: 'Hello ${testSubstring}', + variableSyntax: '\\${{([\\s\\S]+?)}}', }, plugins: ['testPlugin'], functions: { @@ -238,535 +137,102 @@ describe('Service', () => { artifact: 'some/path/foo.zip', }, }; - const serverlessEnvYml = { - vars: { - testVar: 'commonVar', - testDigit: 10, - testSubstring: 'World', - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - const serverless = new Serverless({ servicePath: tmpDirPath }); + const serverless = new Serverless(); + serverless.init(); + serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); - const commonVars = { - testVar: 'commonVar', - testDigit: 10, - testSubstring: 'World', - }; - return serviceInstance.load().then((loadedService) => { - expect(loadedService.service).to.be.equal('commonVar'); - expect(loadedService.provider).to.deep.equal({ name: 'aws' }); - expect(loadedService.plugins).to.deep.equal(['testPlugin']); - expect(loadedService.environment.vars).to.deep.equal(commonVars); - expect(serviceInstance.environment.stages.dev.regions['us-east-1'].vars) - .to.deep.equal({}); - expect(loadedService.resources.aws).to.deep.equal({ resourcesProp: 'value' }); - expect(loadedService.resources.azure).to.deep.equal({}); - expect(loadedService.resources.google).to.deep.equal({}); - expect(loadedService.package.include.length).to.equal(1); - expect(loadedService.package.include[0]).to.equal('include-me.js'); - expect(loadedService.package.exclude.length).to.equal(1); - expect(loadedService.package.exclude[0]).to.equal('exclude-me.js'); - expect(loadedService.package.artifact).to.equal('some/path/foo.zip'); + return serviceInstance.load().then(() => { + expect(serviceInstance.service).to.be.equal('new-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.defaults.variableSyntax).to.equal('\\${{([\\s\\S]+?)}}'); + expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); + expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); + expect(serviceInstance.resources.azure).to.deep.equal({}); + expect(serviceInstance.resources.google).to.deep.equal({}); + expect(serviceInstance.package.include.length).to.equal(1); + expect(serviceInstance.package.include[0]).to.equal('include-me.js'); + expect(serviceInstance.package.exclude.length).to.equal(1); + expect(serviceInstance.package.exclude[0]).to.equal('exclude-me.js'); + expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); }); }); - it('should load and populate stage vars', () => { + it('should support Serverless file with a .yaml extension', () => { const SUtils = new Utils(); - const serverlessYml = { - service: '${testVar}', - provider: 'aws', - functions: {}, - }; - const serverlessEnvYml = { - vars: { - testVar: 'commonVar', - }, - stages: { - dev: { - vars: { - testVar: 'stageVar', - }, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - return serviceInstance.load().then((loadedService) => { - expect(loadedService.service).to.be.equal('stageVar'); - }); - }); - - it('should load and populate region vars', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: '${testVar}', - provider: 'aws', - plugins: ['testPlugin'], - functions: {}, - }; - const serverlessEnvYml = { - vars: { - testVar: 'commonVar', - }, - stages: { - dev: { - vars: { - testVar: 'stageVar', - }, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: { - testVar: 'regionVar', - }, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load().then((loadedService) => { - expect(loadedService.service).to.be.equal('regionVar'); - }); - }); - - it('should load and populate region vars when region is provided as shortcut', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: '${testVar}', - provider: 'aws', - plugins: ['testPlugin'], - functions: {}, - }; - const serverlessEnvYml = { - vars: { - testVar: 'commonVar', - }, - stages: { - dev: { - vars: { - testVar: 'stageVar', - }, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-west-2'] = { - vars: { - testVar: 'westRegionVar', - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: { - testVar: 'eastRegionVar', - }, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load({ r: 'us-west-2' }).then((loadedService) => { - expect(loadedService.service).to.be.equal('westRegionVar'); - }); - }); - - it('should load and populate non string variables', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'service-name', - provider: 'aws', - custom: { - digit: '${testDigit}', - }, - functions: {}, - }; - const serverlessEnvYml = { - vars: { - testDigit: 10, - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load() - .then((loadedService) => { - expect(loadedService.custom.digit).to.be.equal(10); - }); - }); - - it('should load and populate object variables', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'service-name', - provider: 'aws', - custom: { - object: '${testObject}', - }, - functions: {}, - }; - const serverlessEnvYml = { - vars: { - testObject: { - subProperty: 'test', - }, - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load() - .then((loadedService) => { - expect(loadedService.custom.object).to.deep.equal({ subProperty: 'test' }); - }); - }); - - it('should load and populate boolean and 0 variables', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'service-name', - provider: 'aws', - custom: { - boolean: '${booleanValue}', - zero: '${zeroValue}', - }, - functions: {}, - }; - const serverlessEnvYml = { - vars: { - booleanValue: false, - zeroValue: 0, - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load() - .then((loadedService) => { - expect(loadedService.custom.boolean).to.equal(false); - expect(loadedService.custom.zero).to.equal(0); - }); - }); - - it('should load and populate object variables deep sub properties', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'service-name', - provider: 'aws', - custom: { - object: '${testObject.subProperty.deepSubProperty}', - }, - functions: {}, - }; - const serverlessEnvYml = { - vars: { - testObject: { - subProperty: { - deepSubProperty: 'test', - }, - }, - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load() - .then((loadedService) => { - expect(loadedService.custom.object).to.be.equal('test'); - }); - }); - - it('should load and populate substring variables', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'service-name', - provider: 'aws', - custom: { - substring: 'Hello ${testSubstring.subProperty.deepSubProperty}', - }, - functions: {}, - }; - const serverlessEnvYml = { - vars: { - testSubstring: { - subProperty: { - deepSubProperty: 'World', - }, - }, - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - return serviceInstance.load() - .then((loadedService) => { - expect(loadedService.custom.substring).to.be.equal('Hello World'); - }); - }); - - it('should load and populate with custom variable syntax', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: '${{testVar}}', - defaults: { - variableSyntax: '\\${{([\\s\\S]+?)}}', - }, - provider: 'aws', - functions: {}, - }; - const serverlessEnvYml = { - vars: { - testVar: 'commonVar', - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load().then((loadedService) => { - expect(loadedService.service).to.be.equal('commonVar'); - delete serviceInstance.defaults.variableSyntax; - }); - }); - - it('should load custom function names if provided', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'testService', + const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString()); + const serverlessYaml = { + service: 'my-service', provider: 'aws', functions: { functionA: { - name: 'customName', - }, - }, - }; - const serverlessEnvYml = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: {}, + name: 'customFunctionName', }, }, }; - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), + YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); - return serviceInstance.load().then((loadedService) => { - expect(loadedService.functions.functionA.name).to.equal('customName'); + + return serviceInstance.load().then(() => { + const expectedFunc = { + functionA: { + name: 'customFunctionName', + events: [], + }, + }; + expect(serviceInstance.service).to.be.equal('my-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.functions).to.deep.equal(expectedFunc); }); }); - it('should load and add events property if no events provided', () => { + it('should support Serverless file with a .yml extension', () => { const SUtils = new Utils(); + const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString()); const serverlessYml = { - service: 'testService', + service: 'my-service', provider: 'aws', - runtime: 'nodejs4.3', functions: { functionA: {}, }, }; - const serverlessEnvYml = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); - return serviceInstance.load().then((loadedService) => { + + return serviceInstance.load({ stage: 'dev' }).then(() => { const expectedFunc = { functionA: { - name: 'testService-dev-functionA', + name: 'my-service-dev-functionA', events: [], }, }; - expect(loadedService.functions).to.be.deep.equal(expectedFunc); + expect(serviceInstance.service).to.be.equal('my-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.functions).to.deep.equal(expectedFunc); }); }); it('should throw error if service property is missing', () => { const SUtils = new Utils(); + const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString()); const serverlessYml = { provider: 'aws', functions: {}, }; - const serverlessEnvYml = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -782,66 +248,13 @@ describe('Service', () => { it('should throw error if provider property is missing', () => { const SUtils = new Utils(); + const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString()); const serverlessYml = { service: 'service-name', functions: {}, }; - const serverlessEnvYml = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load().then(() => { - // if we reach this, then no error was thrown as expected - // so make assertion fail intentionally to let us know something is wrong - expect(1).to.equal(2); - }).catch(e => { - expect(e.name).to.be.equal('ServerlessError'); - }); - }); - - it('should throw error if provider property is invalid', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'service-name', - provider: 'invalid', - functions: {}, - }; - const serverlessEnvYml = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -857,67 +270,13 @@ describe('Service', () => { it('should throw error if functions property is missing', () => { const SUtils = new Utils(); + const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString()); const serverlessYml = { service: 'service-name', provider: 'aws', }; - const serverlessEnvYml = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load().then(loadedService => { - expect(loadedService.functions).to.equal(true); - }).catch(e => { - expect(e.name).to.be.equal('ServerlessError'); - }); - }); - - it('should throw error if variable does not exist', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'service-name', - provider: 'aws', - custom: { - object: '${testVar}', - }, - functions: {}, - }; - const serverlessEnvYml = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -931,214 +290,17 @@ describe('Service', () => { }); }); - it('should throw error if we try to access sub property of string variable', () => { + it('should throw error if provider property is invalid', () => { const SUtils = new Utils(); + const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString()); const serverlessYml = { service: 'service-name', - provider: 'aws', - custom: { - testVar: '${testVar.subProperty}', - }, + provider: 'invalid', functions: {}, }; - const serverlessEnvYml = { - vars: { - testVar: 'test', - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load().then(() => { - // if we reach this, then no error was thrown as expected - // so make assertion fail intentionally to let us know something is wrong - expect(1).to.equal(2); - }).catch(e => { - expect(e.name).to.be.equal('ServerlessError'); - }); - }); - - it('should throw error if we try to access sub property of non-object variable', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'service-name', - provider: 'aws', - custom: { - testVar: '${testVar.subProperty}', - }, - functions: {}, - }; - const serverlessEnvYml = { - vars: { - testVar: 10, - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load().then(() => { - // if we reach this, then no error was thrown as expected - // so make assertion fail intentionally to let us know something is wrong - expect(1).to.equal(2); - }).catch(e => { - expect(e.name).to.be.equal('ServerlessError'); - }); - }); - - it('should throw error if sub property does not exist in object at any level', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'service-name', - provider: 'aws', - custom: { - testObject: '${testObject.subProperty.deepSubProperty}', - }, - functions: {}, - }; - const serverlessEnvYml = { - vars: { - testObject: { - subProperty: 'string', - }, - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load().then(() => { - // if we reach this, then no error was thrown as expected - // so make assertion fail intentionally to let us know something is wrong - expect(1).to.equal(2); - }).catch(e => { - expect(e.name).to.be.equal('ServerlessError'); - }); - }); - - it('should throw error if trying to populate non string vars into string', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'service-name', - provider: 'aws', - custom: { - testVar: '${testVar} String', - }, - functions: {}, - }; - const serverlessEnvYml = { - vars: { - testVar: 10, - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); - - const serverless = new Serverless({ servicePath: tmpDirPath }); - serviceInstance = new Service(serverless); - - return serviceInstance.load().then(() => { - // if we reach this, then no error was thrown as expected - // so make assertion fail intentionally to let us know something is wrong - expect(1).to.equal(2); - }).catch(e => { - expect(e.name).to.be.equal('ServerlessError'); - }); - }); - - it('should throw error if trying to populate non string deep vars into string', () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'service-name', - provider: 'aws', - custom: { - testObject: '${testObject.subProperty} String', - }, - functions: {}, - }; - const serverlessEnvYml = { - vars: { - testObject: { - subProperty: { - deepSubProperty: 'string', - }, - }, - }, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serverlessEnvYml.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yml'), - YAML.dump(serverlessEnvYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -1252,176 +414,4 @@ describe('Service', () => { .to.deep.equal(['schedule', 'bucket']); }); }); - - describe('#getStage()', () => { - let serviceInstance; - before(() => { - const serverless = new Serverless(); - serviceInstance = new Service(serverless); - serviceInstance.environment = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serviceInstance.environment.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - }); - - it('should return stage object', () => { - const expectedStageObj = { - vars: {}, - regions: {}, - }; - expectedStageObj.regions['us-east-1'] = { - vars: {}, - }; - expect(serviceInstance.getStage('dev')).to.deep.equal(expectedStageObj); - }); - - it('should throw error if stage does not exist', () => { - expect(() => { - serviceInstance.getStage('prod'); - }).to.throw(Error); - }); - }); - - describe('#getAllStages()', () => { - it('should return an array of stage names in Service', () => { - const serverless = new Serverless(); - const serviceInstance = new Service(serverless); - serviceInstance.environment = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serviceInstance.environment.stages.dev.regions['us-east-1'] = { - vars: {}, - }; - - expect(serviceInstance.getAllStages()).to.deep.equal(['dev']); - }); - }); - - - describe('#getRegionInStage()', () => { - let serviceInstance; - before(() => { - const serverless = new Serverless(); - serviceInstance = new Service(serverless); - serviceInstance.environment = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serviceInstance.environment.stages.dev.regions['us-east-1'] = { - vars: { - regionVar: 'regionValue', - }, - }; - }); - - it('should return a region object based on provided stage', () => { - expect(serviceInstance.getRegionInStage('dev', 'us-east-1') - .vars.regionVar).to.be.equal('regionValue'); - }); - - it('should throw error if stage does not exist in service', () => { - expect(() => { - serviceInstance.getRegionInStage('prod'); - }).to.throw(Error); - }); - - it('should throw error if region doesnt exist in stage', () => { - expect(() => { - serviceInstance.getRegionInStage('dev', 'us-west-2'); - }).to.throw(Error); - }); - }); - - describe('#getAllRegionsInStage()', () => { - it('should return an array of regions in a specified stage', () => { - const serverless = new Serverless(); - const serviceInstance = new Service(serverless); - serviceInstance.environment = { - vars: {}, - stages: { - dev: { - vars: {}, - regions: {}, - }, - }, - }; - - serviceInstance.environment.stages.dev.regions['us-east-1'] = { - vars: { - regionVar: 'regionValue', - }, - }; - - serviceInstance.environment.stages.dev.regions['us-west-2'] = { - vars: { - regionVar: 'regionValue', - }, - }; - - expect(serviceInstance.getAllRegionsInStage('dev')) - .to.deep.equal(['us-east-1', 'us-west-2']); - }); - }); - - describe('#getVariables()', () => { - let serviceInstance; - before(() => { - const serverless = new Serverless(); - serviceInstance = new Service(serverless); - serviceInstance.environment = { - vars: { - commonVar: 'commonValue', - }, - stages: { - dev: { - vars: { - stageVar: 'stageValue', - }, - regions: {}, - }, - }, - }; - - serviceInstance.environment.stages.dev.regions['us-east-1'] = { - vars: { - regionVar: 'regionValue', - }, - }; - }); - - it('should return common variables if no stage/region provided', () => { - expect(serviceInstance.getVariables().commonVar).to.be.equal('commonValue'); - }); - - it('should return stage variables if no region provided', () => { - expect(serviceInstance.getVariables('dev').stageVar).to.be.equal('stageValue'); - }); - - it('should return region variables if both stage and region provided', () => { - expect(serviceInstance.getVariables('dev', 'us-east-1').regionVar) - .to.be.equal('regionValue'); - }); - }); });