mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2024-11-15 18:08:32 -07:00
Add Environment Variable Override (#4085)
* Add Environment Variable Override I need to be able to manage settings on this service via Environment Variables. I've added the ability to override anything in the settings schema with a corresponding environment variable. e.g. to override settings.serial.port you can set ZIGBEE2MQTT_SERIAL_PORT=/dev/ttyS0 to override the mqtt username you can set ZIGBEE2MQTT_MQTT_USER=testusername This new addition will not perform any action on existing installations unless the matching environment variable is set. I have tested this setting string, number, boolean, object and array values. * Adding test case for environment variables and slight modification to ensure 100% code coverage. * Adding a test to confirm that env variables will set non default values. Also realized that I was errantly applying the env variables to the defaults for testing. Understanding what this is doing more clearly I realize that should be clean. * Refactoring to 1. Remove the test variables from the schema and defaults and manually reflect the tests in the test. 2. Rename environment variable base from ZIGBEE2MQTT_ to ZIGBEE2MQTT_CONFIG_ * Small improvements * Removing the unneeded test. Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
This commit is contained in:
parent
5de279c20e
commit
08524953bf
@ -438,9 +438,52 @@ function read() {
|
||||
return s;
|
||||
}
|
||||
|
||||
function applyEnvironmentVariables(settings) {
|
||||
const iterate = (obj, path) => {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (key !== 'type') {
|
||||
if (key !== 'properties') {
|
||||
const type = (obj[key].type || 'object').toString();
|
||||
const envPart = path.reduce((acc, val) => `${acc}${val}_`, '');
|
||||
const envVariableName = (`ZIGBEE2MQTT_CONFIG_${envPart}${key}`).toUpperCase();
|
||||
if (process.env[envVariableName]) {
|
||||
const setting = path.reduce((acc, val, index) => {
|
||||
acc[val] = acc[val] || {};
|
||||
return acc[val];
|
||||
}, settings);
|
||||
|
||||
if (type.indexOf('object') >= 0 || type.indexOf('array') >= 0) {
|
||||
setting[key] = JSON.parse(process.env[envVariableName]);
|
||||
}
|
||||
if (type.indexOf('number') >= 0) {
|
||||
setting[key] = process.env[envVariableName] * 1;
|
||||
}
|
||||
if (type.indexOf('boolean') >= 0) {
|
||||
setting[key] = process.env[envVariableName].toLowerCase() === 'true';
|
||||
}
|
||||
if (type.indexOf('string') >= 0) {
|
||||
setting[key] = process.env[envVariableName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof obj[key] === 'object') {
|
||||
const newPath = [...path];
|
||||
if (key !== 'properties') {
|
||||
newPath.push(key);
|
||||
}
|
||||
iterate(obj[key], newPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
iterate(schema.properties, []);
|
||||
}
|
||||
|
||||
function get() {
|
||||
if (!_settings) {
|
||||
_settings = read();
|
||||
applyEnvironmentVariables(_settings);
|
||||
}
|
||||
|
||||
return _settings;
|
||||
@ -743,11 +786,14 @@ module.exports = {
|
||||
// For tests only
|
||||
_write: write,
|
||||
_reRead: () => {
|
||||
_settings = read();
|
||||
_settingsWithDefaults = objectAssignDeep.noMutate(defaults, get());
|
||||
_settings = null;
|
||||
get();
|
||||
_settingsWithDefaults = null;
|
||||
getWithDefaults();
|
||||
},
|
||||
_clear: () => {
|
||||
_settings = null;
|
||||
_settingsWithDefaults = null;
|
||||
},
|
||||
_getDefaults: () => defaults,
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ const devicesFile = data.joinPath('devices.yaml');
|
||||
const groupsFile = data.joinPath('groups.yaml');
|
||||
const secretFile = data.joinPath('secret.yaml');
|
||||
const yaml = require('js-yaml');
|
||||
const objectAssignDeep = require(`object-assign-deep`);
|
||||
|
||||
const minimalConfig = {
|
||||
permit_join: true,
|
||||
@ -27,11 +28,19 @@ describe('Settings', () => {
|
||||
const remove = (file) => {
|
||||
if (fs.existsSync(file)) fs.unlinkSync(file);
|
||||
}
|
||||
const clearEnvironmentVariables = () => {
|
||||
Object.keys(process.env).forEach((key) => {
|
||||
if(key.indexOf('ZIGBEE2MQTT_CONFIG_') >= 0) {
|
||||
delete process.env[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
remove(configurationFile);
|
||||
remove(devicesFile);
|
||||
remove(groupsFile);
|
||||
clearEnvironmentVariables();
|
||||
});
|
||||
|
||||
it('Should return default settings', () => {
|
||||
@ -53,6 +62,29 @@ describe('Settings', () => {
|
||||
expect(s).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it('Should apply environment variables', () => {
|
||||
process.env['ZIGBEE2MQTT_CONFIG_SERIAL_DISABLE_LED'] = 'true';
|
||||
process.env['ZIGBEE2MQTT_CONFIG_ADVANCED_SOFT_RESET_TIMEOUT'] = 1;
|
||||
process.env['ZIGBEE2MQTT_CONFIG_EXPERIMENTAL_OUTPUT'] = 'csvtest';
|
||||
process.env['ZIGBEE2MQTT_CONFIG_ADVANCED_AVAILABILITY_BLOCKLIST'] = '["0x43597f0dac781b1e", "x223b0aef2ae8d1b0"]';
|
||||
process.env['ZIGBEE2MQTT_CONFIG_MAP_OPTIONS_GRAPHVIZ_COLORS_FILL'] = '{"enddevice": "#ff0000", "coordinator": "#00ff00", "router": "#0000ff"}';
|
||||
process.env['ZIGBEE2MQTT_CONFIG_MQTT_BASE_TOPIC'] = 'testtopic';
|
||||
|
||||
write(configurationFile, {});
|
||||
const s = settings.get();
|
||||
const expected = objectAssignDeep.noMutate({}, settings._getDefaults());
|
||||
expected.devices = {};
|
||||
expected.groups = {};
|
||||
expected.serial.disable_led = true;
|
||||
expected.advanced.soft_reset_timeout = 1;
|
||||
expected.experimental.output = 'csvtest';
|
||||
expected.advanced.availability_blocklist = ['0x43597f0dac781b1e', 'x223b0aef2ae8d1b0'];
|
||||
expected.map_options.graphviz.colors.fill = {enddevice: '#ff0000', coordinator: '#00ff00', router: '#0000ff'};
|
||||
expected.mqtt.base_topic = 'testtopic';
|
||||
|
||||
expect(s).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it('Should add devices', () => {
|
||||
write(configurationFile, {});
|
||||
settings.addDevice('0x12345678');
|
||||
|
Loading…
Reference in New Issue
Block a user