Pin a device to a commit when preload has a pinDevice field

Change-type: minor
Closes: #668
Signed-off-by: Cameron Diver <cameron@resin.io>
This commit is contained in:
Cameron Diver 2018-05-30 19:22:22 +01:00
parent 2a6d5557e4
commit 089f31cb5d
No known key found for this signature in database
GPG Key ID: 69264F9C923F55C1
5 changed files with 101 additions and 1 deletions

View File

@ -198,6 +198,20 @@ module.exports = class APIBinder
@config.set(configToUpdate)
.then =>
@eventTracker.track('Device bootstrap success')
# Check if we need to pin the device, regardless of if we provisioned
.then =>
@config.get('pinDevice')
.then(JSON.parse)
.tapCatch ->
console.log('Warning: Malformed pinDevice value in supervisor database')
.catchReturn(null)
.then (pinValue) =>
if pinValue?
if !pinValue.app? or !pinValue.commit?
console.log("Malformed pinDevice fields in supervisor database: #{pinValue}")
return
console.log('Attempting to pin device to preloaded release...')
@pinDevice(pinValue)
_provisionOrRetry: (retryDelay) =>
@eventTracker.track('Device bootstrap')
@ -214,9 +228,10 @@ module.exports = class APIBinder
'provisioned'
'bootstrapRetryDelay'
'apiKey'
'pinDevice'
])
.tap (conf) =>
if !conf.provisioned or conf.apiKey?
if !conf.provisioned or conf.apiKey? or conf.pinDevice?
@_provisionOrRetry(conf.bootstrapRetryDelay)
provisionDependentDevice: (device) =>
@ -263,6 +278,34 @@ module.exports = class APIBinder
body: updatedFields
.timeout(conf.apiTimeout)
pinDevice: ({ app, commit }) =>
@config.get('deviceId')
.then (deviceId) =>
@resinApi.get
resource: 'release'
options:
filter:
belongs_to__application: app
commit: commit
status: 'success'
select: 'id'
.then (release) =>
releaseId = _.get(release, '[0].id')
if !releaseId?
throw new Error('Cannot continue pinning preloaded device! No release found!')
@resinApi.patch
resource: 'device'
id: deviceId
body:
should_be_running__release: releaseId
.then =>
# Set the config value for pinDevice to null, so that we know the
# task has been completed
@config.remove('pinDevice')
.tapCatch (e) ->
console.log('Could not pin device to release!')
console.log('Error: ', e)
_sendLogsRequest: (uuid, data) =>
reqBody = _.map(data, (msg) -> _.mapKeys(msg, (v, k) -> _.snakeCase(k)))
@config.get('resinApiEndpoint')

View File

@ -151,6 +151,8 @@ module.exports = class Config extends EventEmitter
lockOverride: { source: 'db', mutable: true, default: 'false' }
legacyAppsPresent: { source: 'db', mutable: true, default: 'false' }
nativeLogger: { source: 'db', mutable: true, default: 'true' }
# A JSON value, which is either null, or { app: number, commit: string }
pinDevice: { source: 'db', mutable: true, default: 'null' }
}
@configJsonCache = {}

View File

@ -313,11 +313,17 @@ module.exports = class DeviceState extends EventEmitter
fs.readFileAsync(appsPath, 'utf8')
.then(JSON.parse)
.then (stateFromFile) =>
commitToPin = null
appToPin = null
if !_.isEmpty(stateFromFile)
if _.isArray(stateFromFile)
# This is a legacy apps.json
stateFromFile = @_convertLegacyAppsJson(stateFromFile)
images = _.flatMap stateFromFile.apps, (app, appId) =>
# multi-app warning!
# The following will need to be changed once running multiple applications is possible
commitToPin = app.commit
appToPin = appId
_.map app.services, (service, serviceId) =>
svc = {
imageName: service.image
@ -341,6 +347,16 @@ module.exports = class DeviceState extends EventEmitter
@setTarget({
local: stateFromFile
})
.then =>
if stateFromFile.pinDevice
# multi-app warning!
# The following will need to be changed once running multiple applications is possible
if commitToPin? and appToPin?
@config.set
pinDevice: JSON.stringify {
commit: commitToPin,
app: appToPin,
}
.catch (err) =>
@eventTracker.track('Loading preloaded apps failed', { error: err })

View File

@ -235,6 +235,19 @@ describe 'deviceState', ->
@deviceState.applications.images.save.restore()
@deviceState.deviceConfig.getCurrent.restore()
it 'stores info for pinning a device after loading an apps.json with a pinDevice field', ->
stub(@deviceState.applications.images, 'save').returns(Promise.resolve())
stub(@deviceState.deviceConfig, 'getCurrent').returns(Promise.resolve(mockedInitialConfig))
@deviceState.loadTargetFromFile(process.env.ROOT_MOUNTPOINT + '/apps-pin.json')
.then =>
@deviceState.applications.images.save.restore()
@deviceState.deviceConfig.getCurrent.restore()
@config.get('pinDevice').then (pinnedString) ->
pinned = JSON.parse(pinnedString)
expect(pinned).to.have.property('app').that.equals('1234')
expect(pinned).to.have.property('commit').that.equals('abcdef')
it 'emits a change event when a new state is reported', ->
@deviceState.reportCurrentState({ someStateDiff: 'someValue' })
expect(@deviceState).to.emit('change')

26
test/data/apps-pin.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "aDevice",
"config": {
"RESIN_HOST_CONFIG_gpu_mem": "256",
"RESIN_HOST_LOG_TO_DISPLAY": "0"
},
"apps": {
"1234": {
"name": "superapp",
"commit": "abcdef",
"releaseId": 1,
"services": {
"23": {
"imageId": 12345,
"serviceName": "someservice",
"image": "registry2.resin.io/superapp/abcdef",
"labels": {
"io.resin.something": "bar"
},
"environment": {}
}
}
}
},
"pinDevice": true
}