deviceConfig: allow BALENA_ config variables

They will take precedence over any existing RESIN_ variables. We strip both namespaces now
whenever we get the target values.

This also fixes preloading with a legacy config (the interface to get the config keys from
the legacy apps.json was broken).

Change-type: minor
Signed-off-by: Pablo Carranza Velez <pablo@balena.io>
This commit is contained in:
Pablo Carranza Velez 2018-10-17 10:47:11 +02:00
parent 658639ea65
commit 24cbfbb860
7 changed files with 153 additions and 102 deletions

View File

@ -54,15 +54,31 @@ export function bootConfigToEnv(
.value(); .value();
} }
export function filterConfigKeys( 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 filterAndFormatConfigKeys(
configBackend: DeviceConfigBackend | null, configBackend: DeviceConfigBackend | null,
allowedKeys: string[], allowedKeys: string[],
conf: { [key: string]: any }, conf: { [key: string]: any },
): { [key: string]: any } { ): { [key: string]: any } {
const isConfigType = configBackend != null; const isConfigType = configBackend != null;
const namespaceRegex = /^BALENA_(.*)/;
const legacyNamespaceRegex = /^RESIN_(.*)/;
const confFromNamespace = filterNamespaceFromConfig(namespaceRegex, conf);
const confFromLegacyNamespace = filterNamespaceFromConfig(legacyNamespaceRegex, conf);
const confWithoutNamespace = _.defaults(confFromNamespace, confFromLegacyNamespace);
return _.pickBy(conf, (_v, k) => { return _.pickBy(confWithoutNamespace, (_v, k) => {
return _.includes(allowedKeys, k) || (isConfigType && configBackend!.isBootConfigVar(k)); return _.includes(allowedKeys, k) || (isConfigType && configBackend!.isBootConfigVar(k));
}); });
} }

View File

