diff --git a/lib/executor.js b/lib/executor.js index dfc7379..72f917f 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -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); }); } diff --git a/test/executor.test.js b/test/executor.test.js index 867000b..dde248e 100644 --- a/test/executor.test.js +++ b/test/executor.test.js @@ -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) {