Settings.js: support configuration via environment variables.
All the configuration values can be read from environment variables using the syntax "${ENV_VAR_NAME}". This is useful, for example, when running in a Docker container. EXAMPLE: "port": "${PORT}" "minify": "${MINIFY}" "skinName": "${SKIN_NAME}" Would read the configuration values for those items from the environment variables PORT, MINIFY and SKIN_NAME. REMARKS: Please note that a variable substitution always needs to be quoted. "port": 9001, <-- Literal values. When not using substitution, "minify": false only strings must be quoted: booleans and "skin": "colibris" numbers must not. "port": ${PORT} <-- ERROR: this is not valid json "minify": ${MINIFY} "skin": ${SKIN_NAME} "port": "${PORT}" <-- CORRECT: if you want to use a variable "minify": "${MINIFY}" substitution, put quotes around its name, "skin": "${SKIN_NAME}" even if the required value is a number or a boolean. Etherpad will take care of rewriting it to the proper type if necessary. Resolves #3543pull/3573/head
parent
f96e139b17
commit
6d400050a3
|
@ -14,6 +14,8 @@ cp ../settings.json.template settings.json
|
|||
[ further edit your settings.json as needed]
|
||||
```
|
||||
|
||||
**Each configuration parameter can also be set via an environment variable**, using the syntax `"${ENV_VAR_NAME}"`. For details, refer to `settings.json.template`.
|
||||
|
||||
Build the version you prefer:
|
||||
```bash
|
||||
# builds latest development version
|
||||
|
|
|
@ -5,6 +5,39 @@
|
|||
*
|
||||
* Please note that starting from Etherpad 1.6.0 you can store DB credentials in
|
||||
* a separate file (credentials.json).
|
||||
*
|
||||
*
|
||||
* ENVIRONMENT VARIABLE SUBSTITUTION
|
||||
* =================================
|
||||
*
|
||||
* All the configuration values can be read from environment variables using the
|
||||
* syntax "${ENV_VAR_NAME}".
|
||||
* This is useful, for example, when running in a Docker container.
|
||||
*
|
||||
* EXAMPLE:
|
||||
* "port": "${PORT}"
|
||||
* "minify": "${MINIFY}"
|
||||
* "skinName": "${SKIN_NAME}"
|
||||
*
|
||||
* Would read the configuration values for those items from the environment
|
||||
* variables PORT, MINIFY and SKIN_NAME.
|
||||
*
|
||||
* REMARKS:
|
||||
* Please note that a variable substitution always needs to be quoted.
|
||||
* "port": 9001, <-- Literal values. When not using substitution,
|
||||
* "minify": false only strings must be quoted: booleans and
|
||||
* "skin": "colibris" numbers must not.
|
||||
*
|
||||
* "port": ${PORT} <-- ERROR: this is not valid json
|
||||
* "minify": ${MINIFY}
|
||||
* "skin": ${SKIN_NAME}
|
||||
*
|
||||
* "port": "${PORT}" <-- CORRECT: if you want to use a variable
|
||||
* "minify": "${MINIFY}" substitution, put quotes around its name,
|
||||
* "skin": "${SKIN_NAME}" even if the required value is a number or a
|
||||
* boolean.
|
||||
* Etherpad will take care of rewriting it to
|
||||
* the proper type if necessary.
|
||||
*/
|
||||
{
|
||||
/*
|
||||
|
|
|
@ -370,9 +370,118 @@ function storeSettings(settingsObj) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a javascript object containing Etherpad's configuration, and returns
|
||||
* another object, in which all the string properties whose name is of the form
|
||||
* "${ENV_VAR}", got their value replaced with the value of the given
|
||||
* environment variable.
|
||||
*
|
||||
* An environment variable's value is always a string. However, the code base
|
||||
* makes use of the various json types. To maintain compatiblity, some
|
||||
* heuristics is applied:
|
||||
*
|
||||
* - if ENV_VAR does not exist in the environment, null is returned;
|
||||
* - if ENV_VAR's value is "true" or "false", it is converted to the js boolean
|
||||
* values true or false;
|
||||
* - if ENV_VAR's value looks like a number, it is converted to a js number
|
||||
* (details in the code).
|
||||
*
|
||||
* Variable substitution is performed doing a round trip conversion to/from
|
||||
* json, using a custom replacer parameter in JSON.stringify(), and parsing the
|
||||
* JSON back again. This ensures that environment variable replacement is
|
||||
* performed even on nested objects.
|
||||
*
|
||||
* see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter
|
||||
*/
|
||||
function lookupEnvironmentVariables(obj) {
|
||||
const stringifiedAndReplaced = JSON.stringify(obj, (key, value) => {
|
||||
/*
|
||||
* the first invocation of replacer() is with an empty key. Just go on, or
|
||||
* we would zap the entire object.
|
||||
*/
|
||||
if (key === '') {
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we received from the configuration file a number, a boolean or
|
||||
* something that is not a string, we can be sure that it was a literal
|
||||
* value. No need to perform any variable substitution.
|
||||
*
|
||||
* The environment variable expansion syntax "${ENV_VAR}" is just a string
|
||||
* of specific form, after all.
|
||||
*/
|
||||
if (typeof value !== 'string') {
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Let's check if the string value looks like a variable expansion (e.g.:
|
||||
* "${ENV_VAR}")
|
||||
*/
|
||||
const match = value.match(/^\$\{(.*)\}$/);
|
||||
|
||||
if (match === null) {
|
||||
// no match: use the value literally, without any substitution
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// we found the name of an environment variable. Let's read its value.
|
||||
const envVarName = match[1];
|
||||
const envVarValue = process.env[envVarName];
|
||||
|
||||
if (envVarValue === undefined) {
|
||||
console.warn(`Configuration key ${key} tried to read its value from environment variable ${envVarName}, but no value was found. Returning null. Please check your configuration and environment settings.`);
|
||||
|
||||
/*
|
||||
* We have to return null, because if we just returned undefined, the
|
||||
* configuration item "key" would be stripped from the returned object.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
// envVarName contained some value.
|
||||
|
||||
/*
|
||||
* For numeric and boolean strings let's convert it to proper types before
|
||||
* returning it, in order to maintain backward compatibility.
|
||||
*/
|
||||
|
||||
// cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
|
||||
const isNumeric = !isNaN(envVarValue) && !isNaN(parseFloat(envVarValue) && isFinite(envVarValue));
|
||||
|
||||
if (isNumeric) {
|
||||
console.debug(`Configuration key "${key}" will be read from environment variable ${envVarName}. Detected numeric string, that will be coerced to a number`);
|
||||
|
||||
return +envVarValue;
|
||||
}
|
||||
|
||||
// the boolean literal case is easy.
|
||||
if (envVarValue === "true" || envVarValue === "false") {
|
||||
console.debug(`Configuration key "${key}" will be read from environment variable ${envVarName}. Detected boolean string, that will be coerced to a boolean`);
|
||||
|
||||
return (envVarValue === "true");
|
||||
}
|
||||
|
||||
/*
|
||||
* The only remaining case is that envVarValue is a string with no special
|
||||
* meaning, and we just return it as-is.
|
||||
*/
|
||||
console.debug(`Configuration key "${key}" will be read from environment variable ${envVarName}`);
|
||||
|
||||
return envVarValue;
|
||||
});
|
||||
|
||||
const newSettings = JSON.parse(stringifiedAndReplaced);
|
||||
|
||||
return newSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* - reads the JSON configuration file settingsFilename from disk
|
||||
* - strips the comments
|
||||
* - replaces environment variables calling lookupEnvironmentVariables()
|
||||
* - returns a parsed Javascript object
|
||||
*
|
||||
* The isSettings variable only controls the error logging.
|
||||
|
@ -409,7 +518,9 @@ function parseSettings(settingsFilename, isSettings) {
|
|||
|
||||
console.info(`${settingsType} loaded from: ${settingsFilename}`);
|
||||
|
||||
return settings;
|
||||
const replacedSettings = lookupEnvironmentVariables(settings);
|
||||
|
||||
return replacedSettings;
|
||||
} catch(e) {
|
||||
console.error(`There was an error processing your ${settingsType} file from ${settingsFilename}: ${e.message}`);
|
||||
|
||||
|
|
Loading…
Reference in New Issue