@ -12,22 +12,22 @@ module.exports = class DeviceConfig
constructor: ({ @db, @config, @logger }) -> constructor: ({ @db, @config, @logger }) ->
@rebootRequired = false @rebootRequired = false
@configKeys = { @configKeys = {
appUpdatePollInterval: { envVarName: 'RESIN_SUPERVISOR_POLL_INTERVAL', varType: 'int', defaultValue: '60000' } appUpdatePollInterval: { envVarName: 'SUPERVISOR_POLL_INTERVAL', varType: 'int', defaultValue: '60000' }
localMode: { envVarName: 'RESIN_SUPERVISOR_LOCAL_MODE', varType: 'bool', defaultValue: 'false' } localMode: { envVarName: 'SUPERVISOR_LOCAL_MODE', varType: 'bool', defaultValue: 'false' }
connectivityCheckEnabled: { envVarName: 'RESIN_SUPERVISOR_CONNECTIVITY_CHECK', varType: 'bool', defaultValue: 'true' } connectivityCheckEnabled: { envVarName: 'SUPERVISOR_CONNECTIVITY_CHECK', varType: 'bool', defaultValue: 'true' }
loggingEnabled: { envVarName: 'RESIN_SUPERVISOR_LOG_CONTROL', varType: 'bool', defaultValue: 'true' } loggingEnabled: { envVarName: 'SUPERVISOR_LOG_CONTROL', varType: 'bool', defaultValue: 'true' }
delta: { envVarName: 'RESIN_SUPERVISOR_DELTA', varType: 'bool', defaultValue: 'false' } delta: { envVarName: 'SUPERVISOR_DELTA', varType: 'bool', defaultValue: 'false' }
deltaRequestTimeout: { envVarName: 'RESIN_SUPERVISOR_DELTA_REQUEST_TIMEOUT', varType: 'int', defaultValue: '30000' } deltaRequestTimeout: { envVarName: 'SUPERVISOR_DELTA_REQUEST_TIMEOUT', varType: 'int', defaultValue: '30000' }
deltaApplyTimeout: { envVarName: 'RESIN_SUPERVISOR_DELTA_APPLY_TIMEOUT', varType: 'int', defaultValue: '' } deltaApplyTimeout: { envVarName: 'SUPERVISOR_DELTA_APPLY_TIMEOUT', varType: 'int', defaultValue: '' }
deltaRetryCount: { envVarName: 'RESIN_SUPERVISOR_DELTA_RETRY_COUNT', varType: 'int', defaultValue: '30' } deltaRetryCount: { envVarName: 'SUPERVISOR_DELTA_RETRY_COUNT', varType: 'int', defaultValue: '30' }
deltaRetryInterval: { envVarName: 'RESIN_SUPERVISOR_DELTA_RETRY_INTERVAL', varType: 'int', defaultValue: '10000' } deltaRetryInterval: { envVarName: 'SUPERVISOR_DELTA_RETRY_INTERVAL', varType: 'int', defaultValue: '10000' }
deltaVersion: { envVarName: 'RESIN_SUPERVISOR_DELTA_VERSION', varType: 'int', defaultValue: '2' } deltaVersion: { envVarName: 'SUPERVISOR_DELTA_VERSION', varType: 'int', defaultValue: '2' }
lockOverride: { envVarName: 'RESIN_SUPERVISOR_OVERRIDE_LOCK', varType: 'bool', defaultValue: 'false' } lockOverride: { envVarName: 'SUPERVISOR_OVERRIDE_LOCK', varType: 'bool', defaultValue: 'false' }
persistentLogging: { envVarName: 'RESIN_SUPERVISOR_PERSISTENT_LOGGING', varType: 'bool', defaultValue: 'false', rebootRequired: true } persistentLogging: { envVarName: 'SUPERVISOR_PERSISTENT_LOGGING', varType: 'bool', defaultValue: 'false', rebootRequired: true }
} }
@validKeys = [ @validKeys = [
'RESIN_SUPERVISOR_VPN_CONTROL', 'SUPERVISOR_VPN_CONTROL',
'RESIN_OVERRIDE_LOCK', 'OVERRIDE_LOCK',
].concat(_.map(@configKeys, 'envVarName')) ].concat(_.map(@configKeys, 'envVarName'))
@actionExecutors = { @actionExecutors = {
changeConfig: (step) => changeConfig: (step) =>
@ -40,7 +40,7 @@ module.exports = class DeviceConfig
.tapCatch (err) => .tapCatch (err) =>
@logger.logConfigChange(step.humanReadableTarget, { err }) @logger.logConfigChange(step.humanReadableTarget, { err })
setVPNEnabled: (step, { initial = false } = {}) => setVPNEnabled: (step, { initial = false } = {}) =>
logValue = { RESIN_SUPERVISOR_VPN_CONTROL: step.target } logValue = { SUPERVISOR_VPN_CONTROL: step.target }
if !initial if !initial
@logger.logConfigChange(logValue) @logger.logConfigChange(logValue)
@setVPNEnabled(step.target) @setVPNEnabled(step.target)
@ -80,9 +80,9 @@ module.exports = class DeviceConfig
@getConfigBackend() @getConfigBackend()
] ]
.then ([ conf, configBackend ]) => .then ([ conf, configBackend ]) =>
conf = configUtils.filterConfigKeys(configBackend, @validKeys, conf) conf = configUtils.filterAndFormatConfigKeys(configBackend, @validKeys, conf)
if initial or !conf.RESIN_SUPERVISOR_VPN_CONTROL? if initial or !conf.SUPERVISOR_VPN_CONTROL?
conf.RESIN_SUPERVISOR_VPN_CONTROL = 'true' conf.SUPERVISOR_VPN_CONTROL = 'true'
for own k, { envVarName, defaultValue } of @configKeys for own k, { envVarName, defaultValue } of @configKeys
conf[envVarName] ?= defaultValue conf[envVarName] ?= defaultValue
return conf return conf
@ -98,17 +98,22 @@ module.exports = class DeviceConfig
@getBootConfig(configBackend) @getBootConfig(configBackend)
(vpnStatus, bootConfig) => (vpnStatus, bootConfig) =>
currentConf = { currentConf = {
RESIN_SUPERVISOR_VPN_CONTROL: (vpnStatus ? 'true').toString() SUPERVISOR_VPN_CONTROL: (vpnStatus ? 'true').toString()
} }
for own key, { envVarName } of @configKeys for own key, { envVarName } of @configKeys
currentConf[envVarName] = (conf[key] ? '').toString() currentConf[envVarName] = (conf[key] ? '').toString()
return _.assign(currentConf, bootConfig) return _.assign(currentConf, bootConfig)
) )
filterAndFormatConfigKeys: (conf) =>
@getConfigBackend()
.then (configBackend) =>
configUtils.filterAndFormatConfigKeys(configBackend, @validKeys, conf)
getDefaults: => getDefaults: =>
Promise.try => Promise.try =>
return _.extend({ return _.extend({
RESIN_SUPERVISOR_VPN_CONTROL: 'true' SUPERVISOR_VPN_CONTROL: 'true'
}, _.mapValues(_.mapKeys(@configKeys, 'envVarName'), 'defaultValue')) }, _.mapValues(_.mapKeys(@configKeys, 'envVarName'), 'defaultValue'))
bootConfigChangeRequired: (configBackend, current, target) => bootConfigChangeRequired: (configBackend, current, target) =>
@ -143,8 +148,8 @@ module.exports = class DeviceConfig
checkInt(a) == checkInt(b) checkInt(a) == checkInt(b)
} }
# If the legacy lock override is used, place it as the new variable # If the legacy lock override is used, place it as the new variable
if checkTruthy(target['RESIN_OVERRIDE_LOCK']) if checkTruthy(target['OVERRIDE_LOCK'])
target['RESIN_SUPERVISOR_OVERRIDE_LOCK'] = target['RESIN_OVERRIDE_LOCK'] target['SUPERVISOR_OVERRIDE_LOCK'] = target['OVERRIDE_LOCK']
reboot = false reboot = false
for own key, { envVarName, varType, rebootRequired } of @configKeys for own key, { envVarName, varType, rebootRequired } of @configKeys
if !match[varType](current[envVarName], target[envVarName]) if !match[varType](current[envVarName], target[envVarName])
@ -162,11 +167,11 @@ module.exports = class DeviceConfig
# Check if we need to perform special case actions for the VPN # Check if we need to perform special case actions for the VPN
if !checkTruthy(offlineMode) && if !checkTruthy(offlineMode) &&
!_.isEmpty(target['RESIN_SUPERVISOR_VPN_CONTROL']) && !_.isEmpty(target['SUPERVISOR_VPN_CONTROL']) &&
@checkBoolChanged(current, target, 'RESIN_SUPERVISOR_VPN_CONTROL') @checkBoolChanged(current, target, 'SUPERVISOR_VPN_CONTROL')
steps.push({ steps.push({
action: 'setVPNEnabled' action: 'setVPNEnabled'
target: target['RESIN_SUPERVISOR_VPN_CONTROL'] target: target['SUPERVISOR_VPN_CONTROL']
}) })
# Do we need to change the boot config? # Do we need to change the boot config?

