mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-04-12 21:52:57 +00:00
Merge pull request #1431 from balena-io/allow-multiple-config-backends
Allow multiple config backends
This commit is contained in:
commit
3794c3f7bb
@ -21,7 +21,7 @@ export async function remountAndWriteAtomic(
|
||||
await writeFileAtomic(file, data);
|
||||
}
|
||||
|
||||
export abstract class DeviceConfigBackend {
|
||||
export abstract class ConfigBackend {
|
||||
// Does this config backend support the given device type?
|
||||
public abstract async matches(
|
||||
deviceType: string,
|
||||
@ -61,7 +61,7 @@ export abstract class DeviceConfigBackend {
|
||||
public abstract createConfigVarName(configName: string): string | null;
|
||||
|
||||
// Allow a chosen config backend to be initialised
|
||||
public async initialise(): Promise<DeviceConfigBackend> {
|
||||
public async initialise(): Promise<ConfigBackend> {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import * as path from 'path';
|
||||
|
||||
import {
|
||||
ConfigOptions,
|
||||
DeviceConfigBackend,
|
||||
ConfigBackend,
|
||||
bootMountPoint,
|
||||
remountAndWriteAtomic,
|
||||
} from './backend';
|
||||
@ -21,7 +21,7 @@ import log from '../../lib/supervisor-console';
|
||||
|
||||
type ConfigfsConfig = Dictionary<string[]>;
|
||||
|
||||
export class ConfigfsConfigBackend extends DeviceConfigBackend {
|
||||
export class ConfigFs extends ConfigBackend {
|
||||
private readonly SystemAmlFiles = path.join(
|
||||
constants.rootMountPoint,
|
||||
'boot/acpi-tables',
|
||||
@ -129,7 +129,7 @@ export class ConfigfsConfigBackend extends DeviceConfigBackend {
|
||||
}
|
||||
}
|
||||
|
||||
public async initialise(): Promise<ConfigfsConfigBackend> {
|
||||
public async initialise(): Promise<ConfigFs> {
|
||||
try {
|
||||
await super.initialise();
|
||||
|
||||
@ -158,7 +158,7 @@ export class ConfigfsConfigBackend extends DeviceConfigBackend {
|
||||
}
|
||||
|
||||
public async matches(deviceType: string): Promise<boolean> {
|
||||
return ConfigfsConfigBackend.SupportedDeviceTypes.includes(deviceType);
|
||||
return ConfigFs.SupportedDeviceTypes.includes(deviceType);
|
||||
}
|
||||
|
||||
public async getBootConfig(): Promise<ConfigOptions> {
|
||||
@ -195,15 +195,11 @@ export class ConfigfsConfigBackend extends DeviceConfigBackend {
|
||||
}
|
||||
|
||||
public isSupportedConfig(name: string): boolean {
|
||||
return ConfigfsConfigBackend.BootConfigVars.includes(
|
||||
this.stripPrefix(name),
|
||||
);
|
||||
return ConfigFs.BootConfigVars.includes(this.stripPrefix(name));
|
||||
}
|
||||
|
||||
public isBootConfigVar(name: string): boolean {
|
||||
return ConfigfsConfigBackend.BootConfigVars.includes(
|
||||
this.stripPrefix(name),
|
||||
);
|
||||
return ConfigFs.BootConfigVars.includes(this.stripPrefix(name));
|
||||
}
|
||||
|
||||
public processConfigVarName(name: string): string {
|
||||
|
@ -3,7 +3,7 @@ import { fs } from 'mz';
|
||||
|
||||
import {
|
||||
ConfigOptions,
|
||||
DeviceConfigBackend,
|
||||
ConfigBackend,
|
||||
bootMountPoint,
|
||||
remountAndWriteAtomic,
|
||||
} from './backend';
|
||||
@ -21,12 +21,12 @@ import log from '../../lib/supervisor-console';
|
||||
* - {BALENA|RESIN}_HOST_CONFIG_gpio = value | "value" | "value1","value2"
|
||||
*/
|
||||
|
||||
export class RPiConfigBackend extends DeviceConfigBackend {
|
||||
export class ConfigTxt extends ConfigBackend {
|
||||
private static bootConfigVarPrefix = `${constants.hostConfigVarPrefix}CONFIG_`;
|
||||
private static bootConfigPath = `${bootMountPoint}/config.txt`;
|
||||
|
||||
public static bootConfigVarRegex = new RegExp(
|
||||
'(?:' + _.escapeRegExp(RPiConfigBackend.bootConfigVarPrefix) + ')(.+)',
|
||||
'(?:' + _.escapeRegExp(ConfigTxt.bootConfigVarPrefix) + ')(.+)',
|
||||
);
|
||||
|
||||
private static arrayConfigKeys = [
|
||||
@ -57,13 +57,10 @@ export class RPiConfigBackend extends DeviceConfigBackend {
|
||||
public async getBootConfig(): Promise<ConfigOptions> {
|
||||
let configContents = '';
|
||||
|
||||
if (await fs.exists(RPiConfigBackend.bootConfigPath)) {
|
||||
configContents = await fs.readFile(
|
||||
RPiConfigBackend.bootConfigPath,
|
||||
'utf-8',
|
||||
);
|
||||
if (await fs.exists(ConfigTxt.bootConfigPath)) {
|
||||
configContents = await fs.readFile(ConfigTxt.bootConfigPath, 'utf-8');
|
||||
} else {
|
||||
await fs.writeFile(RPiConfigBackend.bootConfigPath, '');
|
||||
await fs.writeFile(ConfigTxt.bootConfigPath, '');
|
||||
}
|
||||
|
||||
const conf: ConfigOptions = {};
|
||||
@ -78,7 +75,7 @@ export class RPiConfigBackend extends DeviceConfigBackend {
|
||||
let keyValue = /^([^=]+)=(.*)$/.exec(configStr);
|
||||
if (keyValue != null) {
|
||||
const [, key, value] = keyValue;
|
||||
if (!RPiConfigBackend.arrayConfigKeys.includes(key)) {
|
||||
if (!ConfigTxt.arrayConfigKeys.includes(key)) {
|
||||
conf[key] = value;
|
||||
} else {
|
||||
if (conf[key] == null) {
|
||||
@ -119,23 +116,23 @@ export class RPiConfigBackend extends DeviceConfigBackend {
|
||||
}
|
||||
});
|
||||
const confStr = `${confStatements.join('\n')}\n`;
|
||||
await remountAndWriteAtomic(RPiConfigBackend.bootConfigPath, confStr);
|
||||
await remountAndWriteAtomic(ConfigTxt.bootConfigPath, confStr);
|
||||
}
|
||||
|
||||
public isSupportedConfig(configName: string): boolean {
|
||||
return !RPiConfigBackend.forbiddenConfigKeys.includes(configName);
|
||||
return !ConfigTxt.forbiddenConfigKeys.includes(configName);
|
||||
}
|
||||
|
||||
public isBootConfigVar(envVar: string): boolean {
|
||||
return envVar.startsWith(RPiConfigBackend.bootConfigVarPrefix);
|
||||
return envVar.startsWith(ConfigTxt.bootConfigVarPrefix);
|
||||
}
|
||||
|
||||
public processConfigVarName(envVar: string): string {
|
||||
return envVar.replace(RPiConfigBackend.bootConfigVarRegex, '$1');
|
||||
return envVar.replace(ConfigTxt.bootConfigVarRegex, '$1');
|
||||
}
|
||||
|
||||
public processConfigVarValue(key: string, value: string): string | string[] {
|
||||
if (RPiConfigBackend.arrayConfigKeys.includes(key)) {
|
||||
if (ConfigTxt.arrayConfigKeys.includes(key)) {
|
||||
if (!value.startsWith('"')) {
|
||||
return [value];
|
||||
} else {
|
||||
@ -146,6 +143,6 @@ export class RPiConfigBackend extends DeviceConfigBackend {
|
||||
}
|
||||
|
||||
public createConfigVarName(configName: string): string {
|
||||
return RPiConfigBackend.bootConfigVarPrefix + configName;
|
||||
return ConfigTxt.bootConfigVarPrefix + configName;
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import * as semver from 'semver';
|
||||
|
||||
import {
|
||||
ConfigOptions,
|
||||
DeviceConfigBackend,
|
||||
ConfigBackend,
|
||||
bootMountPoint,
|
||||
remountAndWriteAtomic,
|
||||
} from './backend';
|
||||
@ -29,7 +29,7 @@ const EXTLINUX_READONLY = '2.47.0';
|
||||
* - {BALENA|RESIN}_HOST_EXTLINUX_fdt = value | "value"
|
||||
*/
|
||||
|
||||
export class ExtlinuxConfigBackend extends DeviceConfigBackend {
|
||||
export class Extlinux extends ConfigBackend {
|
||||
private static bootConfigVarPrefix = `${constants.hostConfigVarPrefix}EXTLINUX_`;
|
||||
private static bootConfigPath = `${bootMountPoint}/extlinux/extlinux.conf`;
|
||||
private static supportedConfigValues = ['isolcpus', 'fdt'];
|
||||
@ -38,13 +38,11 @@ export class ExtlinuxConfigBackend extends DeviceConfigBackend {
|
||||
private fdtDirective = new FDTDirective();
|
||||
private appendDirective = new AppendDirective(
|
||||
// Pass in list of supportedConfigValues that APPEND can have
|
||||
ExtlinuxConfigBackend.supportedConfigValues.filter(
|
||||
(v) => !this.isDirective(v),
|
||||
),
|
||||
Extlinux.supportedConfigValues.filter((v) => !this.isDirective(v)),
|
||||
);
|
||||
|
||||
public static bootConfigVarRegex = new RegExp(
|
||||
'(?:' + _.escapeRegExp(ExtlinuxConfigBackend.bootConfigVarPrefix) + ')(.+)',
|
||||
'(?:' + _.escapeRegExp(Extlinux.bootConfigVarPrefix) + ')(.+)',
|
||||
);
|
||||
|
||||
public async matches(
|
||||
@ -63,10 +61,7 @@ export class ExtlinuxConfigBackend extends DeviceConfigBackend {
|
||||
let confContents: string;
|
||||
|
||||
try {
|
||||
confContents = await fs.readFile(
|
||||
ExtlinuxConfigBackend.bootConfigPath,
|
||||
'utf-8',
|
||||
);
|
||||
confContents = await fs.readFile(Extlinux.bootConfigPath, 'utf-8');
|
||||
} catch {
|
||||
// In the rare case where the user might have deleted extlinux conf file between linux boot and supervisor boot
|
||||
// We do not have any backup to fallback too; warn the user of a possible brick
|
||||
@ -76,18 +71,13 @@ export class ExtlinuxConfigBackend extends DeviceConfigBackend {
|
||||
}
|
||||
|
||||
// Parse ExtlinuxFile from file contents
|
||||
const parsedBootFile = ExtlinuxConfigBackend.parseExtlinuxFile(
|
||||
confContents,
|
||||
);
|
||||
const parsedBootFile = Extlinux.parseExtlinuxFile(confContents);
|
||||
|
||||
// Get default label to know which label entry to parse
|
||||
const defaultLabel = ExtlinuxConfigBackend.findDefaultLabel(parsedBootFile);
|
||||
const defaultLabel = Extlinux.findDefaultLabel(parsedBootFile);
|
||||
|
||||
// Get the label entry we will parse
|
||||
const labelEntry = ExtlinuxConfigBackend.getLabelEntry(
|
||||
parsedBootFile,
|
||||
defaultLabel,
|
||||
);
|
||||
const labelEntry = Extlinux.getLabelEntry(parsedBootFile, defaultLabel);
|
||||
|
||||
// Parse APPEND directive and filter out unsupported values
|
||||
const appendConfig = _.pickBy(
|
||||
@ -109,10 +99,7 @@ export class ExtlinuxConfigBackend extends DeviceConfigBackend {
|
||||
let confContents: string;
|
||||
|
||||
try {
|
||||
confContents = await fs.readFile(
|
||||
ExtlinuxConfigBackend.bootConfigPath,
|
||||
'utf-8',
|
||||
);
|
||||
confContents = await fs.readFile(Extlinux.bootConfigPath, 'utf-8');
|
||||
} catch {
|
||||
// In the rare case where the user might have deleted extlinux conf file between linux boot and supervisor boot
|
||||
// We do not have any backup to fallback too; warn the user of a possible brick
|
||||
@ -122,18 +109,13 @@ export class ExtlinuxConfigBackend extends DeviceConfigBackend {
|
||||
}
|
||||
|
||||
// Parse ExtlinuxFile from file contents
|
||||
const parsedBootFile = ExtlinuxConfigBackend.parseExtlinuxFile(
|
||||
confContents,
|
||||
);
|
||||
const parsedBootFile = Extlinux.parseExtlinuxFile(confContents);
|
||||
|
||||
// Get default label to know which label entry to edit
|
||||
const defaultLabel = ExtlinuxConfigBackend.findDefaultLabel(parsedBootFile);
|
||||
const defaultLabel = Extlinux.findDefaultLabel(parsedBootFile);
|
||||
|
||||
// Get the label entry we will edit
|
||||
const defaultEntry = ExtlinuxConfigBackend.getLabelEntry(
|
||||
parsedBootFile,
|
||||
defaultLabel,
|
||||
);
|
||||
const defaultEntry = Extlinux.getLabelEntry(parsedBootFile, defaultLabel);
|
||||
|
||||
// Set `FDT` directive if a value is provided
|
||||
if (opts.fdt) {
|
||||
@ -155,21 +137,21 @@ export class ExtlinuxConfigBackend extends DeviceConfigBackend {
|
||||
|
||||
// Write new extlinux configuration
|
||||
return await remountAndWriteAtomic(
|
||||
ExtlinuxConfigBackend.bootConfigPath,
|
||||
ExtlinuxConfigBackend.extlinuxFileToString(parsedBootFile),
|
||||
Extlinux.bootConfigPath,
|
||||
Extlinux.extlinuxFileToString(parsedBootFile),
|
||||
);
|
||||
}
|
||||
|
||||
public isSupportedConfig(configName: string): boolean {
|
||||
return ExtlinuxConfigBackend.supportedConfigValues.includes(configName);
|
||||
return Extlinux.supportedConfigValues.includes(configName);
|
||||
}
|
||||
|
||||
public isBootConfigVar(envVar: string): boolean {
|
||||
return envVar.startsWith(ExtlinuxConfigBackend.bootConfigVarPrefix);
|
||||
return envVar.startsWith(Extlinux.bootConfigVarPrefix);
|
||||
}
|
||||
|
||||
public processConfigVarName(envVar: string): string {
|
||||
return envVar.replace(ExtlinuxConfigBackend.bootConfigVarRegex, '$1');
|
||||
return envVar.replace(Extlinux.bootConfigVarRegex, '$1');
|
||||
}
|
||||
|
||||
public processConfigVarValue(_key: string, value: string): string {
|
||||
@ -177,13 +159,11 @@ export class ExtlinuxConfigBackend extends DeviceConfigBackend {
|
||||
}
|
||||
|
||||
public createConfigVarName(configName: string): string {
|
||||
return `${ExtlinuxConfigBackend.bootConfigVarPrefix}${configName}`;
|
||||
return `${Extlinux.bootConfigVarPrefix}${configName}`;
|
||||
}
|
||||
|
||||
private isDirective(configName: string): boolean {
|
||||
return ExtlinuxConfigBackend.supportedDirectives.includes(
|
||||
configName.toUpperCase(),
|
||||
);
|
||||
return Extlinux.supportedDirectives.includes(configName.toUpperCase());
|
||||
}
|
||||
|
||||
private static parseExtlinuxFile(confStr: string): ExtlinuxFile {
|
||||
|
@ -3,7 +3,7 @@ import { fs } from 'mz';
|
||||
|
||||
import {
|
||||
ConfigOptions,
|
||||
DeviceConfigBackend,
|
||||
ConfigBackend,
|
||||
bootMountPoint,
|
||||
remountAndWriteAtomic,
|
||||
} from './backend';
|
||||
@ -40,7 +40,7 @@ const OPTION_REGEX = /^\s*(\w+)=(.*)$/;
|
||||
* - {BALENA|RESIN}_HOST_EXTLINUX_fdt = value | "value"
|
||||
*/
|
||||
|
||||
export class ExtraUEnvConfigBackend extends DeviceConfigBackend {
|
||||
export class ExtraUEnv extends ConfigBackend {
|
||||
private static bootConfigVarPrefix = `${constants.hostConfigVarPrefix}EXTLINUX_`;
|
||||
private static bootConfigPath = `${bootMountPoint}/extra_uEnv.txt`;
|
||||
|
||||
@ -50,29 +50,27 @@ export class ExtraUEnvConfigBackend extends DeviceConfigBackend {
|
||||
};
|
||||
|
||||
private static supportedConfigs: Dictionary<Entry> = {
|
||||
fdt: ExtraUEnvConfigBackend.entries['custom_fdt_file'],
|
||||
isolcpus: ExtraUEnvConfigBackend.entries['extra_os_cmdline'],
|
||||
fdt: ExtraUEnv.entries['custom_fdt_file'],
|
||||
isolcpus: ExtraUEnv.entries['extra_os_cmdline'],
|
||||
};
|
||||
|
||||
public static bootConfigVarRegex = new RegExp(
|
||||
'(?:' +
|
||||
_.escapeRegExp(ExtraUEnvConfigBackend.bootConfigVarPrefix) +
|
||||
')(.+)',
|
||||
'(?:' + _.escapeRegExp(ExtraUEnv.bootConfigVarPrefix) + ')(.+)',
|
||||
);
|
||||
|
||||
public async matches(deviceType: string): Promise<boolean> {
|
||||
return (
|
||||
(deviceType === 'intel-nuc' || deviceType.startsWith('jetson')) &&
|
||||
(await fs.exists(ExtraUEnvConfigBackend.bootConfigPath))
|
||||
(await fs.exists(ExtraUEnv.bootConfigPath))
|
||||
);
|
||||
}
|
||||
|
||||
public async getBootConfig(): Promise<ConfigOptions> {
|
||||
// Get config contents at bootConfigPath
|
||||
const confContents = await ExtraUEnvConfigBackend.readBootConfigPath();
|
||||
const confContents = await ExtraUEnv.readBootConfigPath();
|
||||
|
||||
// Parse ConfigOptions from bootConfigPath contents
|
||||
const parsedConfigFile = ExtraUEnvConfigBackend.parseOptions(confContents);
|
||||
const parsedConfigFile = ExtraUEnv.parseOptions(confContents);
|
||||
|
||||
// Filter out unsupported values
|
||||
return _.pickBy(parsedConfigFile, (_value, key) =>
|
||||
@ -92,24 +90,21 @@ export class ExtraUEnvConfigBackend extends DeviceConfigBackend {
|
||||
|
||||
// Write new extra_uEnv configuration
|
||||
return await remountAndWriteAtomic(
|
||||
ExtraUEnvConfigBackend.bootConfigPath,
|
||||
ExtraUEnvConfigBackend.configToString(supportedOptions),
|
||||
ExtraUEnv.bootConfigPath,
|
||||
ExtraUEnv.configToString(supportedOptions),
|
||||
);
|
||||
}
|
||||
|
||||
public isSupportedConfig(config: string): boolean {
|
||||
return config in ExtraUEnvConfigBackend.supportedConfigs;
|
||||
return config in ExtraUEnv.supportedConfigs;
|
||||
}
|
||||
|
||||
public isBootConfigVar(envVar: string): boolean {
|
||||
return envVar.startsWith(ExtraUEnvConfigBackend.bootConfigVarPrefix);
|
||||
return envVar.startsWith(ExtraUEnv.bootConfigVarPrefix);
|
||||
}
|
||||
|
||||
public processConfigVarName(envVar: string): string | null {
|
||||
const name = envVar.replace(
|
||||
ExtraUEnvConfigBackend.bootConfigVarRegex,
|
||||
'$1',
|
||||
);
|
||||
const name = envVar.replace(ExtraUEnv.bootConfigVarRegex, '$1');
|
||||
if (name === envVar) {
|
||||
return null;
|
||||
}
|
||||
@ -124,7 +119,7 @@ export class ExtraUEnvConfigBackend extends DeviceConfigBackend {
|
||||
if (configName === '') {
|
||||
return null;
|
||||
}
|
||||
return `${ExtraUEnvConfigBackend.bootConfigVarPrefix}${configName}`;
|
||||
return `${ExtraUEnv.bootConfigVarPrefix}${configName}`;
|
||||
}
|
||||
|
||||
private static parseOptions(configFile: string): ConfigOptions {
|
||||
@ -143,7 +138,7 @@ export class ExtraUEnvConfigBackend extends DeviceConfigBackend {
|
||||
}
|
||||
// Merge new option with existing options
|
||||
return {
|
||||
...ExtraUEnvConfigBackend.parseOption(optionValues),
|
||||
...ExtraUEnv.parseOption(optionValues),
|
||||
...options,
|
||||
};
|
||||
}, {});
|
||||
@ -152,13 +147,13 @@ export class ExtraUEnvConfigBackend extends DeviceConfigBackend {
|
||||
private static parseOption(optionArray: string[]): ConfigOptions {
|
||||
const [, KEY, VALUE] = optionArray;
|
||||
// Check if this key's value is a collection
|
||||
if (ExtraUEnvConfigBackend.entries[KEY as EntryKey]?.collection) {
|
||||
if (ExtraUEnv.entries[KEY as EntryKey]?.collection) {
|
||||
// Return split collection of options
|
||||
return ExtraUEnvConfigBackend.parseOptionCollection(VALUE);
|
||||
return ExtraUEnv.parseOptionCollection(VALUE);
|
||||
}
|
||||
// Find the option that belongs to this entry
|
||||
const optionKey = _.findKey(
|
||||
ExtraUEnvConfigBackend.supportedConfigs,
|
||||
ExtraUEnv.supportedConfigs,
|
||||
(config) => config.key === KEY,
|
||||
);
|
||||
// Check if we found a corresponding option for this entry
|
||||
@ -198,12 +193,12 @@ export class ExtraUEnvConfigBackend extends DeviceConfigBackend {
|
||||
|
||||
private static async readBootConfigPath(): Promise<string> {
|
||||
try {
|
||||
return await fs.readFile(ExtraUEnvConfigBackend.bootConfigPath, 'utf-8');
|
||||
return await fs.readFile(ExtraUEnv.bootConfigPath, 'utf-8');
|
||||
} catch {
|
||||
// In the rare case where the user might have deleted extra_uEnv conf file between linux boot and supervisor boot
|
||||
// We do not have any backup to fallback too; warn the user of a possible brick
|
||||
log.error(
|
||||
`Unable to read extra_uEnv file at: ${ExtraUEnvConfigBackend.bootConfigPath}`,
|
||||
`Unable to read extra_uEnv file at: ${ExtraUEnv.bootConfigPath}`,
|
||||
);
|
||||
throw new ExtraUEnvError(
|
||||
'Could not find extra_uEnv file. Device is possibly bricked',
|
||||
@ -213,7 +208,7 @@ export class ExtraUEnvConfigBackend extends DeviceConfigBackend {
|
||||
|
||||
private static configToString(configs: ConfigOptions): string {
|
||||
// Get Map of ConfigOptions object
|
||||
const configMap = ExtraUEnvConfigBackend.configToMap(configs);
|
||||
const configMap = ExtraUEnv.configToMap(configs);
|
||||
// Iterator over configMap and concat to configString
|
||||
let configString = '';
|
||||
for (const [key, value] of configMap) {
|
||||
@ -230,12 +225,12 @@ export class ExtraUEnvConfigBackend extends DeviceConfigBackend {
|
||||
const {
|
||||
key: ENTRY_KEY,
|
||||
collection: ENTRY_IS_COLLECTION,
|
||||
} = ExtraUEnvConfigBackend.supportedConfigs[configKey];
|
||||
} = ExtraUEnv.supportedConfigs[configKey];
|
||||
// Check if we have to build the value for the entry
|
||||
if (ENTRY_IS_COLLECTION) {
|
||||
return configMap.set(
|
||||
ENTRY_KEY,
|
||||
ExtraUEnvConfigBackend.appendToCollection(
|
||||
ExtraUEnv.appendToCollection(
|
||||
configMap.get(ENTRY_KEY),
|
||||
configKey,
|
||||
configValue,
|
||||
|
11
src/config/backends/index.ts
Normal file
11
src/config/backends/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Extlinux } from './extlinux';
|
||||
import { ExtraUEnv } from './extra-uEnv';
|
||||
import { ConfigTxt } from './config-txt';
|
||||
import { ConfigFs } from './config-fs';
|
||||
|
||||
export default [
|
||||
new Extlinux(),
|
||||
new ExtraUEnv(),
|
||||
new ConfigTxt(),
|
||||
new ConfigFs(),
|
||||
];
|
@ -1,51 +1,32 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as Bluebird from 'bluebird';
|
||||
|
||||
import * as config from '../config';
|
||||
import * as constants from '../lib/constants';
|
||||
import { getMetaOSRelease } from '../lib/os-release';
|
||||
import { EnvVarObject } from '../lib/types';
|
||||
import { ExtlinuxConfigBackend } from './backends/extlinux';
|
||||
import { ExtraUEnvConfigBackend } from './backends/extra-uEnv';
|
||||
import { RPiConfigBackend } from './backends/raspberry-pi';
|
||||
import { ConfigfsConfigBackend } from './backends/config-fs';
|
||||
import { ConfigOptions, DeviceConfigBackend } from './backends/backend';
|
||||
import Backends from './backends';
|
||||
import { ConfigOptions, ConfigBackend } from './backends/backend';
|
||||
|
||||
const configBackends = [
|
||||
new ExtlinuxConfigBackend(),
|
||||
new ExtraUEnvConfigBackend(),
|
||||
new RPiConfigBackend(),
|
||||
new ConfigfsConfigBackend(),
|
||||
];
|
||||
|
||||
export const initialiseConfigBackend = async (deviceType: string) => {
|
||||
const backend = await getConfigBackend(deviceType);
|
||||
if (backend) {
|
||||
await backend.initialise();
|
||||
return backend;
|
||||
}
|
||||
};
|
||||
|
||||
async function getConfigBackend(
|
||||
deviceType: string,
|
||||
): Promise<DeviceConfigBackend | undefined> {
|
||||
// Some backends are only supported by certain release versions so pass in metaRelease
|
||||
const metaRelease = await getMetaOSRelease(constants.hostOSVersionPath);
|
||||
let matched;
|
||||
for (const backend of configBackends) {
|
||||
if (await backend.matches(deviceType, metaRelease)) {
|
||||
matched = backend;
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
export async function getSupportedBackends(): Promise<ConfigBackend[]> {
|
||||
// Get required information to find supported backends
|
||||
const [deviceType, metaRelease] = await Promise.all([
|
||||
config.get('deviceType'),
|
||||
getMetaOSRelease(constants.hostOSVersionPath),
|
||||
]);
|
||||
// Return list of configurable backends that match this deviceType and metaRelease
|
||||
return Bluebird.filter(Backends, (backend: ConfigBackend) =>
|
||||
backend.matches(deviceType, metaRelease),
|
||||
);
|
||||
}
|
||||
|
||||
export function envToBootConfig(
|
||||
configBackend: DeviceConfigBackend | null,
|
||||
configBackend: ConfigBackend | null,
|
||||
env: EnvVarObject,
|
||||
): ConfigOptions {
|
||||
if (configBackend == null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return _(env)
|
||||
.pickBy((_val, key) => configBackend.isBootConfigVar(key))
|
||||
.mapKeys((_val, key) => configBackend.processConfigVarName(key))
|
||||
@ -56,10 +37,10 @@ export function envToBootConfig(
|
||||
}
|
||||
|
||||
export function bootConfigToEnv(
|
||||
configBackend: DeviceConfigBackend,
|
||||
config: ConfigOptions,
|
||||
configBackend: ConfigBackend,
|
||||
configOptions: ConfigOptions,
|
||||
): EnvVarObject {
|
||||
return _(config)
|
||||
return _(configOptions)
|
||||
.mapKeys((_val, key) => configBackend.createConfigVarName(key))
|
||||
.mapValues((val) => {
|
||||
if (_.isArray(val)) {
|
||||
@ -70,22 +51,8 @@ export function bootConfigToEnv(
|
||||
.value();
|
||||
}
|
||||
|
||||
function filterNamespaceFromConfig(
|
||||
namespace: RegExp,
|
||||
conf: { [key: string]: any },
|
||||
): { [key: string]: any } {
|
||||
return _.mapKeys(
|
||||
_.pickBy(conf, (_v, k) => {
|
||||
return namespace.test(k);
|
||||
}),
|
||||
(_v, k) => {
|
||||
return k.replace(namespace, '$1');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function formatConfigKeys(
|
||||
configBackend: DeviceConfigBackend | null,
|
||||
configBackend: ConfigBackend | null,
|
||||
allowedKeys: string[],
|
||||
conf: { [key: string]: any },
|
||||
): { [key: string]: any } {
|
||||
@ -113,3 +80,17 @@ export function formatConfigKeys(
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function filterNamespaceFromConfig(
|
||||
namespace: RegExp,
|
||||
conf: { [key: string]: any },
|
||||
): { [key: string]: any } {
|
||||
return _.mapKeys(
|
||||
_.pickBy(conf, (_v, k) => {
|
||||
return namespace.test(k);
|
||||
}),
|
||||
(_v, k) => {
|
||||
return k.replace(namespace, '$1');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { SchemaTypeKey } from './config/schema-type';
|
||||
import * as db from './db';
|
||||
import * as logger from './logger';
|
||||
|
||||
import { ConfigOptions, DeviceConfigBackend } from './config/backends/backend';
|
||||
import { ConfigOptions, ConfigBackend } from './config/backends/backend';
|
||||
import * as configUtils from './config/utils';
|
||||
import * as dbus from './lib/dbus';
|
||||
import { UnitNotLoadedError } from './lib/errors';
|
||||
@ -100,15 +100,17 @@ const actionExecutors: DeviceActionExecutors = {
|
||||
}
|
||||
},
|
||||
setBootConfig: async (step) => {
|
||||
const $configBackend = await getConfigBackend();
|
||||
if (!_.isObject(step.target)) {
|
||||
throw new Error('Non-dictionary passed to DeviceConfig.setBootConfig');
|
||||
}
|
||||
await setBootConfig($configBackend, step.target as Dictionary<string>);
|
||||
const backends = await getConfigBackends();
|
||||
for (const backend of backends) {
|
||||
await setBootConfig(backend, step.target as Dictionary<string>);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let configBackend: DeviceConfigBackend | null = null;
|
||||
const configBackends: ConfigBackend[] = [];
|
||||
|
||||
const configKeys: Dictionary<ConfigOption> = {
|
||||
appUpdatePollInterval: {
|
||||
@ -206,14 +208,19 @@ const rateLimits: Dictionary<{
|
||||
},
|
||||
};
|
||||
|
||||
async function getConfigBackend() {
|
||||
if (configBackend != null) {
|
||||
return configBackend;
|
||||
async function getConfigBackends(): Promise<ConfigBackend[]> {
|
||||
// Exit early if we already have a list
|
||||
if (configBackends.length > 0) {
|
||||
return configBackends;
|
||||
}
|
||||
const dt = await config.get('deviceType');
|
||||
configBackend = (await configUtils.initialiseConfigBackend(dt)) ?? null;
|
||||
|
||||
return configBackend;
|
||||
// Get all the configurable backends this device supports
|
||||
const backends = await configUtils.getSupportedBackends();
|
||||
// Initialize each backend
|
||||
for (const backend of backends) {
|
||||
await backend.initialise();
|
||||
}
|
||||
// Return list of initialized ConfigBackends
|
||||
return backends;
|
||||
}
|
||||
|
||||
export async function setTarget(
|
||||
@ -264,37 +271,45 @@ export async function getTarget({
|
||||
return conf;
|
||||
}
|
||||
|
||||
export async function getCurrent() {
|
||||
export async function getCurrent(): Promise<Dictionary<string>> {
|
||||
// Build a Dictionary of currently set config values
|
||||
const currentConf: Dictionary<string> = {};
|
||||
// Get environment variables
|
||||
const conf = await config.getMany(
|
||||
['deviceType'].concat(_.keys(configKeys)) as SchemaTypeKey[],
|
||||
);
|
||||
|
||||
const $configBackend = await getConfigBackend();
|
||||
|
||||
const [vpnStatus, bootConfig] = await Promise.all([
|
||||
getVPNEnabled(),
|
||||
getBootConfig($configBackend),
|
||||
]);
|
||||
|
||||
const currentConf: Dictionary<string> = {
|
||||
// TODO: Fix this mess of half strings half boolean values everywhere
|
||||
SUPERVISOR_VPN_CONTROL: vpnStatus != null ? vpnStatus.toString() : 'true',
|
||||
};
|
||||
|
||||
// Add each value
|
||||
for (const key of _.keys(configKeys)) {
|
||||
const { envVarName } = configKeys[key];
|
||||
const confValue = conf[key as SchemaTypeKey];
|
||||
currentConf[envVarName] = confValue != null ? confValue.toString() : '';
|
||||
}
|
||||
|
||||
return _.assign(currentConf, bootConfig);
|
||||
// Add VPN information
|
||||
currentConf['SUPERVISOR_VPN_CONTROL'] = (await isVPNEnabled())
|
||||
? 'true'
|
||||
: 'false';
|
||||
// Get list of configurable backends
|
||||
const backends = await getConfigBackends();
|
||||
// Add each backends configurable values
|
||||
for (const backend of backends) {
|
||||
_.assign(currentConf, await getBootConfig(backend));
|
||||
}
|
||||
// Return compiled configuration
|
||||
return currentConf;
|
||||
}
|
||||
|
||||
export async function formatConfigKeys(
|
||||
conf: Dictionary<string>,
|
||||
): Promise<Dictionary<any>> {
|
||||
const backend = await getConfigBackend();
|
||||
return configUtils.formatConfigKeys(backend, validKeys, conf);
|
||||
const backends = await getConfigBackends();
|
||||
const formattedKeys: Dictionary<any> = {};
|
||||
for (const backend of backends) {
|
||||
_.assign(
|
||||
formattedKeys,
|
||||
configUtils.formatConfigKeys(backend, validKeys, conf),
|
||||
);
|
||||
}
|
||||
return formattedKeys;
|
||||
}
|
||||
|
||||
export function getDefaults() {
|
||||
@ -314,16 +329,13 @@ export function resetRateLimits() {
|
||||
|
||||
// Exported for tests
|
||||
export function bootConfigChangeRequired(
|
||||
$configBackend: DeviceConfigBackend | null,
|
||||
configBackend: ConfigBackend | null,
|
||||
current: Dictionary<string>,
|
||||
target: Dictionary<string>,
|
||||
deviceType: string,
|
||||
): boolean {
|
||||
const targetBootConfig = configUtils.envToBootConfig($configBackend, target);
|
||||
const currentBootConfig = configUtils.envToBootConfig(
|
||||
$configBackend,
|
||||
current,
|
||||
);
|
||||
const targetBootConfig = configUtils.envToBootConfig(configBackend, target);
|
||||
const currentBootConfig = configUtils.envToBootConfig(configBackend, current);
|
||||
|
||||
// Some devices require specific overlays, here we apply them
|
||||
ensureRequiredOverlay(deviceType, targetBootConfig);
|
||||
@ -331,7 +343,7 @@ export function bootConfigChangeRequired(
|
||||
if (!_.isEqual(currentBootConfig, targetBootConfig)) {
|
||||
_.each(targetBootConfig, (value, key) => {
|
||||
// Ignore null check because we can't get here if configBackend is null
|
||||
if (!$configBackend!.isSupportedConfig(key)) {
|
||||
if (!configBackend!.isSupportedConfig(key)) {
|
||||
if (currentBootConfig[key] !== value) {
|
||||
const err = `Attempt to change blacklisted config value ${key}`;
|
||||
logger.logSystemMessage(
|
||||
@ -369,7 +381,6 @@ export async function getRequiredSteps(
|
||||
'deviceType',
|
||||
'unmanaged',
|
||||
]);
|
||||
const backend = await getConfigBackend();
|
||||
|
||||
const configChanges: Dictionary<string> = {};
|
||||
const humanReadableConfigChanges: Dictionary<string> = {};
|
||||
@ -455,13 +466,16 @@ export async function getRequiredSteps(
|
||||
return step;
|
||||
});
|
||||
|
||||
// Do we need to change the boot config?
|
||||
if (bootConfigChangeRequired(backend, current, target, deviceType)) {
|
||||
steps.push({
|
||||
action: 'setBootConfig',
|
||||
target,
|
||||
});
|
||||
}
|
||||
const backends = await getConfigBackends();
|
||||
// Check for required bootConfig changes
|
||||
backends.forEach((backend) => {
|
||||
if (bootConfigChangeRequired(backend, current, target, deviceType)) {
|
||||
steps.push({
|
||||
action: 'setBootConfig',
|
||||
target,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Check if there is either no steps, or they are all
|
||||
// noops, and we need to reboot. We want to do this
|
||||
@ -492,7 +506,7 @@ export function isValidAction(action: string): boolean {
|
||||
}
|
||||
|
||||
export async function getBootConfig(
|
||||
backend: DeviceConfigBackend | null,
|
||||
backend: ConfigBackend | null,
|
||||
): Promise<EnvVarObject> {
|
||||
if (backend == null) {
|
||||
return {};
|
||||
@ -503,7 +517,7 @@ export async function getBootConfig(
|
||||
|
||||
// Exported for tests
|
||||
export async function setBootConfig(
|
||||
backend: DeviceConfigBackend | null,
|
||||
backend: ConfigBackend | null,
|
||||
target: Dictionary<string>,
|
||||
) {
|
||||
if (backend == null) {
|
||||
@ -539,7 +553,7 @@ export async function setBootConfig(
|
||||
}
|
||||
}
|
||||
|
||||
async function getVPNEnabled(): Promise<boolean> {
|
||||
async function isVPNEnabled(): Promise<boolean> {
|
||||
try {
|
||||
const activeState = await dbus.serviceActiveState(vpnServiceName);
|
||||
return !_.includes(['inactive', 'deactivating'], activeState);
|
||||
|
@ -10,7 +10,7 @@ import Log from '../src/lib/supervisor-console';
|
||||
import * as dockerUtils from '../src/lib/docker-utils';
|
||||
import * as config from '../src/config';
|
||||
import * as images from '../src/compose/images';
|
||||
import { RPiConfigBackend } from '../src/config/backends/raspberry-pi';
|
||||
import { ConfigTxt } from '../src/config/backends/config-txt';
|
||||
import DeviceState from '../src/device-state';
|
||||
import * as deviceConfig from '../src/device-config';
|
||||
import { loadTargetFromFile } from '../src/device-state/preload';
|
||||
@ -254,7 +254,7 @@ describe('deviceState', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error Assigning to a RO property
|
||||
deviceConfig.configBackend = new RPiConfigBackend();
|
||||
deviceConfig.configBackend = new ConfigTxt();
|
||||
|
||||
// @ts-expect-error Assigning to a RO property
|
||||
deviceConfig.getCurrent = async () => mockedInitialConfig;
|
||||
|
@ -6,12 +6,12 @@ import { expect } from './lib/chai-config';
|
||||
import * as deviceConfig from '../src/device-config';
|
||||
import * as fsUtils from '../src/lib/fs-utils';
|
||||
import * as logger from '../src/logger';
|
||||
import { ExtlinuxConfigBackend } from '../src/config/backends/extlinux';
|
||||
import { RPiConfigBackend } from '../src/config/backends/raspberry-pi';
|
||||
import { Extlinux } from '../src/config/backends/extlinux';
|
||||
import { ConfigTxt } from '../src/config/backends/config-txt';
|
||||
import prepare = require('./lib/prepare');
|
||||
|
||||
const extlinuxBackend = new ExtlinuxConfigBackend();
|
||||
const rpiConfigBackend = new RPiConfigBackend();
|
||||
const extlinuxBackend = new Extlinux();
|
||||
const configTxtBackend = new ConfigTxt();
|
||||
|
||||
describe('Device Backend Config', () => {
|
||||
let logSpy: SinonSpy;
|
||||
@ -33,7 +33,7 @@ describe('Device Backend Config', () => {
|
||||
// Will try to parse /test/data/mnt/boot/config.txt
|
||||
await expect(
|
||||
// @ts-ignore accessing private value
|
||||
deviceConfig.getBootConfig(rpiConfigBackend),
|
||||
deviceConfig.getBootConfig(configTxtBackend),
|
||||
).to.eventually.deep.equal({
|
||||
HOST_CONFIG_dtparam: '"i2c_arm=on","spi=on","audio=on"',
|
||||
HOST_CONFIG_enable_uart: '1',
|
||||
@ -54,7 +54,7 @@ describe('Device Backend Config', () => {
|
||||
|
||||
await expect(
|
||||
// @ts-ignore accessing private value
|
||||
deviceConfig.getBootConfig(rpiConfigBackend),
|
||||
deviceConfig.getBootConfig(configTxtBackend),
|
||||
).to.eventually.deep.equal({
|
||||
HOST_CONFIG_initramfs: 'initramf.gz 0x00800000',
|
||||
HOST_CONFIG_dtparam: '"i2c=on","audio=on"',
|
||||
@ -83,7 +83,7 @@ describe('Device Backend Config', () => {
|
||||
|
||||
expect(() =>
|
||||
// @ts-ignore accessing private value
|
||||
deviceConfig.bootConfigChangeRequired(rpiConfigBackend, current, target),
|
||||
deviceConfig.bootConfigChangeRequired(configTxtBackend, current, target),
|
||||
).to.throw('Attempt to change blacklisted config value initramfs');
|
||||
|
||||
// Check if logs were called
|
||||
@ -115,7 +115,7 @@ describe('Device Backend Config', () => {
|
||||
|
||||
expect(
|
||||
// @ts-ignore accessing private value
|
||||
deviceConfig.bootConfigChangeRequired(rpiConfigBackend, current, target),
|
||||
deviceConfig.bootConfigChangeRequired(configTxtBackend, current, target),
|
||||
).to.equal(false);
|
||||
expect(logSpy).to.not.be.called;
|
||||
});
|
||||
@ -140,11 +140,11 @@ describe('Device Backend Config', () => {
|
||||
|
||||
expect(
|
||||
// @ts-ignore accessing private value
|
||||
deviceConfig.bootConfigChangeRequired(rpiConfigBackend, current, target),
|
||||
deviceConfig.bootConfigChangeRequired(configTxtBackend, current, target),
|
||||
).to.equal(true);
|
||||
|
||||
// @ts-ignore accessing private value
|
||||
await deviceConfig.setBootConfig(rpiConfigBackend, target);
|
||||
await deviceConfig.setBootConfig(configTxtBackend, target);
|
||||
expect(child_process.exec).to.be.calledOnce;
|
||||
expect(logSpy).to.be.calledTwice;
|
||||
expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success');
|
||||
@ -280,7 +280,7 @@ describe('Device Backend Config', () => {
|
||||
it('should not cause a config change when the cloud does not specify the balena-fin overlay', () => {
|
||||
expect(
|
||||
deviceConfig.bootConfigChangeRequired(
|
||||
rpiConfigBackend,
|
||||
configTxtBackend,
|
||||
{ HOST_CONFIG_dtoverlay: '"test","balena-fin"' },
|
||||
{ HOST_CONFIG_dtoverlay: '"test"' },
|
||||
'fincm3',
|
||||
@ -289,7 +289,7 @@ describe('Device Backend Config', () => {
|
||||
|
||||
expect(
|
||||
deviceConfig.bootConfigChangeRequired(
|
||||
rpiConfigBackend,
|
||||
configTxtBackend,
|
||||
{ HOST_CONFIG_dtoverlay: '"test","balena-fin"' },
|
||||
{ HOST_CONFIG_dtoverlay: 'test' },
|
||||
'fincm3',
|
||||
@ -298,7 +298,7 @@ describe('Device Backend Config', () => {
|
||||
|
||||
expect(
|
||||
deviceConfig.bootConfigChangeRequired(
|
||||
rpiConfigBackend,
|
||||
configTxtBackend,
|
||||
{ HOST_CONFIG_dtoverlay: '"test","test2","balena-fin"' },
|
||||
{ HOST_CONFIG_dtoverlay: '"test","test2"' },
|
||||
'fincm3',
|
||||
@ -341,7 +341,7 @@ describe('Device Backend Config', () => {
|
||||
it('should not cause a config change when the cloud does not specify the pi4 overlay', () => {
|
||||
expect(
|
||||
deviceConfig.bootConfigChangeRequired(
|
||||
rpiConfigBackend,
|
||||
configTxtBackend,
|
||||
{ HOST_CONFIG_dtoverlay: '"test","vc4-fkms-v3d"' },
|
||||
{ HOST_CONFIG_dtoverlay: '"test"' },
|
||||
'raspberrypi4-64',
|
||||
@ -349,7 +349,7 @@ describe('Device Backend Config', () => {
|
||||
).to.equal(false);
|
||||
expect(
|
||||
deviceConfig.bootConfigChangeRequired(
|
||||
rpiConfigBackend,
|
||||
configTxtBackend,
|
||||
{ HOST_CONFIG_dtoverlay: '"test","vc4-fkms-v3d"' },
|
||||
{ HOST_CONFIG_dtoverlay: 'test' },
|
||||
'raspberrypi4-64',
|
||||
@ -357,7 +357,7 @@ describe('Device Backend Config', () => {
|
||||
).to.equal(false);
|
||||
expect(
|
||||
deviceConfig.bootConfigChangeRequired(
|
||||
rpiConfigBackend,
|
||||
configTxtBackend,
|
||||
{ HOST_CONFIG_dtoverlay: '"test","test2","vc4-fkms-v3d"' },
|
||||
{ HOST_CONFIG_dtoverlay: '"test","test2"' },
|
||||
'raspberrypi4-64',
|
||||
@ -368,7 +368,7 @@ describe('Device Backend Config', () => {
|
||||
|
||||
// describe('ConfigFS', () => {
|
||||
// const upboardConfig = new DeviceConfig();
|
||||
// let upboardConfigBackend: DeviceConfigBackend | null;
|
||||
// let upboardConfigBackend: ConfigBackend | null;
|
||||
|
||||
// before(async () => {
|
||||
// stub(child_process, 'exec').resolves();
|
||||
|
@ -1,25 +1,124 @@
|
||||
import { expect } from './lib/chai-config';
|
||||
import * as configUtils from '../src/config/utils';
|
||||
import { RPiConfigBackend } from '../src/config/backends/raspberry-pi';
|
||||
import { stub } from 'sinon';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
const rpiBackend = new RPiConfigBackend();
|
||||
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';
|
||||
import { ConfigTxt } from '../src/config/backends/config-txt';
|
||||
import { ConfigFs } from '../src/config/backends/config-fs';
|
||||
import { ConfigBackend } from '../src/config/backends/backend';
|
||||
|
||||
describe('Config Utilities', () => {
|
||||
describe('Boot config', () => {
|
||||
it('correctly transforms environments to boot config objects', () => {
|
||||
const bootConfig = configUtils.envToBootConfig(rpiBackend, {
|
||||
HOST_CONFIG_initramfs: 'initramf.gz 0x00800000',
|
||||
HOST_CONFIG_dtparam: '"i2c=on","audio=on"',
|
||||
HOST_CONFIG_dtoverlay:
|
||||
'"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"',
|
||||
HOST_CONFIG_foobar: 'baz',
|
||||
});
|
||||
expect(bootConfig).to.deep.equal({
|
||||
initramfs: 'initramf.gz 0x00800000',
|
||||
dtparam: ['i2c=on', 'audio=on'],
|
||||
dtoverlay: ['ads7846', 'lirc-rpi,gpio_out_pin=17,gpio_in_pin=13'],
|
||||
foobar: 'baz',
|
||||
});
|
||||
it('gets list of supported backends', async () => {
|
||||
// Stub so that we get an array containing only config-txt backend
|
||||
const configStub = stub(config, 'get').resolves('raspberry');
|
||||
// Get list of backends
|
||||
const devices = await configUtils.getSupportedBackends();
|
||||
expect(devices.length).to.equal(1);
|
||||
expect(devices[0].constructor.name).to.equal('ConfigTxt');
|
||||
// Restore stub
|
||||
configStub.restore();
|
||||
// TO-DO: When we have a device that will match for multiple backends
|
||||
// add a test that we get more then 1 backend for that device
|
||||
});
|
||||
|
||||
it('transforms environment variables to boot configs', () => {
|
||||
_.forEach(CONFIGS, (configObj: any, key: string) => {
|
||||
expect(
|
||||
configUtils.envToBootConfig(BACKENDS[key], configObj.envVars),
|
||||
).to.deep.equal(configObj.bootConfig);
|
||||
});
|
||||
});
|
||||
|
||||
it('transforms boot configs to environment variables', () => {
|
||||
_.forEach(CONFIGS, (configObj: any, key: string) => {
|
||||
expect(
|
||||
configUtils.bootConfigToEnv(BACKENDS[key], configObj.bootConfig),
|
||||
).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> = {
|
||||
extraUEnv: new ExtraUEnv(),
|
||||
extlinux: new Extlinux(),
|
||||
configtxt: new ConfigTxt(),
|
||||
configfs: new ConfigFs(),
|
||||
};
|
||||
|
||||
const CONFIGS = {
|
||||
extraUEnv: {
|
||||
envVars: {
|
||||
HOST_EXTLINUX_fdt: '/boot/mycustomdtb.dtb',
|
||||
HOST_EXTLINUX_isolcpus: '1,2,3',
|
||||
HOST_EXTLINUX_rootwait: '',
|
||||
},
|
||||
bootConfig: {
|
||||
fdt: '/boot/mycustomdtb.dtb',
|
||||
isolcpus: '1,2,3',
|
||||
rootwait: '',
|
||||
},
|
||||
},
|
||||
extlinux: {
|
||||
envVars: {
|
||||
HOST_EXTLINUX_fdt: '/boot/mycustomdtb.dtb',
|
||||
HOST_EXTLINUX_isolcpus: '1,2,3',
|
||||
HOST_EXTLINUX_rootwait: '',
|
||||
},
|
||||
bootConfig: {
|
||||
fdt: '/boot/mycustomdtb.dtb',
|
||||
isolcpus: '1,2,3',
|
||||
rootwait: '',
|
||||
},
|
||||
},
|
||||
configtxt: {
|
||||
envVars: {
|
||||
HOST_CONFIG_initramfs: 'initramf.gz 0x00800000',
|
||||
HOST_CONFIG_dtparam: '"i2c=on","audio=on"',
|
||||
HOST_CONFIG_dtoverlay:
|
||||
'"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"',
|
||||
HOST_CONFIG_foobar: 'baz',
|
||||
},
|
||||
bootConfig: {
|
||||
initramfs: 'initramf.gz 0x00800000',
|
||||
dtparam: ['i2c=on', 'audio=on'],
|
||||
dtoverlay: ['ads7846', 'lirc-rpi,gpio_out_pin=17,gpio_in_pin=13'],
|
||||
foobar: 'baz',
|
||||
},
|
||||
},
|
||||
// TO-DO: Config-FS is commented out because it behaves differently and doesn't
|
||||
// add value to the Config Utilities if we make it work but would like to add it
|
||||
// configfs: {
|
||||
// envVars: {
|
||||
// ssdt: 'spidev1,1'
|
||||
// },
|
||||
// bootConfig: {
|
||||
// ssdt: ['spidev1,1']
|
||||
// },
|
||||
// },
|
||||
};
|
||||
|
@ -4,10 +4,10 @@ import { SinonStub, stub } from 'sinon';
|
||||
|
||||
import { expect } from './lib/chai-config';
|
||||
import * as fsUtils from '../src/lib/fs-utils';
|
||||
import { ExtlinuxConfigBackend } from '../src/config/backends/extlinux';
|
||||
import { Extlinux } from '../src/config/backends/extlinux';
|
||||
|
||||
describe('Extlinux Configuration', () => {
|
||||
const backend = new ExtlinuxConfigBackend();
|
||||
const backend = new Extlinux();
|
||||
|
||||
it('should parse a extlinux.conf file', () => {
|
||||
const text = stripIndent`\
|
||||
@ -24,7 +24,7 @@ describe('Extlinux Configuration', () => {
|
||||
`;
|
||||
|
||||
// @ts-ignore accessing private method
|
||||
const parsed = ExtlinuxConfigBackend.parseExtlinuxFile(text);
|
||||
const parsed = Extlinux.parseExtlinuxFile(text);
|
||||
expect(parsed.globals).to.have.property('DEFAULT').that.equals('primary');
|
||||
expect(parsed.globals).to.have.property('TIMEOUT').that.equals('30');
|
||||
expect(parsed.globals)
|
||||
@ -61,7 +61,7 @@ describe('Extlinux Configuration', () => {
|
||||
`;
|
||||
|
||||
// @ts-ignore accessing private method
|
||||
const parsed = ExtlinuxConfigBackend.parseExtlinuxFile(text);
|
||||
const parsed = Extlinux.parseExtlinuxFile(text);
|
||||
expect(parsed.labels).to.have.property('primary').that.deep.equals({
|
||||
LINUX: 'test1',
|
||||
FDT: '/boot/mycustomdtb.dtb',
|
||||
|
@ -5,10 +5,10 @@ import { SinonStub, spy, stub } from 'sinon';
|
||||
import { expect } from './lib/chai-config';
|
||||
import * as fsUtils from '../src/lib/fs-utils';
|
||||
import Log from '../src/lib/supervisor-console';
|
||||
import { ExtraUEnvConfigBackend } from '../src/config/backends/extra-uEnv';
|
||||
import { ExtraUEnv } from '../src/config/backends/extra-uEnv';
|
||||
|
||||
describe('extra_uEnv Configuration', () => {
|
||||
const backend = new ExtraUEnvConfigBackend();
|
||||
const backend = new ExtraUEnv();
|
||||
let readFileStub: SinonStub;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -25,7 +25,7 @@ describe('extra_uEnv Configuration', () => {
|
||||
extra_os_cmdline=isolcpus=3,4 splash console=tty0
|
||||
`;
|
||||
// @ts-ignore accessing private method
|
||||
const parsed = ExtraUEnvConfigBackend.parseOptions(fileContents);
|
||||
const parsed = ExtraUEnv.parseOptions(fileContents);
|
||||
expect(parsed).to.deep.equal({
|
||||
fdt: 'mycustom.dtb',
|
||||
isolcpus: '3,4',
|
||||
@ -138,10 +138,10 @@ describe('extra_uEnv Configuration', () => {
|
||||
const logWarningStub = spy(Log, 'warn');
|
||||
|
||||
// @ts-ignore accessing private value
|
||||
const previousSupportedConfigs = ExtraUEnvConfigBackend.supportedConfigs;
|
||||
const previousSupportedConfigs = ExtraUEnv.supportedConfigs;
|
||||
// Stub isSupportedConfig so we can confirm collections work
|
||||
// @ts-ignore accessing private value
|
||||
ExtraUEnvConfigBackend.supportedConfigs = {
|
||||
ExtraUEnv.supportedConfigs = {
|
||||
fdt: { key: 'custom_fdt_file', collection: false },
|
||||
isolcpus: { key: 'extra_os_cmdline', collection: true },
|
||||
console: { key: 'extra_os_cmdline', collection: true },
|
||||
@ -166,7 +166,7 @@ describe('extra_uEnv Configuration', () => {
|
||||
(child_process.exec as SinonStub).restore();
|
||||
logWarningStub.restore();
|
||||
// @ts-ignore accessing private value
|
||||
ExtraUEnvConfigBackend.supportedConfigs = previousSupportedConfigs;
|
||||
ExtraUEnv.supportedConfigs = previousSupportedConfigs;
|
||||
});
|
||||
|
||||
it('only allows supported configuration options', () => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user