Skip to content

Commit

Permalink
🎨 config optimisations (#7921)
Browse files Browse the repository at this point in the history
refs #7488

- rename file keys for config files, see https://github.com/TryGhost/Ghost/pull/7493/files
- add tests to avoid running into config hierarchy problems again
- overrides.json is the strongest!
- argv/env can override any default
- custom config can override defaults
- reorganise util functions for config again
  • Loading branch information
kirrg001 authored and ErisDS committed Feb 2, 2017
1 parent 5507ada commit 85c0913
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 81 deletions.
96 changes: 54 additions & 42 deletions core/server/config/index.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,57 @@
var Nconf = require('nconf'),
nconf = new Nconf.Provider(),
path = require('path'),
debug = require('debug')('ghost:config'),
localUtils = require('./utils'),
env = process.env.NODE_ENV || 'development';

/**
* command line arguments
*/
nconf.argv();

/**
* env arguments
*/
nconf.env({
separator: '__'
});

/**
* load config files
* @TODO:
* - why does this work? i have no idea!
* - find out why argv override works, when defining these weird keys
* - i could not find any nconf usages so that all config requirements work
*/
nconf.file('ghost1', __dirname + '/overrides.json');
nconf.file('ghost2', path.join(process.cwd(), 'config.' + env + '.json'));
nconf.file('ghost3', __dirname + '/env/config.' + env + '.json');
nconf.file('ghost4', __dirname + '/defaults.json');

/**
* transform all relative paths to absolute paths
* transform sqlite filename path for Ghost-CLI
*/
localUtils.makePathsAbsolute.bind(nconf)(nconf.get('paths'), 'paths');
localUtils.makePathsAbsolute.bind(nconf)(nconf.get('database:connection'), 'database:connection');

/**
* values we have to set manual
*/
nconf.set('env', env);

module.exports = nconf;
module.exports.isPrivacyDisabled = localUtils.isPrivacyDisabled.bind(nconf);
module.exports.getContentPath = localUtils.getContentPath.bind(nconf);
env = process.env.NODE_ENV || 'development',
_private = {};

_private.loadNconf = function loadNconf(options) {
options = options || {};

var baseConfigPath = options.baseConfigPath || __dirname,
customConfigPath = options.customConfigPath || process.cwd(),
nconf = new Nconf.Provider();

/**
* no channel can override the overrides
*/
nconf.file('overrides', path.join(baseConfigPath, 'overrides.json'));

/**
* command line arguments
*/
nconf.argv();

/**
* env arguments
*/
nconf.env({
separator: '__'
});

nconf.file('custom-env', path.join(customConfigPath, 'config.' + env + '.json'));
nconf.file('default-env', path.join(baseConfigPath, 'env', 'config.' + env + '.json'));
nconf.file('defaults', path.join(baseConfigPath, 'defaults.json'));

/**
* transform all relative paths to absolute paths
* transform sqlite filename path for Ghost-CLI
*/
nconf.makePathsAbsolute = localUtils.makePathsAbsolute.bind(nconf);
nconf.isPrivacyDisabled = localUtils.isPrivacyDisabled.bind(nconf);
nconf.getContentPath = localUtils.getContentPath.bind(nconf);

nconf.makePathsAbsolute(nconf.get('paths'), 'paths');
nconf.makePathsAbsolute(nconf.get('database:connection'), 'database:connection');

/**
* values we have to set manual
*/
nconf.set('env', env);

debug(nconf.get());
return nconf;
};

module.exports = _private.loadNconf();
module.exports.loadNconf = _private.loadNconf;
3 changes: 2 additions & 1 deletion core/server/config/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ exports.makePathsAbsolute = function makePathsAbsolute(obj, parent) {

if (!obj) {
throw new errors.IncorrectUsageError({
message: 'makePathsAbsolute: Object is missing.'
message: 'makePathsAbsolute: Can\'t make paths absolute of non existing object.',
help: parent
});
}

Expand Down
129 changes: 91 additions & 38 deletions core/test/unit/config/index_spec.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,89 @@
var should = require('should'),
sinon = require('sinon'),
Promise = require('bluebird'),
moment = require('moment'),
path = require('path'),
fs = require('fs'),
_ = require('lodash'),

testUtils = require('../../utils'),
i18n = require('../../../server/i18n'),
utils = require('../../../server/utils'),
/*jshint unused:false*/
db = require('../../../server/data/db/connection'),

// Thing we are testing
configUtils = require('../../utils/configUtils'),
config = configUtils.config;

i18n.init();
// jscs:disable requireDotNotation

var should = require('should'),
path = require('path'),
rewire = require('rewire'),
_ = require('lodash'),
i18n = require('../../../server/i18n'),
configUtils = require('../../utils/configUtils');

should.equal(true, true);

describe('Config', function () {
before(function () {
i18n.init();
configUtils.restore();
});

afterEach(function () {
configUtils.restore();
});

describe('hierarchy of config channels', function () {
var originalEnv, originalArgv, customConfig, config;

beforeEach(function () {
originalEnv = _.clone(process.env);
originalArgv = _.clone(process.argv);
config = rewire('../../../server/config');
});

afterEach(function () {
process.env = originalEnv;
process.argv = originalArgv;
});

it('env parameter is stronger than file', function () {
process.env['database__client'] = 'test';

customConfig = config.loadNconf({
baseConfigPath: path.join(__dirname, '../../utils/fixtures/config'),
customConfigPath: path.join(__dirname, '../../utils/fixtures/config')
});

customConfig.get('database:client').should.eql('test');
});

it('argv is stronger than env parameter', function () {
process.env['database__client'] = 'test';
process.argv[2] = '--database:client=stronger';

customConfig = config.loadNconf({
baseConfigPath: path.join(__dirname, '../../utils/fixtures/config'),
customConfigPath: path.join(__dirname, '../../utils/fixtures/config')
});

customConfig.get('database:client').should.eql('stronger');
});

it('argv or env is NOT stronger than overrides', function () {
process.env['paths__corePath'] = 'try-to-override';
process.argv[2] = '--paths:corePath=try-to-override';

customConfig = config.loadNconf({
baseConfigPath: path.join(__dirname, '../../utils/fixtures/config'),
customConfigPath: path.join(__dirname, '../../utils/fixtures/config')
});

customConfig.get('paths:corePath').should.not.containEql('try-to-override');
});

it('overrides is stronger than every other config file', function () {
customConfig = config.loadNconf({
baseConfigPath: path.join(__dirname, '../../utils/fixtures/config'),
customConfigPath: path.join(__dirname, '../../utils/fixtures/config')
});

customConfig.get('paths:corePath').should.not.containEql('try-to-override');
customConfig.get('database:client').should.eql('sqlite3');
customConfig.get('database:connection:filename').should.eql('/hehe.db');
customConfig.get('database:debug').should.eql(true);
customConfig.get('url').should.eql('http://localhost:2368');
customConfig.get('logging:level').should.eql('error');
customConfig.get('logging:transports').should.eql(['stdout']);
});
});

describe('Theme', function () {
beforeEach(function () {
configUtils.set({
Expand All @@ -43,14 +100,14 @@ describe('Config', function () {
});

it('should have exactly the right keys', function () {
var themeConfig = config.get('theme');
var themeConfig = configUtils.config.get('theme');

// This will fail if there are any extra keys
themeConfig.should.have.keys('title', 'description', 'logo', 'cover', 'timezone', 'icon');
});

it('should have the correct values for each key', function () {
var themeConfig = config.get('theme');
var themeConfig = configUtils.config.get('theme');

// Check values are as we expect
themeConfig.should.have.property('title', 'casper');
Expand All @@ -64,7 +121,7 @@ describe('Config', function () {

describe('Timezone default', function () {
it('should use timezone from settings when set', function () {
var themeConfig = config.get('theme');
var themeConfig = configUtils.config.get('theme');

// Check values are as we expect
themeConfig.should.have.property('timezone', 'Etc/UTC');
Expand All @@ -75,7 +132,7 @@ describe('Config', function () {
}
});

config.get('theme').should.have.property('timezone', 'Africa/Cairo');
configUtils.config.get('theme').should.have.property('timezone', 'Africa/Cairo');
});

it('should set theme object with timezone by default', function () {
Expand All @@ -89,7 +146,7 @@ describe('Config', function () {

describe('Index', function () {
it('should have exactly the right keys', function () {
var pathConfig = config.get('paths');
var pathConfig = configUtils.config.get('paths');

// This will fail if there are any extra keys
pathConfig.should.have.keys(
Expand All @@ -107,44 +164,42 @@ describe('Config', function () {
});

it('should have the correct values for each key', function () {
var pathConfig = config.get('paths'),
var pathConfig = configUtils.config.get('paths'),
appRoot = path.resolve(__dirname, '../../../../');

pathConfig.should.have.property('appRoot', appRoot);
pathConfig.should.have.property('imagesRelPath', 'content/images');
});

it('should allow specific properties to be user defined', function () {
var contentPath = path.join(config.get('paths').appRoot, 'otherContent', '/');
var contentPath = path.join(configUtils.config.get('paths').appRoot, 'otherContent', '/');

configUtils.set('paths:contentPath', contentPath);
config.get('paths').should.have.property('contentPath', contentPath);
config.getContentPath('images').should.eql(contentPath + 'images/');
configUtils.config.get('paths').should.have.property('contentPath', contentPath);
configUtils.config.getContentPath('images').should.eql(contentPath + 'images/');
});
});

describe('Storage', function () {
it('should default to local-file-store', function () {
config.get('paths').should.have.property('internalStoragePath', path.join(config.get('paths').corePath, '/server/storage/'));
configUtils.config.get('paths').should.have.property('internalStoragePath', path.join(configUtils.config.get('paths').corePath, '/server/storage/'));

config.get('storage').should.have.property('active', {
configUtils.config.get('storage').should.have.property('active', {
images: 'local-file-store',
themes: 'local-file-store'
});
});

it('no effect: setting a custom active storage as string', function () {
var storagePath = path.join(config.get('paths').contentPath, 'storage', 's3');

configUtils.set({
storage: {
active: 's3',
s3: {}
}
});

config.get('storage').should.have.property('active', 's3');
config.get('storage').should.have.property('s3', {});
configUtils.config.get('storage').should.have.property('active', 's3');
configUtils.config.get('storage').should.have.property('s3', {});
});

it('able to set storage for themes (but not officially supported!)', function () {
Expand All @@ -157,15 +212,13 @@ describe('Config', function () {
}
});

config.get('storage').should.have.property('active', {
configUtils.config.get('storage').should.have.property('active', {
images: 'local-file-store',
themes: 's3'
});
});

it('should allow setting a custom active storage as object', function () {
var storagePath = path.join(config.get('paths').contentPath, 'storage', 's3');

configUtils.set({
storage: {
active: {
Expand All @@ -175,7 +228,7 @@ describe('Config', function () {
}
});

config.get('storage').should.have.property('active', {
configUtils.config.get('storage').should.have.property('active', {
images: 's2',
themes: 'local-file-store'
});
Expand Down
14 changes: 14 additions & 0 deletions core/test/utils/fixtures/config/config.testing-mysql.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"paths": {
"corePath": "try-to-override"
},
"database": {
"connection": {
"filename": "/hehe.db"
},
"debug": true
},
"logging": {
"level": "error"
}
}
14 changes: 14 additions & 0 deletions core/test/utils/fixtures/config/config.testing.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"paths": {
"corePath": "try-to-override"
},
"database": {
"connection": {
"filename": "/hehe.db"
},
"debug": true
},
"logging": {
"level": "error"
}
}
9 changes: 9 additions & 0 deletions core/test/utils/fixtures/config/defaults.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"database": {
"client": "sqlite3",
"connection": {
"filename": "/test.db"
},
"debug": false
}
}
7 changes: 7 additions & 0 deletions core/test/utils/fixtures/config/env/config.testing-mysql.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"url": "http://localhost:2368",
"logging": {
"level": "info",
"transports": ["stdout"]
}
}
7 changes: 7 additions & 0 deletions core/test/utils/fixtures/config/env/config.testing.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"url": "http://localhost:2368",
"logging": {
"level": "info",
"transports": ["stdout"]
}
}
Loading

0 comments on commit 85c0913

Please sign in to comment.