View File

@ -306,23 +306,28 @@ module.exports = class DeviceState extends EventEmitter
@emitAsync('change') @emitAsync('change')
_convertLegacyAppsJson: (appsArray) => _convertLegacyAppsJson: (appsArray) =>
config = _.reduce(appsArray, (conf, app) => deviceConf = _.reduce(appsArray, (conf, app) =>
return _.merge({}, conf, @deviceConfig.filterConfigKeys(app.config)) return _.merge({}, conf, app.config)
, {}) , {})
apps = _.keyBy(_.map(appsArray, singleToMulticontainerApp), 'appId') apps = _.keyBy(_.map(appsArray, singleToMulticontainerApp), 'appId')
return { apps, config } @deviceConfig.filterAndFormatConfigKeys(deviceConf)
.then (filteredDeviceConf)->
return { apps, config: filteredDeviceConf }
loadTargetFromFile: (appsPath) -> loadTargetFromFile: (appsPath) ->
appsPath ?= constants.appsJsonPath appsPath ?= constants.appsJsonPath
fs.readFileAsync(appsPath, 'utf8') fs.readFileAsync(appsPath, 'utf8')
.then(JSON.parse) .then(JSON.parse)
.then (stateFromFile) =>
if _.isArray(stateFromFile)
# This is a legacy apps.json
return @_convertLegacyAppsJson(stateFromFile)
else
return stateFromFile
.then (stateFromFile) => .then (stateFromFile) =>
commitToPin = null commitToPin = null
appToPin = null appToPin = null
if !_.isEmpty(stateFromFile) if !_.isEmpty(stateFromFile)
if _.isArray(stateFromFile)
# This is a legacy apps.json
stateFromFile = @_convertLegacyAppsJson(stateFromFile)
images = _.flatMap stateFromFile.apps, (app, appId) => images = _.flatMap stateFromFile.apps, (app, appId) =>
# multi-app warning! # multi-app warning!
# The following will need to be changed once running multiple applications is possible # The following will need to be changed once running multiple applications is possible

