Skip to content
33 changes: 33 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const Injector = require("./lib/injector");
const Component = require("./lib/component");
const Logger = require("./lib/logger");
const utils = require("./lib/utils");
const EnvValidator = require("./lib/env_validator");

function merapi(options) {
return new Container(options);
Expand Down Expand Up @@ -150,6 +151,10 @@ class Container extends Component.mixin(AsyncEmitter) {
}

*_initialize() {
yield this.emit("beforeValidateConfig", this);
this.validateConfig();
yield this.emit("afterValidateConfig", this);

yield this.emit("beforeInit", this);
yield this.emit("beforeConfigResolve", this);
this.config.resolve();
Expand Down Expand Up @@ -315,6 +320,34 @@ class Container extends Component.mixin(AsyncEmitter) {
}
}
}

validateConfig() {
const systemEnv = () => {
const result = {};
const env = process.env;
for(const key of Object.keys(env))
result["$"+key] = env[key]; // system env, append $ to key
return result;
};
const combinedEnv = Object.assign(
{},
this.options.envConfig && this.options.envConfig[this.config.env],
this.options.extConfig,
systemEnv()
);
const { config, delimiters } = this.options;
const result = EnvValidator.validateEnvironment(combinedEnv, config, delimiters);

if (result.empty.length > 0) {
this.logger.warn("WARNING! These configurations are empty string: ", result.empty);
}

if (result.undefined.length > 0) {
this.logger.error("These configurations are not set on env variables: ", result.undefined);
throw new Error("Configuration error, some env variables are not set");
}
return true;
}
}

merapi.Container = Container;
Expand Down
23 changes: 11 additions & 12 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function trimify(str) {

/**
* Config creator
*
*
* @param {Object} data
* Config data
*/
Expand Down Expand Up @@ -60,7 +60,6 @@ class Config {
}
data = val;
}

return data;
}
/**
Expand Down Expand Up @@ -109,7 +108,7 @@ class Config {

/**
* Check if config path exist
*
*
* @param {String} path
* config path
* @returns {Boolean}
Expand All @@ -118,7 +117,7 @@ class Config {
has(path) {
return this.get(path, true) !== undefined;
}

/**
* Get or use default value
* @param {String} path
Expand All @@ -128,7 +127,7 @@ class Config {
default(path, def) {
return this.has(path) ? this.get(path) : def;
}

/**
* Internal flatten function
* @param {Object} data
Expand All @@ -149,7 +148,7 @@ class Config {
}
});
}

return res;
}

Expand Down Expand Up @@ -192,7 +191,6 @@ class Config {
}
return tpl(params);
}

return tpl(params);
}

Expand Down Expand Up @@ -226,12 +224,13 @@ class Config {
for (let n in flat) {
ret[n] = this.set(n, this.resolve(n, false));
}

return ret;
}

/**
* Create subconfig by path
*
*
* @method path
* @param {String} path
* config path
Expand All @@ -241,7 +240,7 @@ class Config {
path(path, opts) {
return this.create(this.get(path), opts);
}

/**
* Extend config with data
* @param {Object} data
Expand All @@ -255,7 +254,7 @@ class Config {
}
return this;
}

/**
* Create new config
* @param {Object} data
Expand All @@ -278,7 +277,7 @@ class Config {

return config;
}

/**
* Create new config
* @param {Object} data
Expand Down
73 changes: 73 additions & 0 deletions lib/env_validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"use strict";
const Config = require("./config");
const isNil = require("lodash/isNil");
const isEmpty = require("lodash/isEmpty");

/**
* Make sure environment variables needed in configuration exists.
*
* Return environment variables needed to be defined.
*
* @param {object} environment Environment variables to be validated, intended to be filled with process.env object
* @param {object} configuration Configuration used for merapi
* @param {object} delimiters Delimiters object, used to parse variables. Examples:
* formatted in {
* left: "${"
* right: "}"
* }
* for entry ${$SOME_ENV}
*
*/
exports.validateEnvironment = (environment, configuration, delimiters = { left: "{", right: "}" }) => {
if (isNil(environment)) throw new Error("No environment variable set in this system");
if (isNil(configuration) || isEmpty(environment)) throw new Error("No configuration is set");

const config = new Config();
const flattenConfiguration = config._flatten(configuration);
const neededValues = {
undefined: [],
empty: []
};

for (const key of Object.keys(flattenConfiguration)) {
const value = flattenConfiguration[key];
if (isNil(value)) {
throw new Error(`Error on Config, '${key}' is needed, but the value is ${value}`);
}
if (containDelimiters(value, delimiters)) {
const envKey = value.substring(delimiters.left.length, value.length - delimiters.right.length);
const envValue = environment[envKey];
const sanitisedEnvKey = envKey.replace(/\$/,""); // remove $

if (envValue === "") {
neededValues["empty"].push(sanitisedEnvKey);
} else if (isNil(envValue) && !isNestedValue(envKey, configuration)) {
neededValues["undefined"].push(sanitisedEnvKey);
}
}
}
return neededValues;
};

