Merge pull request #1440 from balena-io/1437-patch-empty-config

Preventing removing all configurations if device has no backends
This commit is contained in:
bulldozer-balena[bot] 2020-08-24 18:30:04 +00:00 committed by GitHub
commit ac69f04c10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 55 additions and 91 deletions

View File

@ -447,9 +447,7 @@ export class APIBinder {
const defaultConfig = deviceConfig.getDefaults(); const defaultConfig = deviceConfig.getDefaults();
const currentState = await this.deviceState.getCurrentForComparison(); const currentState = await this.deviceState.getCurrentForComparison();
const targetConfig = await deviceConfig.formatConfigKeys( const targetConfig = deviceConfig.formatConfigKeys(targetConfigUnformatted);
targetConfigUnformatted,
);
if (!currentState.local.config) { if (!currentState.local.config) {
throw new InternalInconsistencyError( throw new InternalInconsistencyError(

View File

@ -1,11 +1,17 @@
import * as _ from 'lodash';
import { Extlinux } from './extlinux'; import { Extlinux } from './extlinux';
import { ExtraUEnv } from './extra-uEnv'; import { ExtraUEnv } from './extra-uEnv';
import { ConfigTxt } from './config-txt'; import { ConfigTxt } from './config-txt';
import { ConfigFs } from './config-fs'; import { ConfigFs } from './config-fs';
export default [ export const allBackends = [
new Extlinux(), new Extlinux(),
new ExtraUEnv(), new ExtraUEnv(),
new ConfigTxt(), new ConfigTxt(),
new ConfigFs(), new ConfigFs(),
]; ];
export function matchesAnyBootConfig(envVar: string): boolean {
return allBackends.some((a) => a.isBootConfigVar(envVar));
}

View File

@ -5,7 +5,7 @@ import * as config from '../config';
import * as constants from '../lib/constants'; import * as constants from '../lib/constants';
import { getMetaOSRelease } from '../lib/os-release'; import { getMetaOSRelease } from '../lib/os-release';
import { EnvVarObject } from '../lib/types'; import { EnvVarObject } from '../lib/types';
import Backends from './backends'; import { allBackends as Backends } from './backends';
import { ConfigOptions, ConfigBackend } from './backends/backend'; import { ConfigOptions, ConfigBackend } from './backends/backend';
export async function getSupportedBackends(): Promise<ConfigBackend[]> { export async function getSupportedBackends(): Promise<ConfigBackend[]> {
@ -51,37 +51,7 @@ export function bootConfigToEnv(
.value(); .value();
} }
export function formatConfigKeys( export function filterNamespaceFromConfig(
configBackend: ConfigBackend | null,
allowedKeys: string[],
conf: { [key: string]: any },
): { [key: string]: any } {
const isConfigType = configBackend != null;
const namespaceRegex = /^BALENA_(.*)/;
const legacyNamespaceRegex = /^RESIN_(.*)/;
const confFromNamespace = filterNamespaceFromConfig(namespaceRegex, conf);
const confFromLegacyNamespace = filterNamespaceFromConfig(
legacyNamespaceRegex,
conf,
);
const noNamespaceConf = _.pickBy(conf, (_v, k) => {
return !_.startsWith(k, 'RESIN_') && !_.startsWith(k, 'BALENA_');
});
const confWithoutNamespace = _.defaults(
confFromNamespace,
confFromLegacyNamespace,
noNamespaceConf,
);
return _.pickBy(confWithoutNamespace, (_v, k) => {
return (
_.includes(allowedKeys, k) ||
(isConfigType && configBackend!.isBootConfigVar(k))
);
});
}
function filterNamespaceFromConfig(
namespace: RegExp, namespace: RegExp,
conf: { [key: string]: any }, conf: { [key: string]: any },
): { [key: string]: any } { ): { [key: string]: any } {

View File

@ -2,17 +2,17 @@ import * as _ from 'lodash';
import { inspect } from 'util'; import { inspect } from 'util';
import * as config from './config'; import * as config from './config';
import { SchemaTypeKey } from './config/schema-type';
import * as db from './db'; import * as db from './db';
import * as logger from './logger'; import * as logger from './logger';
import { ConfigOptions, ConfigBackend } from './config/backends/backend';
import * as configUtils from './config/utils';
import * as dbus from './lib/dbus'; import * as dbus from './lib/dbus';
import { UnitNotLoadedError } from './lib/errors';
import { EnvVarObject } from './lib/types'; import { EnvVarObject } from './lib/types';
import { UnitNotLoadedError } from './lib/errors';
import { checkInt, checkTruthy } from './lib/validation'; import { checkInt, checkTruthy } from './lib/validation';
import { DeviceStatus } from './types/state'; import { DeviceStatus } from './types/state';
import * as configUtils from './config/utils';
import { SchemaTypeKey } from './config/schema-type';
import { matchesAnyBootConfig } from './config/backends';
import { ConfigOptions, ConfigBackend } from './config/backends/backend';
const vpnServiceName = 'openvpn'; const vpnServiceName = 'openvpn';
@ -229,7 +229,7 @@ export async function setTarget(
): Promise<void> { ): Promise<void> {
const $db = trx ?? db.models.bind(db); const $db = trx ?? db.models.bind(db);
const formatted = await formatConfigKeys(target); const formatted = formatConfigKeys(target);
// check for legacy keys // check for legacy keys
if (formatted['OVERRIDE_LOCK'] != null) { if (formatted['OVERRIDE_LOCK'] != null) {
formatted['SUPERVISOR_OVERRIDE_LOCK'] = formatted['OVERRIDE_LOCK']; formatted['SUPERVISOR_OVERRIDE_LOCK'] = formatted['OVERRIDE_LOCK'];
@ -298,18 +298,30 @@ export async function getCurrent(): Promise<Dictionary<string>> {
return currentConf; return currentConf;
} }
export async function formatConfigKeys( export function formatConfigKeys(conf: {
conf: Dictionary<string>, [key: string]: any;
): Promise<Dictionary<any>> { }): Dictionary<any> {
const backends = await getConfigBackends(); const namespaceRegex = /^BALENA_(.*)/;
const formattedKeys: Dictionary<any> = {}; const legacyNamespaceRegex = /^RESIN_(.*)/;
for (const backend of backends) { const confFromNamespace = configUtils.filterNamespaceFromConfig(
_.assign( namespaceRegex,
formattedKeys, conf,
configUtils.formatConfigKeys(backend, validKeys, conf), );
); const confFromLegacyNamespace = configUtils.filterNamespaceFromConfig(
} legacyNamespaceRegex,
return formattedKeys; conf,
);
const noNamespaceConf = _.pickBy(conf, (_v, k) => {
return !_.startsWith(k, 'RESIN_') && !_.startsWith(k, 'BALENA_');
});
const confWithoutNamespace = _.defaults(
confFromNamespace,
confFromLegacyNamespace,
noNamespaceConf,
);
return _.pickBy(confWithoutNamespace, (_v, k) => {
return _.includes(validKeys, k) || matchesAnyBootConfig(k);
});
} }
export function getDefaults() { export function getDefaults() {

View File

@ -80,9 +80,7 @@ export async function loadTargetFromFile(
} }
const deviceConf = await deviceConfig.getCurrent(); const deviceConf = await deviceConfig.getCurrent();
const formattedConf = await deviceConfig.formatConfigKeys( const formattedConf = deviceConfig.formatConfigKeys(preloadState.config);
preloadState.config,
);
preloadState.config = { ...formattedConf, ...deviceConf }; preloadState.config = { ...formattedConf, ...deviceConf };
const localState = { const localState = {
local: { name: '', ...preloadState }, local: { name: '', ...preloadState },

View File

@ -168,19 +168,22 @@ describe('Device Backend Config', () => {
it('accepts RESIN_ and BALENA_ variables', async () => { it('accepts RESIN_ and BALENA_ variables', async () => {
return expect( return expect(
deviceConfig.formatConfigKeys({ deviceConfig.formatConfigKeys({
FOO: 'bar', FOO: 'bar', // should be removed
BAR: 'baz', BAR: 'baz', // should be removed
RESIN_HOST_CONFIG_foo: 'foobaz', RESIN_SUPERVISOR_LOCAL_MODE: 'false', // any device
BALENA_HOST_CONFIG_foo: 'foobar', BALENA_SUPERVISOR_OVERRIDE_LOCK: 'false', // any device
RESIN_HOST_CONFIG_other: 'val', BALENA_SUPERVISOR_POLL_INTERVAL: '100', // any device
BALENA_HOST_CONFIG_baz: 'bad', RESIN_HOST_CONFIG_dtparam: 'i2c_arm=on","spi=on","audio=on', // config.txt backend
BALENA_SUPERVISOR_POLL_INTERVAL: '100', RESIN_HOST_CONFIGFS_ssdt: 'spidev1.0', // configfs backend
BALENA_HOST_EXTLINUX_isolcpus: '1,2,3', // extlinux backend
}), }),
).to.eventually.deep.equal({ ).to.deep.equal({
HOST_CONFIG_foo: 'foobar', SUPERVISOR_LOCAL_MODE: 'false',
HOST_CONFIG_other: 'val', SUPERVISOR_OVERRIDE_LOCK: 'false',
HOST_CONFIG_baz: 'bad',
SUPERVISOR_POLL_INTERVAL: '100', SUPERVISOR_POLL_INTERVAL: '100',
HOST_CONFIG_dtparam: 'i2c_arm=on","spi=on","audio=on',
HOST_CONFIGFS_ssdt: 'spidev1.0',
HOST_EXTLINUX_isolcpus: '1,2,3',
}); });
}); });

View File

@ -3,7 +3,6 @@ import * as _ from 'lodash';
import { expect } from './lib/chai-config'; import { expect } from './lib/chai-config';
import * as config from '../src/config'; import * as config from '../src/config';
import { validKeys } from '../src/device-config';
import * as configUtils from '../src/config/utils'; import * as configUtils from '../src/config/utils';
import { ExtraUEnv } from '../src/config/backends/extra-uEnv'; import { ExtraUEnv } from '../src/config/backends/extra-uEnv';
import { Extlinux } from '../src/config/backends/extlinux'; import { Extlinux } from '../src/config/backends/extlinux';
@ -40,28 +39,6 @@ describe('Config Utilities', () => {
).to.deep.equal(configObj.envVars); ).to.deep.equal(configObj.envVars);
}); });
}); });
it('formats keys from config', () => {
// Pick any backend to use for test
// note: some of the values used will be specific to this backend
const backend = BACKENDS['extlinux'];
const formattedKeys = configUtils.formatConfigKeys(backend, validKeys, {
FOO: 'bar',
BAR: 'baz',
RESIN_HOST_CONFIG_foo: 'foobaz',
BALENA_HOST_CONFIG_foo: 'foobar',
RESIN_HOST_CONFIG_other: 'val',
BALENA_HOST_CONFIG_baz: 'bad',
BALENA_SUPERVISOR_POLL_INTERVAL: '100', // any device
BALENA_HOST_EXTLINUX_isolcpus: '1,2,3', // specific to backend
RESIN_HOST_EXTLINUX_fdt: '/boot/mycustomdtb.dtb', // specific to backend
});
expect(formattedKeys).to.deep.equal({
HOST_EXTLINUX_isolcpus: '1,2,3',
HOST_EXTLINUX_fdt: '/boot/mycustomdtb.dtb',
SUPERVISOR_POLL_INTERVAL: '100',
});
});
}); });
const BACKENDS: Record<string, ConfigBackend> = { const BACKENDS: Record<string, ConfigBackend> = {