serverless/lib/classes/Variables.js

192 lines
6.3 KiB
JavaScript

'use strict';
const _ = require('lodash');
const path = require('path');
const traverse = require('traverse');
const replaceall = require('replaceall');
class Variables {
constructor(serverless) {
this.serverless = serverless;
this.service = this.serverless.service;
this.overwriteSyntax = RegExp(/,/g);
this.fileRefSyntax = RegExp(/^file\(([a-zA-Z0-9._\-\/]+?)\)/g);
this.envRefSyntax = RegExp(/^env:./g);
this.optRefSyntax = RegExp(/^opt:./g);
this.selfRefSyntax = RegExp(/^self:./g);
}
loadVariableSyntax() {
this.variableSyntax = RegExp(this.service.defaults.variableSyntax, 'g');
}
populateService(processedOptions) {
const that = this;
this.options = processedOptions || {};
this.loadVariableSyntax();
const variableSyntaxProperty = this.service.defaults.variableSyntax;
// temporally remove variable syntax from service otherwise it'll match
this.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.service).forEach(function (propertyParam) {
const t = this;
let property = propertyParam;
if (typeof property === 'string') {
property = that.populateProperty(property);
t.update(property);
}
});
this.service.defaults.variableSyntax = variableSyntaxProperty;
return this.service;
}
populateProperty(propertyParam) {
let property = propertyParam;
let valueToPopulate;
if (typeof property === 'string' && property.match(this.variableSyntax)) {
property.match(this.variableSyntax).forEach((matchedString) => {
const variableString = matchedString
.replace(this.variableSyntax, (match, varName) => varName.trim())
.replace(/\s/g, '');
if (variableString.match(this.overwriteSyntax)) {
valueToPopulate = this.overwrite(variableString);
} else {
valueToPopulate = this.getValueFromSource(variableString);
}
property = this.populateVariable(property, matchedString, valueToPopulate);
});
return this.populateProperty(property);
}
return property;
}
populateVariable(propertyParam, matchedString, valueToPopulate) {
let property = propertyParam;
if (typeof valueToPopulate === 'string') {
property = replaceall(matchedString, valueToPopulate, property);
} else {
if (property !== matchedString) {
const errorMessage = [
'Trying to populate non string value into',
` a string for variable ${matchedString}.`,
' Please make sure the value of the property is a string.',
].join('');
throw new this.serverless.classes
.Error(errorMessage);
}
property = valueToPopulate;
}
return property;
}
overwrite(variableStringsString) {
let finalValue;
const variableStringsArray = variableStringsString.split(',');
variableStringsArray.find(variableString => {
finalValue = this.getValueFromSource(variableString);
return (typeof finalValue !== 'undefined' && finalValue !== null);
});
return finalValue;
}
getValueFromSource(variableString) {
let valueToPopulate;
if (variableString.match(this.envRefSyntax)) {
valueToPopulate = this.getValueFromEnv(variableString);
} else if (variableString.match(this.optRefSyntax)) {
valueToPopulate = this.getValueFromOptions(variableString);
} else if (variableString.match(this.selfRefSyntax)) {
valueToPopulate = this.getValueFromSelf(variableString);
} else if (variableString.match(this.fileRefSyntax)) {
valueToPopulate = this.getValueFromFile(variableString);
} else {
const errorMessage = [
`Invalid variable reference syntax for variable ${variableString}.`,
' You can only reference env vars, options, & files.',
' You can check our docs for more info.',
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
return valueToPopulate;
}
getValueFromEnv(variableString) {
const requestedEnvVar = variableString.split(':')[1];
const valueToPopulate = process.env[requestedEnvVar];
return valueToPopulate;
}
getValueFromOptions(variableString) {
const requestedOption = variableString.split(':')[1];
const valueToPopulate = this.options[requestedOption];
return valueToPopulate;
}
getValueFromSelf(variableString) {
let valueToPopulate = _.cloneDeep(this.service);
const deepProperties = variableString.split(':')[1].split('.');
valueToPopulate = this.getDeepValue(deepProperties, valueToPopulate);
return valueToPopulate;
}
getValueFromFile(variableString) {
const matchedFileRefString = variableString.match(this.fileRefSyntax)[0];
const referencedFileRelativePath = matchedFileRefString
.replace(this.fileRefSyntax, (match, varName) => varName.trim());
const referencedFileFullPath = path.join(this.serverless.config.servicePath,
referencedFileRelativePath);
let valueToPopulate = this.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}" sub properties`,
' Please use ":" or "." to reference sub properties.',
].join('');
throw new this.serverless.classes
.Error(errorMessage);
}
deepProperties = deepProperties.slice(1).split('.');
valueToPopulate = this.getDeepValue(deepProperties, valueToPopulate);
}
return valueToPopulate;
}
getDeepValue(deepProperties, valueToPopulateParam) {
let valueToPopulate = valueToPopulateParam;
deepProperties.forEach(subProperty => {
if (typeof valueToPopulate === 'undefined') {
valueToPopulate = {};
} else {
valueToPopulate = valueToPopulate[subProperty];
}
if (typeof valueToPopulate === 'string' && valueToPopulate.match(this.variableSyntax)) {
valueToPopulate = this.populateProperty(valueToPopulate);
}
});
return valueToPopulate;
}
}
module.exports = Variables;