View File

@ -37,7 +37,7 @@ const constants = {
'io.balena.supervised': 'true', 'io.balena.supervised': 'true',
}, },
bootBlockDevice: '/dev/mmcblk0p1', bootBlockDevice: '/dev/mmcblk0p1',
hostConfigVarPrefix: 'RESIN_HOST_', hostConfigVarPrefix: 'HOST_',
}; };
if (process.env.DOCKER_HOST == null) { if (process.env.DOCKER_HOST == null) {

View File

@ -32,20 +32,20 @@ testTarget1 = {
local: { local: {
name: 'aDevice' name: 'aDevice'
config: { config: {
'RESIN_HOST_CONFIG_gpu_mem': '256' 'HOST_CONFIG_gpu_mem': '256'
'RESIN_SUPERVISOR_CONNECTIVITY_CHECK': 'true' 'SUPERVISOR_CONNECTIVITY_CHECK': 'true'
'RESIN_SUPERVISOR_DELTA': 'false' 'SUPERVISOR_DELTA': 'false'
'RESIN_SUPERVISOR_DELTA_APPLY_TIMEOUT': '' 'SUPERVISOR_DELTA_APPLY_TIMEOUT': ''
'RESIN_SUPERVISOR_DELTA_REQUEST_TIMEOUT': '30000' 'SUPERVISOR_DELTA_REQUEST_TIMEOUT': '30000'
'RESIN_SUPERVISOR_DELTA_RETRY_COUNT': '30' 'SUPERVISOR_DELTA_RETRY_COUNT': '30'
'RESIN_SUPERVISOR_DELTA_RETRY_INTERVAL': '10000' 'SUPERVISOR_DELTA_RETRY_INTERVAL': '10000'
'RESIN_SUPERVISOR_DELTA_VERSION': '2' 'SUPERVISOR_DELTA_VERSION': '2'
'RESIN_SUPERVISOR_LOCAL_MODE': 'false' 'SUPERVISOR_LOCAL_MODE': 'false'
'RESIN_SUPERVISOR_LOG_CONTROL': 'true' 'SUPERVISOR_LOG_CONTROL': 'true'
'RESIN_SUPERVISOR_OVERRIDE_LOCK': 'false' 'SUPERVISOR_OVERRIDE_LOCK': 'false'
'RESIN_SUPERVISOR_POLL_INTERVAL': '60000' 'SUPERVISOR_POLL_INTERVAL': '60000'
'RESIN_SUPERVISOR_VPN_CONTROL': 'true' 'SUPERVISOR_VPN_CONTROL': 'true'
'RESIN_SUPERVISOR_PERSISTENT_LOGGING': 'false' 'SUPERVISOR_PERSISTENT_LOGGING': 'false'
} }
apps: { apps: {
'1234': { '1234': {
@ -114,20 +114,20 @@ testTargetWithDefaults2 = {
local: { local: {
name: 'aDeviceWithDifferentName' name: 'aDeviceWithDifferentName'
config: { config: {
'RESIN_HOST_CONFIG_gpu_mem': '512' 'HOST_CONFIG_gpu_mem': '512'
'RESIN_SUPERVISOR_CONNECTIVITY_CHECK': 'true' 'SUPERVISOR_CONNECTIVITY_CHECK': 'true'
'RESIN_SUPERVISOR_DELTA': 'false' 'SUPERVISOR_DELTA': 'false'
'RESIN_SUPERVISOR_DELTA_APPLY_TIMEOUT': '' 'SUPERVISOR_DELTA_APPLY_TIMEOUT': ''
'RESIN_SUPERVISOR_DELTA_REQUEST_TIMEOUT': '30000' 'SUPERVISOR_DELTA_REQUEST_TIMEOUT': '30000'
'RESIN_SUPERVISOR_DELTA_RETRY_COUNT': '30' 'SUPERVISOR_DELTA_RETRY_COUNT': '30'
'RESIN_SUPERVISOR_DELTA_RETRY_INTERVAL': '10000' 'SUPERVISOR_DELTA_RETRY_INTERVAL': '10000'
'RESIN_SUPERVISOR_DELTA_VERSION': '2' 'SUPERVISOR_DELTA_VERSION': '2'
'RESIN_SUPERVISOR_LOCAL_MODE': 'false' 'SUPERVISOR_LOCAL_MODE': 'false'
'RESIN_SUPERVISOR_LOG_CONTROL': 'true' 'SUPERVISOR_LOG_CONTROL': 'true'
'RESIN_SUPERVISOR_OVERRIDE_LOCK': 'false' 'SUPERVISOR_OVERRIDE_LOCK': 'false'
'RESIN_SUPERVISOR_POLL_INTERVAL': '60000' 'SUPERVISOR_POLL_INTERVAL': '60000'
'RESIN_SUPERVISOR_VPN_CONTROL': 'true' 'SUPERVISOR_VPN_CONTROL': 'true'
'RESIN_SUPERVISOR_PERSISTENT_LOGGING': 'false' 'SUPERVISOR_PERSISTENT_LOGGING': 'false'
} }
apps: { apps: {
'1234': { '1234': {
@ -232,6 +232,7 @@ describe 'deviceState', ->
return Service.fromComposeObject(s, { appName: 'superapp' }) return Service.fromComposeObject(s, { appName: 'superapp' })
# We serialize and parse JSON to avoid checking fields that are functions or undefined # We serialize and parse JSON to avoid checking fields that are functions or undefined
expect(JSON.parse(JSON.stringify(targetState))).to.deep.equal(JSON.parse(JSON.stringify(testTarget))) expect(JSON.parse(JSON.stringify(targetState))).to.deep.equal(JSON.parse(JSON.stringify(testTarget)))
.finally =>
@deviceState.applications.images.save.restore() @deviceState.applications.images.save.restore()
@deviceState.deviceConfig.getCurrent.restore() @deviceState.deviceConfig.getCurrent.restore()

View File

@ -21,7 +21,14 @@ describe 'DeviceConfig', ->
@timeout(5000) @timeout(5000)
prepare() prepare()
@fakeDB = {} @fakeDB = {}
@fakeConfig = {} @fakeConfig = {
get: (key) ->
Promise.try ->
if key == 'deviceType'
return 'raspberrypi3'
else
throw new Error('Unknown fake config key')
}
@fakeLogger = { @fakeLogger = {
logSystemMessage: spy() logSystemMessage: spy()
} }
@ -43,36 +50,36 @@ describe 'DeviceConfig', ->
.then (conf) -> .then (conf) ->
fs.readFile.restore() fs.readFile.restore()
expect(conf).to.deep.equal({ expect(conf).to.deep.equal({
RESIN_HOST_CONFIG_initramfs: 'initramf.gz 0x00800000' HOST_CONFIG_initramfs: 'initramf.gz 0x00800000'
RESIN_HOST_CONFIG_dtparam: '"i2c=on","audio=on"' HOST_CONFIG_dtparam: '"i2c=on","audio=on"'
RESIN_HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"' HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"'
RESIN_HOST_CONFIG_foobar: 'baz' HOST_CONFIG_foobar: 'baz'
}) })
it 'properly reads a real config.txt file', -> it 'properly reads a real config.txt file', ->
@deviceConfig.getBootConfig(rpiConfigBackend) @deviceConfig.getBootConfig(rpiConfigBackend)
.then (conf) -> .then (conf) ->
expect(conf).to.deep.equal({ expect(conf).to.deep.equal({
RESIN_HOST_CONFIG_dtparam: '"i2c_arm=on","spi=on","audio=on"' HOST_CONFIG_dtparam: '"i2c_arm=on","spi=on","audio=on"'
RESIN_HOST_CONFIG_enable_uart: '1' HOST_CONFIG_enable_uart: '1'
RESIN_HOST_CONFIG_disable_splash: '1' HOST_CONFIG_disable_splash: '1'
RESIN_HOST_CONFIG_avoid_warnings: '1' HOST_CONFIG_avoid_warnings: '1'
RESIN_HOST_CONFIG_gpu_mem: '16' HOST_CONFIG_gpu_mem: '16'
}) })
# Test that the format for special values like initramfs and array variables is preserved # Test that the format for special values like initramfs and array variables is preserved
it 'does not allow setting forbidden keys', -> it 'does not allow setting forbidden keys', ->
current = { current = {
RESIN_HOST_CONFIG_initramfs: 'initramf.gz 0x00800000' HOST_CONFIG_initramfs: 'initramf.gz 0x00800000'
RESIN_HOST_CONFIG_dtparam: '"i2c=on","audio=on"' HOST_CONFIG_dtparam: '"i2c=on","audio=on"'
RESIN_HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"' HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"'
RESIN_HOST_CONFIG_foobar: 'baz' HOST_CONFIG_foobar: 'baz'
} }
target = { target = {
RESIN_HOST_CONFIG_initramfs: 'initramf.gz 0x00810000' HOST_CONFIG_initramfs: 'initramf.gz 0x00810000'
RESIN_HOST_CONFIG_dtparam: '"i2c=on","audio=on"' HOST_CONFIG_dtparam: '"i2c=on","audio=on"'
RESIN_HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"' HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"'
RESIN_HOST_CONFIG_foobar: 'baz' HOST_CONFIG_foobar: 'baz'
} }
promise = Promise.try => promise = Promise.try =>
@deviceConfig.bootConfigChangeRequired(rpiConfigBackend, current, target) @deviceConfig.bootConfigChangeRequired(rpiConfigBackend, current, target)
@ -86,16 +93,16 @@ describe 'DeviceConfig', ->
it 'does not try to change config.txt if it should not change', -> it 'does not try to change config.txt if it should not change', ->
current = { current = {
RESIN_HOST_CONFIG_initramfs: 'initramf.gz 0x00800000' HOST_CONFIG_initramfs: 'initramf.gz 0x00800000'
RESIN_HOST_CONFIG_dtparam: '"i2c=on","audio=on"' HOST_CONFIG_dtparam: '"i2c=on","audio=on"'
RESIN_HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"' HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"'
RESIN_HOST_CONFIG_foobar: 'baz' HOST_CONFIG_foobar: 'baz'
} }
target = { target = {
RESIN_HOST_CONFIG_initramfs: 'initramf.gz 0x00800000' HOST_CONFIG_initramfs: 'initramf.gz 0x00800000'
RESIN_HOST_CONFIG_dtparam: '"i2c=on","audio=on"' HOST_CONFIG_dtparam: '"i2c=on","audio=on"'
RESIN_HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"' HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"'
RESIN_HOST_CONFIG_foobar: 'baz' HOST_CONFIG_foobar: 'baz'
} }
promise = Promise.try => promise = Promise.try =>
@deviceConfig.bootConfigChangeRequired(rpiConfigBackend, current, target) @deviceConfig.bootConfigChangeRequired(rpiConfigBackend, current, target)
@ -108,17 +115,17 @@ describe 'DeviceConfig', ->
stub(fsUtils, 'writeFileAtomic').resolves() stub(fsUtils, 'writeFileAtomic').resolves()
stub(childProcess, 'execAsync').resolves() stub(childProcess, 'execAsync').resolves()
current = { current = {
RESIN_HOST_CONFIG_initramfs: 'initramf.gz 0x00800000' HOST_CONFIG_initramfs: 'initramf.gz 0x00800000'
RESIN_HOST_CONFIG_dtparam: '"i2c=on","audio=on"' HOST_CONFIG_dtparam: '"i2c=on","audio=on"'
RESIN_HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"' HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"'
RESIN_HOST_CONFIG_foobar: 'baz' HOST_CONFIG_foobar: 'baz'
} }
target = { target = {
RESIN_HOST_CONFIG_initramfs: 'initramf.gz 0x00800000' HOST_CONFIG_initramfs: 'initramf.gz 0x00800000'
RESIN_HOST_CONFIG_dtparam: '"i2c=on","audio=off"' HOST_CONFIG_dtparam: '"i2c=on","audio=off"'
RESIN_HOST_CONFIG_dtoverlay: '"lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"' HOST_CONFIG_dtoverlay: '"lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"'
RESIN_HOST_CONFIG_foobar: 'bat' HOST_CONFIG_foobar: 'bat'
RESIN_HOST_CONFIG_foobaz: 'bar' HOST_CONFIG_foobaz: 'bar'
} }
promise = Promise.try => promise = Promise.try =>
@deviceConfig.bootConfigChangeRequired(rpiConfigBackend, current, target) @deviceConfig.bootConfigChangeRequired(rpiConfigBackend, current, target)
@ -141,6 +148,23 @@ describe 'DeviceConfig', ->
childProcess.execAsync.restore() childProcess.execAsync.restore()
@fakeLogger.logSystemMessage.reset() @fakeLogger.logSystemMessage.reset()
it 'accepts RESIN_ and BALENA_ variables', ->
@deviceConfig.filterAndFormatConfigKeys({
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',
}).then (filteredConf) ->
expect(filteredConf).to.deep.equal({
HOST_CONFIG_foo: 'foobar',
HOST_CONFIG_other: 'val',
HOST_CONFIG_baz: 'bad',
SUPERVISOR_POLL_INTERVAL: '100',
});
describe 'Extlinux files', -> describe 'Extlinux files', ->
it 'should correctly write to extlinux.conf files', -> it 'should correctly write to extlinux.conf files', ->
@ -150,7 +174,7 @@ describe 'DeviceConfig', ->
current = { current = {
} }
target = { target = {
RESIN_HOST_EXTLINUX_isolcpus: '2' HOST_EXTLINUX_isolcpus: '2'
} }
promise = Promise.try => promise = Promise.try =>

View File

@ -18,10 +18,10 @@ describe 'Config Utilities', ->
it 'correctly transforms environments to boot config objects', -> it 'correctly transforms environments to boot config objects', ->
bootConfig = configUtils.envToBootConfig(rpiBackend, { bootConfig = configUtils.envToBootConfig(rpiBackend, {
RESIN_HOST_CONFIG_initramfs: 'initramf.gz 0x00800000' HOST_CONFIG_initramfs: 'initramf.gz 0x00800000'
RESIN_HOST_CONFIG_dtparam: '"i2c=on","audio=on"' HOST_CONFIG_dtparam: '"i2c=on","audio=on"'
RESIN_HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"' HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"'
RESIN_HOST_CONFIG_foobar: 'baz' HOST_CONFIG_foobar: 'baz'
}) })
expect(bootConfig).to.deep.equal({ expect(bootConfig).to.deep.equal({
initramfs: 'initramf.gz 0x00800000' initramfs: 'initramf.gz 0x00800000'