fix: Store and retrieve device config without namespaces

This avoids issues on provisioning where the current state
(esp. config.txt) that we want to save is retrieved without
a RESIN_ or BALENA_ prefix, causing those values to be lost.

Change-type: patch
Signed-off-by: Pablo Carranza Velez <pablo@balena.io>
This commit is contained in:
Pablo Carranza Velez 2018-10-20 04:26:14 +02:00
parent cb31474d7a
commit b3860b2b70
5 changed files with 48 additions and 27 deletions

View File

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

View File

@ -67,20 +67,17 @@ module.exports = class DeviceConfig
setTarget: (target, trx) => setTarget: (target, trx) =>
db = trx ? @db.models.bind(@db) db = trx ? @db.models.bind(@db)
confToUpdate = { @formatConfigKeys(target)
targetValues: JSON.stringify(target) .then (formattedTarget) ->
} confToUpdate = {
db('deviceConfig').update(confToUpdate) targetValues: JSON.stringify(formattedTarget)
}
db('deviceConfig').update(confToUpdate)
getTarget: ({ initial = false } = {}) => getTarget: ({ initial = false } = {}) =>
@db.models('deviceConfig').select('targetValues') @db.models('deviceConfig').select('targetValues')
.then ([ devConfig ]) => .then ([ devConfig ]) =>
return Promise.all [ conf = JSON.parse(devConfig.targetValues)
JSON.parse(devConfig.targetValues)
@getConfigBackend()
]
.then ([ conf, configBackend ]) =>
conf = configUtils.filterAndFormatConfigKeys(configBackend, @validKeys, conf)
if initial or !conf.SUPERVISOR_VPN_CONTROL? if initial or !conf.SUPERVISOR_VPN_CONTROL?
conf.SUPERVISOR_VPN_CONTROL = 'true' conf.SUPERVISOR_VPN_CONTROL = 'true'
for own k, { envVarName, defaultValue } of @configKeys for own k, { envVarName, defaultValue } of @configKeys
@ -105,10 +102,10 @@ module.exports = class DeviceConfig
return _.assign(currentConf, bootConfig) return _.assign(currentConf, bootConfig)
) )
filterAndFormatConfigKeys: (conf) => formatConfigKeys: (conf) =>
@getConfigBackend() @getConfigBackend()
.then (configBackend) => .then (configBackend) =>
configUtils.filterAndFormatConfigKeys(configBackend, @validKeys, conf) configUtils.formatConfigKeys(configBackend, @validKeys, conf)
getDefaults: => getDefaults: =>
Promise.try => Promise.try =>

View File

@ -319,13 +319,12 @@ module.exports = class DeviceState extends EventEmitter
@emitAsync('change') @emitAsync('change')
_convertLegacyAppsJson: (appsArray) => _convertLegacyAppsJson: (appsArray) =>
deviceConf = _.reduce(appsArray, (conf, app) => Promise.try =>
return _.merge({}, conf, app.config) deviceConf = _.reduce(appsArray, (conf, app) =>
, {}) return _.merge({}, conf, app.config)
apps = _.keyBy(_.map(appsArray, singleToMulticontainerApp), 'appId') , {})
@deviceConfig.filterAndFormatConfigKeys(deviceConf) apps = _.keyBy(_.map(appsArray, singleToMulticontainerApp), 'appId')
.then (filteredDeviceConf)-> return { apps, config: deviceConf }
return { apps, config: filteredDeviceConf }
loadTargetFromFile: (appsPath) -> loadTargetFromFile: (appsPath) ->
console.log('Attempting to load preloaded apps...') console.log('Attempting to load preloaded apps...')
@ -366,11 +365,13 @@ module.exports = class DeviceState extends EventEmitter
.then => .then =>
@deviceConfig.getCurrent() @deviceConfig.getCurrent()
.then (deviceConf) => .then (deviceConf) =>
_.defaults(stateFromFile.config, deviceConf) @deviceConfig.formatConfigKeys(stateFromFile.config)
stateFromFile.name ?= '' .then (formattedConf) =>
@setTarget({ stateFromFile.config = _.defaults(formattedConf, deviceConf)
local: stateFromFile stateFromFile.name ?= ''
}) @setTarget({
local: stateFromFile
})
.then => .then =>
console.log('Preloading complete') console.log('Preloading complete')
if stateFromFile.pinDevice if stateFromFile.pinDevice

20
src/migrations/M00000.js Normal file
View File

@ -0,0 +1,20 @@
const _ = require('lodash');
// We take legacy deviceConfig targets and store them without the RESIN_ prefix
// (we also strip the BALENA_ prefix for completeness, even though no supervisors
// using this prefix made it to production)
exports.up = function (knex, Promise) {
return knex('deviceConfig').select('targetValues')
.then((devConfigs) => {
const devConfig = devConfigs[0];
const targetValues = JSON.parse(devConfig.targetValues);
const filteredTargetValues = _.mapKeys( (_v, k) => {
return k.replace(/^(?:RESIN|BALENA)_(.*)/, '$1');
});
return knex('deviceConfig').update({ targetValues: JSON.stringify(filteredTargetValues) });
});
}
exports.down = function (knex, Promise) {
return Promise.reject(new Error('Not Implemented'));
}

View File

@ -149,7 +149,7 @@ describe 'DeviceConfig', ->
@fakeLogger.logSystemMessage.reset() @fakeLogger.logSystemMessage.reset()
it 'accepts RESIN_ and BALENA_ variables', -> it 'accepts RESIN_ and BALENA_ variables', ->
@deviceConfig.filterAndFormatConfigKeys({ @deviceConfig.formatConfigKeys({
FOO: 'bar', FOO: 'bar',
BAR: 'baz', BAR: 'baz',
RESIN_HOST_CONFIG_foo: 'foobaz', RESIN_HOST_CONFIG_foo: 'foobaz',