const containDelimiters = (string, delimiters) => {
if (isNil(string)) return false;
if (isNil(delimiters)) return false;
return typeof string === "string" &&
string.includes(delimiters.left) &&
string.includes(delimiters.right);
};
exports.containDelimiters = containDelimiters;

const isNestedValue = (value, config) => {
let data = config;
const parts = value.split(".").map(val => /^\d+$/.test(val) ? parseInt(val) : val);
if (parts.length === 1) return false;
for(let i = 0; i < parts.length; i++) {
let value = data[parts[i]];
if (data === undefined) {
return false;
}
data = value;
}
return true;
};
118 changes: 118 additions & 0 deletions test/env_validator.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"use strict";
const assert = require("assert");
const envValidator = require("../lib/env_validator");

/* eslint-env mocha */

describe("Env validator", () => {
let config;
let env = {
"$GEIST_URI": "https://example.com",
"$GEIST_TOKEN": "asasaklns12io1u31oi2u3"
};

const delimiters = {
left: "${",
right: "}"
};

beforeEach(() => {
config = {
geist: {
type: "proxy",
uri: "${$GEIST_URI}",
version: "v1",
secret: "${$GEIST_TOKEN}"
}
};
});

it("should return object of empty and undefined env variables, if not set", () => {
env["$GEIST_EMPTY"] = "";
config.geist.lala = "${$LALA}";
config.geist.empty = "${$GEIST_EMPTY}";
config.diaenne = {
type: "proxy",
uri: "${$DIAENNE_URI}",
version: "${$VERSION}"
};
config.auth = "${$SECRET}";

const result = {
undefined: ["LALA", "DIAENNE_URI", "VERSION", "SECRET"],
empty: ["GEIST_EMPTY"]
};
const actualResult = envValidator.validateEnvironment(env, config, delimiters);
assert.deepEqual(actualResult, result);
});

it("should return empty list of undefined and empty if env needed is set already", () => {
const result = envValidator.validateEnvironment(env, config, delimiters);
assert.deepStrictEqual(result, {
undefined: [],
empty: []
});
});

it("should throw error if one of the variable contains null", () => {
config.diaenne = {
type: null,
uri: "${$DIAENNE_URI}",
version: "${$VERSION}"
};
try {
envValidator.validateEnvironment(env, config, delimiters);
} catch(e) {
assert.equal(e.message, "Error on Config, 'diaenne.type' is needed, but the value is null");
}
});

it("should throw error if no environment variables is not installed in this system", () => {
try {
envValidator.validateEnvironment(null, config, delimiters);
} catch(e) {
assert.equal(e.message, "No environment variable set in this system");
}
});

it("should throw error if no configuration is set", () => {
try {
envValidator.validateEnvironment({}, null, delimiters);
} catch(e) {
assert.equal(e.message, "No configuration is set");
}
});
});

describe("containDelimiters", () => {
let delimiters;
before(() => {
delimiters = {left: "{", right: "}"};
});

it("should return false if string contains NO delimiters", () => {
const res = envValidator.containDelimiters("LALAJO", delimiters);
assert.deepStrictEqual(res, false);
});

it("should return true if string contains delimiters", () => {
const res = envValidator.containDelimiters("{LALAJO}", delimiters);
assert.deepStrictEqual(res, true);
});

it("should return false if string is null / undefined", () => {
let result = envValidator.containDelimiters(null, { left: "{", right: "}" });
assert.deepStrictEqual(result, false);

result = envValidator.containDelimiters(undefined, delimiters);
assert.deepStrictEqual(result, false);
});

it("should return false if delimiters is null / undefined", () => {
let res = envValidator.containDelimiters("{LALAJO}", null);
assert.deepStrictEqual(res, false);

res = envValidator.containDelimiters("{LALAJO}", undefined);
assert.deepStrictEqual(res, false);
});
});
Loading