Dynamic datasources.json from ENV and config.json
Let environment variables override configuration set by config.json and/or app.set() Behavior changes - datasources.json now support dynamic configuration through env-vars and config.json - component-config.json will first consider env-var for resolving dynamic conf, then fallback to config.json - middleware.json will first consider env-var for resolving dynamic conf, then fallback to config.json - for all the dynamic confg, unresolved conf will return as `undefined` Example: Consider the following server/datasources.json ``` { "mysql" : { "name" : "mysql_db", "host" : "${MYSQL_DB_HOST}", ... } } ``` Now you can provide the parameter through an environment variable: ``` $ MYSQL_DB_HOST=127.0.0.1 node . ``` or you can set the value in server/config.json ``` { "MYSQL_DB_HOST": "127.0.0.1" } ```
This commit is contained in:
parent
b3e5f23865
commit
4a815deb27
|
@ -171,6 +171,10 @@ function applyAppConfig(app, instructions) {
|
|||
|
||||
function setupDataSources(app, instructions) {
|
||||
forEachKeyedObject(instructions.dataSources, function(key, obj) {
|
||||
var opts = {
|
||||
useEnvVars: true,
|
||||
};
|
||||
obj = getUpdatedConfigObject(app, obj, opts);
|
||||
app.dataSource(key, obj);
|
||||
});
|
||||
}
|
||||
|
@ -330,24 +334,42 @@ function setupMiddleware(app, instructions) {
|
|||
}
|
||||
assert(typeof factory === 'function',
|
||||
'Middleware factory must be a function');
|
||||
data.config = getUpdatedConfigObject(app, data.config);
|
||||
var opts = {
|
||||
useEnvVars: true,
|
||||
};
|
||||
data.config = getUpdatedConfigObject(app, data.config, opts);
|
||||
app.middlewareFromConfig(factory, data.config);
|
||||
});
|
||||
}
|
||||
|
||||
function getUpdatedConfigObject(app, config) {
|
||||
function getUpdatedConfigObject(app, config, opts) {
|
||||
var DYNAMIC_CONFIG_PARAM = /\$\{(\w+)\}$/;
|
||||
var useEnvVars = opts && opts.useEnvVars;
|
||||
|
||||
function getConfigVariable(param) {
|
||||
var configVariable = param;
|
||||
var match = configVariable.match(DYNAMIC_CONFIG_PARAM);
|
||||
if (match) {
|
||||
var appValue = app.get(match[1]);
|
||||
if (appValue !== undefined) {
|
||||
var varName = match[1];
|
||||
if (useEnvVars && process.env[varName] !== undefined) {
|
||||
debug('Dynamic Configuration: Resolved via process.env: %s as %s',
|
||||
process.env[varName], param);
|
||||
configVariable = process.env[varName];
|
||||
} else if (app.get(varName) !== undefined) {
|
||||
debug('Dynamic Configuration: Resolved via app.get(): %s as %s',
|
||||
app.get(varName), param);
|
||||
var appValue = app.get(varName);
|
||||
configVariable = appValue;
|
||||
} else {
|
||||
console.warn('%s does not resolve to a valid value. ' +
|
||||
'"%s" must be resolvable by app.get().', param, match[1]);
|
||||
// previously it returns the original string such as "${restApiRoot}"
|
||||
// it will now return `undefined`, for the use case of
|
||||
// dynamic datasources url:`undefined` to fallback to other parameters
|
||||
configVariable = undefined;
|
||||
console.warn('%s does not resolve to a valid value, returned as %s. ' +
|
||||
'"%s" must be resolvable in Environment variable or by app.get().',
|
||||
param, configVariable, varName);
|
||||
debug('Dynamic Configuration: Cannot resolve variable for `%s`, ' +
|
||||
'returned as %s', varName, configVariable);
|
||||
}
|
||||
}
|
||||
return configVariable;
|
||||
|
@ -394,7 +416,10 @@ function setupComponents(app, instructions) {
|
|||
instructions.components.forEach(function(data) {
|
||||
debug('Configuring component %j', data.sourceFile);
|
||||
var configFn = require(data.sourceFile);
|
||||
data.config = getUpdatedConfigObject(app, data.config);
|
||||
var opts = {
|
||||
useEnvVars: true,
|
||||
};
|
||||
data.config = getUpdatedConfigObject(app, data.config, opts);
|
||||
configFn(app, data.config);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -442,6 +442,10 @@ describe('executor', function() {
|
|||
});
|
||||
|
||||
describe('with middleware.json', function() {
|
||||
beforeEach(function() {
|
||||
delete process.env.restApiRoot;
|
||||
});
|
||||
|
||||
it('should parse a simple config variable', function(done) {
|
||||
boot.execute(app, simpleMiddlewareConfig('routes',
|
||||
{ path: '${restApiRoot}' }
|
||||
|
@ -454,6 +458,36 @@ describe('executor', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('should parse simple config variable from env var', function(done) {
|
||||
process.env.restApiRoot = '/url-from-env-var';
|
||||
boot.execute(app, simpleMiddlewareConfig('routes',
|
||||
{ path: '${restApiRoot}' }
|
||||
));
|
||||
|
||||
supertest(app).get('/url-from-env-var').end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
expect(res.body.path).to.equal('/url-from-env-var');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dynamic variable from `env var` should have' +
|
||||
' precedence over app.get()', function(done) {
|
||||
process.env.restApiRoot = '/url-from-env-var';
|
||||
var bootInstructions;
|
||||
bootInstructions = simpleMiddlewareConfig('routes',
|
||||
{ path: '${restApiRoot}' });
|
||||
bootInstructions.config = { restApiRoot: '/url-from-config' };
|
||||
boot.execute(app, someInstructions(bootInstructions));
|
||||
|
||||
supertest(app).get('/url-from-env-var').end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
expect(app.get('restApiRoot')).to.equal('/url-from-config');
|
||||
expect(res.body.path).to.equal('/url-from-env-var');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse multiple config variables', function(done) {
|
||||
boot.execute(app, simpleMiddlewareConfig('routes',
|
||||
{ path: '${restApiRoot}', env: '${env}' }
|
||||
|
@ -556,6 +590,11 @@ describe('executor', function() {
|
|||
});
|
||||
|
||||
describe('with component-config.json', function() {
|
||||
beforeEach(function() {
|
||||
delete process.env.DYNAMIC_ENVVAR;
|
||||
delete process.env.DYNAMIC_VARIABLE;
|
||||
});
|
||||
|
||||
it('should parse a simple config variable', function(done) {
|
||||
boot.execute(app, simpleComponentConfig(
|
||||
{ path: '${restApiRoot}' }
|
||||
|
@ -568,6 +607,46 @@ describe('executor', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('should parse config from `env-var` and `config`', function(done) {
|
||||
var bootInstructions = simpleComponentConfig(
|
||||
{
|
||||
path: '${restApiRoot}',
|
||||
fromConfig: '${DYNAMIC_CONFIG}',
|
||||
fromEnvVar: '${DYNAMIC_ENVVAR}',
|
||||
}
|
||||
);
|
||||
|
||||
// result should get value from config.json
|
||||
bootInstructions.config['DYNAMIC_CONFIG'] = 'FOOBAR-CONFIG';
|
||||
// result should get value from env var
|
||||
process.env.DYNAMIC_ENVVAR = 'FOOBAR-ENVVAR';
|
||||
|
||||
boot.execute(app, bootInstructions);
|
||||
supertest(app).get('/component').end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
expect(res.body.fromConfig).to.equal('FOOBAR-CONFIG');
|
||||
expect(res.body.fromEnvVar).to.equal('FOOBAR-ENVVAR');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('`env-var` should have precedence over `config`', function(done) {
|
||||
var key = 'DYNAMIC_VARIABLE';
|
||||
var bootInstructions = simpleComponentConfig({
|
||||
path: '${restApiRoot}',
|
||||
isDynamic: '${' + key + '}',
|
||||
});
|
||||
bootInstructions.config[key] = 'should be overwritten';
|
||||
process.env[key] = 'successfully overwritten';
|
||||
|
||||
boot.execute(app, bootInstructions);
|
||||
supertest(app).get('/component').end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
expect(res.body.isDynamic).to.equal('successfully overwritten');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse multiple config variables', function(done) {
|
||||
boot.execute(app, simpleComponentConfig(
|
||||
{ path: '${restApiRoot}', env: '${env}' }
|
||||
|
@ -809,6 +888,77 @@ describe('executor', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dynamic configuration for datasources.json', function() {
|
||||
beforeEach(function() {
|
||||
delete process.env.DYNAMIC_HOST;
|
||||
delete process.env.DYNAMIC_PORT;
|
||||
});
|
||||
|
||||
it('should convert dynamic variable for datasource', function(done) {
|
||||
var datasource = {
|
||||
mydb: {
|
||||
host: '${DYNAMIC_HOST}',
|
||||
port: '${DYNAMIC_PORT}',
|
||||
},
|
||||
};
|
||||
var bootInstructions = { dataSources: datasource };
|
||||
|
||||
process.env.DYNAMIC_PORT = '10007';
|
||||
process.env.DYNAMIC_HOST = '123.321.123.132';
|
||||
|
||||
boot.execute(app, someInstructions(bootInstructions), function() {
|
||||
expect(app.datasources.mydb.settings.host).to.equal('123.321.123.132');
|
||||
expect(app.datasources.mydb.settings.port).to.equal('10007');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve dynamic config via app.get()', function(done) {
|
||||
var datasource = {
|
||||
mydb: { host: '${DYNAMIC_HOST}' },
|
||||
};
|
||||
var bootInstructions = {
|
||||
config: { DYNAMIC_HOST: '127.0.0.4' },
|
||||
dataSources: datasource,
|
||||
};
|
||||
boot.execute(app, someInstructions(bootInstructions), function() {
|
||||
expect(app.get('DYNAMIC_HOST')).to.equal('127.0.0.4');
|
||||
expect(app.datasources.mydb.settings.host).to.equal(
|
||||
'127.0.0.4');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should take ENV precedence over config.json', function(done) {
|
||||
process.env.DYNAMIC_HOST = '127.0.0.2';
|
||||
var datasource = {
|
||||
mydb: { host: '${DYNAMIC_HOST}' },
|
||||
};
|
||||
var bootInstructions = {
|
||||
config: { DYNAMIC_HOST: '127.0.0.3' },
|
||||
dataSources: datasource,
|
||||
};
|
||||
boot.execute(app, someInstructions(bootInstructions), function() {
|
||||
expect(app.get('DYNAMIC_HOST')).to.equal('127.0.0.3');
|
||||
expect(app.datasources.mydb.settings.host).to.equal('127.0.0.2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('empty dynamic conf should resolve as `undefined`', function(done) {
|
||||
var datasource = {
|
||||
mydb: { host: '${DYNAMIC_HOST}' },
|
||||
};
|
||||
var bootInstructions = { dataSources: datasource };
|
||||
|
||||
boot.execute(app, someInstructions(bootInstructions), function() {
|
||||
expect(app.get('DYNAMIC_HOST')).to.be.undefined();
|
||||
expect(app.datasources.mydb.settings.host).to.be.undefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function simpleMiddlewareConfig(phase, paths, params) {
|
||||
|
|
Loading…
Reference in New Issue