mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-23 15:32:24 +00:00
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:
parent
cb31474d7a
commit
b3860b2b70
@ -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));
|
||||
|
@ -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 =>
|
||||
|
@ -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
20
src/migrations/M00000.js
Normal 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'));
|
||||
}
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user