Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,14 @@
"quotes": [2, "single", "avoid-escape"],
"semi-spacing": [2, { "before": false, "after": true }],
"semi": [2, "always"],
"space-after-keywords": [2, "always"],
"keyword-spacing": 2,
"space-before-blocks": 2,
"space-before-function-paren": [2, "never"],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-return-throw-case": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always"],
"no-useless-escape": 0,

"arrow-spacing": [2, { "before": true, "after": true }],
"constructor-super": 2,
Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ console.log(config);
- It is possible to change `%` to any other character. Just use `replaceEnv` configuration option.
- It is possible to use default values when environmental variable is not set.

### Docker secret replacement

/tmp/config.json:
``` javascript
{ docks1: "#{DOCKER_SECRET1}", docks2: "#{DOCKER_SECRET2|def}" }
```
index.js:
``` javascript
var readConfig = require('read-config'),
config = readConfig('/tmp/config.json');

console.log(config);
// If secret file content DOCKER_SECRET1 is 'abc' and DOCKER_SECRET2 is not defined
// $ node index.js
// { docks1: 'abc', env2: 'def' }
```

- It is possible to change `#` to any other character. Just use `replaceDockerSecret` configuration option.
- It is possible to use default values when docker secret is not present.

### Configuration overriding with system variables

/tmp/config.json:
Expand Down Expand Up @@ -225,7 +245,8 @@ All json files are loaded using [JSON5](https://www.npmjs.com/package/json5) lib
- **optional** - (String/Array, default: []) list of configuration paths that are optional. If any configuration path is not resolved and is not optional it's treated as empty file and no exception is raised.
- **basedir** - (String/Array, default: []) base directory (or directories) used for searching configuration files. Mind that `basedir` has lower priority than a configuration directory, process basedir, and absolute paths.
- **replaceEnv** - (Boolean/String, default: false, constraint: A string value must be different than `replaceLocal`) if specified enables environment variable replacement. Expected string value e.g. `%` that will be used to replace all occurrences of `%{...}` with environment variables. You can use default values like: %{a.b.c|some-default-value}.
- **replaceLocal** - (Boolean/String, default: '@', constraint: A string value must be different than `replaceEnv`) if specified enables configuration variable replacement. Expected string value e.g. `@` that will be used to replace all occurrences of `@{...}` with configuration variables. You can use default values like: @{a.b.c|some-default-value}.
- **replaceDockerSecret** - (Boolean/String, default: false, constraint: A string value must be different than `replaceLocal` and `replaceEnv`) if specified enables [docker secret](https://docs.docker.com/engine/swarm/secrets/) file replacement. Expected string value e.g. `#` that will be used to replace all occurrences of `#{...}` with docker secret file content. You can use default values like: #{DOCKER_SECRET|some-default-value}.
- **replaceLocal** - (Boolean/String, default: '@', constraint: A string value must be different than `replaceEnv` and `replaceDockerSecret`) if specified enables configuration variable replacement. Expected string value e.g. `@` that will be used to replace all occurrences of `@{...}` with configuration variables. You can use default values like: @{a.b.c|some-default-value}.
- **override** - (Boolean/String, default: false) If specified enables configuration overriding with environmental variables like `CONFIG_<propertyName>`.
- **skipUnresolved** - (Boolean, default: false) `true` blocks error throwing on unresolved variables.

Expand All @@ -237,6 +258,7 @@ Default **opts** values:
basedir: null,
replaceEnv: "%",
replaceLocal: "@",
replaceDockerSecret: "#",
skipUnresolved: false
}
```
Expand Down
91 changes: 57 additions & 34 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ const pkg = require('./package'),
});

// Print banner
gutil.log([
fs.readFileSync('.banner', 'utf8'),
`${pkg.name} v${pkg.version}`,
pkg.description
].map(_.unary(gutil.colors.green)).join('\n'));
gutil.log(
[
fs.readFileSync('.banner', 'utf8'),
`${pkg.name} v${pkg.version}`,
pkg.description
]
.map(_.unary(gutil.colors.green))
.join('\n')
);

argv.debug && gutil.log('Paremeters:', argv);

Expand All @@ -43,19 +47,18 @@ function initTestMode() {

gulp.task('clean', (done) => {
del(['build']).then((paths) => {
argv.debug && paths.length && gutil.log('Deleted files/folders:\n', paths.join('\n'));
argv.debug &&
paths.length &&
gutil.log('Deleted files/folders:\n', paths.join('\n'));
done();
});
});

gulp.task('lint', () => {
const src = argv.file || [
'**/*.js',
'!node_modules/**/*',
'!build/**/*'
];
const src = argv.file || ['**/*.js', '!node_modules/**/*', '!build/**/*'];
argv.debug && gutil.log('Running code lint on:', src);
return gulp.src(src)
return gulp
.src(src)
.pipe(eslint())
.pipe(gulpif(!argv.bail, eslint.format()))
.pipe(gulpif(!argv.bail, eslint.failAfterError()))
Expand All @@ -66,12 +69,15 @@ gulp.task('test', () => {
const testSrc = argv.file || 'test/**/*.unit.js';
argv.debug && gutil.log('Running unit tests for:', testSrc);
initTestMode();
return gulp.src(testSrc)
.pipe(mocha({
reporter: 'mocha-jenkins-reporter',
debug: argv.debug,
bail: argv.bail
}))
return gulp
.src(testSrc)
.pipe(
mocha({
reporter: 'mocha-jenkins-reporter',
debug: argv.debug,
bail: argv.bail
})
)
.on('error', (e) => {
gutil.log('[mocha]', e.stack);
});
Expand All @@ -82,31 +88,48 @@ gulp.task('test-cov', (done) => {
mkdirp.sync('./build/test/coverage');
global.testMode = 'unit';
gulp.src('lib/**/*.js')
.pipe(istanbul({
includeUntested: true
}))
.pipe(
istanbul({
includeUntested: true
})
)
.pipe(istanbul.hookRequire())
.on('finish', () => {
const testSrc = argv.file || 'test/**/*.unit.js';
initTestMode();
argv.debug && gutil.log('Running instrumented unit tests for:', testSrc);
argv.debug &&
gutil.log('Running instrumented unit tests for:', testSrc);
gulp.src(testSrc)
.pipe(mocha({
reporter: 'mocha-jenkins-reporter',
debug: argv.debug,
bail: argv.bail
}))
.pipe(istanbul.writeReports({
dir: './build/test/coverage',
reporters: ['lcov', 'json', 'text', 'text-summary', 'cobertura'],
reportOpts: { dir: './build/test/coverage' }
}))
.pipe(
mocha({
reporter: 'mocha-jenkins-reporter',
debug: argv.debug,
bail: argv.bail
})
)
.pipe(
istanbul.writeReports({
dir: './build/test/coverage',
reporters: [
'lcov',
'json',
'text',
'text-summary',
'cobertura'
],
reportOpts: { dir: './build/test/coverage' }
})
)
.on('error', (e) => {
gutil.log('[mocha]', e.stack);
})
.on('end', done);
});
});

gulp.task('default', sync(['clean', 'lint', 'test']));
gulp.task('ci', sync(['clean', 'lint', 'test-cov']));
gulp.task('default', () => {
sync(['clean', 'lint', 'test']);
});
gulp.task('ci', () => {
sync(['clean', 'lint', 'test-cov']);
});
3 changes: 1 addition & 2 deletions lib/load/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ module.exports.async = function(paths, opts, callback) {
};

module.exports.sync = function(paths, opts) {
let configs;
paths = Array.isArray(paths) ? paths : [paths];
configs = paths.map((path) => {
const configs = paths.map((path) => {
return mergeParents.sync(path, opts);
});
return mergeConfigs(configs);
Expand Down
4 changes: 2 additions & 2 deletions lib/load/parse/yaml.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function parse(content, callback) {
if (!content || !content.trim().length) return callback(null, {});
content = normalizeContent(content);
try {
result = yaml.safeLoad(content);
result = yaml.load(content);
} catch (e) {
return callback(e);
}
Expand All @@ -47,7 +47,7 @@ function parse(content, callback) {
function parseSync(content) {
if (content && !content.trim().length) return {};
content = normalizeContent(content);
return yaml.safeLoad(content) || {};
return yaml.load(content) || {};
}

function normalizeContent(content) {
Expand Down
3 changes: 1 addition & 2 deletions lib/load/resolve-path.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ module.exports.async = function(filepath, basedirs, callback) {
};

module.exports.sync = function(filepath, basedirs) {
let paths;
basedirs = basedirs || [];
paths = lookupPaths(filepath, basedirs).filter(fs.existsSync);
const paths = lookupPaths(filepath, basedirs).filter(fs.existsSync);
return paths.length ? paths[0] : null;
};

Expand Down
8 changes: 4 additions & 4 deletions lib/read-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ module.exports.sync = sync;
module.exports.async = async;

function async(paths, opts, callback) {
let err;
if (typeof opts === 'function' && !callback) {
callback = opts;
opts = {};
}
opts = defaultOptions(opts);
err = validateParams(paths, opts, callback);
const err = validateParams(paths, opts, callback);
if (err) {
if (typeof callback === 'function') return callback(err);
throw err;
Expand All @@ -35,9 +34,9 @@ function async(paths, opts, callback) {
}

function sync(paths, opts) {
let err, config;
let config;
opts = defaultOptions(opts);
err = validateParams(paths, opts);
const err = validateParams(paths, opts);
if (err) throw err;
config = load.sync(paths, opts);
config = resolve(config, opts);
Expand Down Expand Up @@ -68,6 +67,7 @@ function defaultOptions(opts) {
basedir: '.',
replaceEnv: '%',
replaceLocal: '@',
replaceDockerSecret: '#',
skipUnresolved: false,
freeze: false
}, opts);
Expand Down
16 changes: 15 additions & 1 deletion lib/resolve/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
const replaceVariables = require('./replace-variables'),
override = require('./override'),
moduleName = '[read-config]',
ReadConfigError = require('../read-config-error');
ReadConfigError = require('../read-config-error'),
secrets = require('./secrets');

module.exports = function(config, opts) {
opts = opts || {};
config = override(opts.override, config, process.env);
config = replaceEnvVariables(config, opts.replaceEnv, opts);
config = replaceLocalVariables(config, opts.replaceLocal, opts);
config = replaceDockerSecrets(config, opts.replaceDockerSecret, opts);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test it

return config;
};

Expand All @@ -36,3 +38,15 @@ function replaceLocalVariables(config, marker, opts) {
return config;
}
}

function replaceDockerSecrets(config, marker, opts) {
if (marker) {
try {
return replaceVariables(marker, config, secrets, opts);
} catch (e) {
throw new ReadConfigError(`${moduleName} Could not resolve docker secret file. ${e.message}`);
}
} else {
return config;
}
}
22 changes: 22 additions & 0 deletions lib/resolve/secrets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

const fs = require('fs'),
path = require('path'),
SECRETS_DIR = '/run/secrets';

function readSecrets() {
let output = {};
if (fs.existsSync(SECRETS_DIR)) {
const files = fs.readdirSync(SECRETS_DIR);

files.forEach((file) => {
const fullPath = path.join(SECRETS_DIR, file),
key = file,
data = fs.readFileSync(fullPath, 'utf8').toString().trim();
output[key] = data;
});
}
return output;
}

module.exports = readSecrets();
Loading