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 currentState = await this.deviceState.getCurrentForComparison();
const targetConfig = await deviceConfig.formatConfigKeys(
targetConfigUnformatted,
);
const targetConfig = deviceConfig.formatConfigKeys(targetConfigUnformatted);
if (!currentState.local.config) {
throw new InternalInconsistencyError(

View File

@ -1,11 +1,17 @@
import * as _ from 'lodash';
import { Extlinux } from './extlinux';
import { ExtraUEnv } from './extra-uEnv';
import { ConfigTxt } from './config-txt';
import { ConfigFs } from './config-fs';
export default [
export const allBackends = [
new Extlinux(),
new ExtraUEnv(),
new ConfigTxt(),
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 { getMetaOSRelease } from '../lib/os-release';
import { EnvVarObject } from '../lib/types';
import Backends from './backends';
import { allBackends as Backends } from './backends';
import { ConfigOptions, ConfigBackend } from './backends/backend';
export async function getSupportedBackends(): Promise<ConfigBackend[]> {
@ -51,37 +51,7 @@ export function bootConfigToEnv(
.value();
}
export function formatConfigKeys(
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(
export function filterNamespaceFromConfig(
namespace: RegExp,
conf: { [key: string]: any },
): { [key: string]: any } {

View File

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

View File

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

View File

@ -168,19 +168,22 @@ describe('Device Backend Config', () => {
it('accepts RESIN_ and BALENA_ variables', async () => {
return expect(
deviceConfig.formatConfigKeys({
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',
FOO: 'bar', // should be removed
BAR: 'baz', // should be removed
RESIN_SUPERVISOR_LOCAL_MODE: 'false', // any device
BALENA_SUPERVISOR_OVERRIDE_LOCK: 'false', // any device
BALENA_SUPERVISOR_POLL_INTERVAL: '100', // any device
RESIN_HOST_CONFIG_dtparam: 'i2c_arm=on","spi=on","audio=on', // config.txt backend
RESIN_HOST_CONFIGFS_ssdt: 'spidev1.0', // configfs backend
BALENA_HOST_EXTLINUX_isolcpus: '1,2,3', // extlinux backend
}),
).to.eventually.deep.equal({
HOST_CONFIG_foo: 'foobar',
HOST_CONFIG_other: 'val',
HOST_CONFIG_baz: 'bad',
).to.deep.equal({
SUPERVISOR_LOCAL_MODE: 'false',
SUPERVISOR_OVERRIDE_LOCK: 'false',
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 * as config from '../src/config';
import { validKeys } from '../src/device-config';
import * as configUtils from '../src/config/utils';
import { ExtraUEnv } from '../src/config/backends/extra-uEnv';
import { Extlinux } from '../src/config/backends/extlinux';
@ -40,28 +39,6 @@ describe('Config Utilities', () => {
).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> = {