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

View File

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

View File

@ -319,13 +319,12 @@ module.exports = class DeviceState extends EventEmitter
@emitAsync('change')
_convertLegacyAppsJson: (appsArray) =>
deviceConf = _.reduce(appsArray, (conf, app) =>
return _.merge({}, conf, app.config)
, {})
apps = _.keyBy(_.map(appsArray, singleToMulticontainerApp), 'appId')
@deviceConfig.filterAndFormatConfigKeys(deviceConf)
.then (filteredDeviceConf)->
return { apps, config: filteredDeviceConf }
Promise.try =>
deviceConf = _.reduce(appsArray, (conf, app) =>
return _.merge({}, conf, app.config)
, {})
apps = _.keyBy(_.map(appsArray, singleToMulticontainerApp), 'appId')
return { apps, config: deviceConf }
loadTargetFromFile: (appsPath) ->
console.log('Attempting to load preloaded apps...')
@ -366,11 +365,13 @@ module.exports = class DeviceState extends EventEmitter
.then =>
@deviceConfig.getCurrent()
.then (deviceConf) =>
_.defaults(stateFromFile.config, deviceConf)
stateFromFile.name ?= ''
@setTarget({
local: stateFromFile
})
@deviceConfig.formatConfigKeys(stateFromFile.config)
.then (formattedConf) =>
stateFromFile.config = _.defaults(formattedConf, deviceConf)
stateFromFile.name ?= ''
@setTarget({
local: stateFromFile
})
.then =>
console.log('Preloading complete')
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()
it 'accepts RESIN_ and BALENA_ variables', ->
@deviceConfig.filterAndFormatConfigKeys({
@deviceConfig.formatConfigKeys({
FOO: 'bar',
BAR: 'baz',
RESIN_HOST_CONFIG_foo: 'foobaz',