Auto-merge for PR #669 via VersionBot

Pin a device to a commit when preload has a pinDevice field
This commit is contained in:
resin-io-versionbot[bot] 2018-06-13 11:05:32 +00:00 committed by GitHub
commit b9a16c067b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 106 additions and 2 deletions

View File

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY! automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## v7.11.0 - 2018-06-13
* Pin a device to a commit when preload has a pinDevice field #669 [Cameron Diver]
## v7.10.2 - 2018-06-11 ## v7.10.2 - 2018-06-11
* Fix typo in EEXIST error predicate #677 [Cameron Diver] * Fix typo in EEXIST error predicate #677 [Cameron Diver]

View File

@ -1,7 +1,7 @@
{ {
"name": "resin-supervisor", "name": "resin-supervisor",
"description": "This is resin.io's Supervisor, a program that runs on IoT devices and has the task of running user Apps (which are Docker containers), and updating them as Resin's API informs it to.", "description": "This is resin.io's Supervisor, a program that runs on IoT devices and has the task of running user Apps (which are Docker containers), and updating them as Resin's API informs it to.",
"version": "7.10.2", "version": "7.11.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -198,6 +198,20 @@ module.exports = class APIBinder
@config.set(configToUpdate) @config.set(configToUpdate)
.then => .then =>
@eventTracker.track('Device bootstrap success') @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) => _provisionOrRetry: (retryDelay) =>
@eventTracker.track('Device bootstrap') @eventTracker.track('Device bootstrap')
@ -214,9 +228,10 @@ module.exports = class APIBinder
'provisioned' 'provisioned'
'bootstrapRetryDelay' 'bootstrapRetryDelay'
'apiKey' 'apiKey'
'pinDevice'
]) ])
.tap (conf) => .tap (conf) =>
if !conf.provisioned or conf.apiKey? if !conf.provisioned or conf.apiKey? or conf.pinDevice?
@_provisionOrRetry(conf.bootstrapRetryDelay) @_provisionOrRetry(conf.bootstrapRetryDelay)
provisionDependentDevice: (device) => provisionDependentDevice: (device) =>
@ -263,6 +278,34 @@ module.exports = class APIBinder
body: updatedFields body: updatedFields
.timeout(conf.apiTimeout) .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) => _sendLogsRequest: (uuid, data) =>
reqBody = _.map(data, (msg) -> _.mapKeys(msg, (v, k) -> _.snakeCase(k))) reqBody = _.map(data, (msg) -> _.mapKeys(msg, (v, k) -> _.snakeCase(k)))
@config.get('resinApiEndpoint') @config.get('resinApiEndpoint')

View File

@ -151,6 +151,8 @@ module.exports = class Config extends EventEmitter
lockOverride: { source: 'db', mutable: true, default: 'false' } lockOverride: { source: 'db', mutable: true, default: 'false' }
legacyAppsPresent: { source: 'db', mutable: true, default: 'false' } legacyAppsPresent: { source: 'db', mutable: true, default: 'false' }
nativeLogger: { source: 'db', mutable: true, default: 'true' } 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 = {} @configJsonCache = {}

View File

@ -313,11 +313,17 @@ module.exports = class DeviceState extends EventEmitter
fs.readFileAsync(appsPath, 'utf8') fs.readFileAsync(appsPath, 'utf8')
.then(JSON.parse) .then(JSON.parse)
.then (stateFromFile) => .then (stateFromFile) =>
commitToPin = null
appToPin = null
if !_.isEmpty(stateFromFile) if !_.isEmpty(stateFromFile)
if _.isArray(stateFromFile) if _.isArray(stateFromFile)
# This is a legacy apps.json # This is a legacy apps.json
stateFromFile = @_convertLegacyAppsJson(stateFromFile) stateFromFile = @_convertLegacyAppsJson(stateFromFile)
images = _.flatMap stateFromFile.apps, (app, appId) => 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) => _.map app.services, (service, serviceId) =>
svc = { svc = {
imageName: service.image imageName: service.image
@ -341,6 +347,16 @@ module.exports = class DeviceState extends EventEmitter
@setTarget({ @setTarget({
local: stateFromFile 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) => .catch (err) =>
@eventTracker.track('Loading preloaded apps failed', { error: err }) @eventTracker.track('Loading preloaded apps failed', { error: err })

View File

@ -235,6 +235,19 @@ describe 'deviceState', ->
@deviceState.applications.images.save.restore() @deviceState.applications.images.save.restore()
@deviceState.deviceConfig.getCurrent.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', -> it 'emits a change event when a new state is reported', ->
@deviceState.reportCurrentState({ someStateDiff: 'someValue' }) @deviceState.reportCurrentState({ someStateDiff: 'someValue' })
expect(@deviceState).to.emit('change') 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
}