Improvements in proxyvisor:

* Better parameter handling in PUT /v1/devices/:uuid
* An update hook response of 200 will cause the proxyvisor to stop pinging the hook
* Allow deleting dependent apps and devices
* Implement delete dependent device hook
* Omit some fields when responding with a device object
This commit is contained in:
Pablo Carranza Velez 2016-10-07 14:16:27 +00:00
parent 7988933ac9
commit feb97539ef
3 changed files with 80 additions and 34 deletions

View File

@ -1,3 +1,8 @@
* Better parameter handling in PUT /v1/devices/:uuid [Pablo]
* An update hook response of 200 will cause the proxyvisor to stop pinging the hook [Pablo]
* Allow deleting dependent apps and devices [Pablo]
* Implement delete dependent device hook [Pablo]
* Omit some fields when responding with a device object [Pablo]
* Add validation to dependent device provisioning [Pablo] * Add validation to dependent device provisioning [Pablo]
# v2.5.2 # v2.5.2

View File

@ -96,6 +96,9 @@ knex.init = Promise.all([
t.json('targetEnvironment') t.json('targetEnvironment')
t.json('config') t.json('config')
t.json('targetConfig') t.json('targetConfig')
t.boolean('markedForDeletion')
else
addColumn('dependentDevice', 'markedForDeletion', 'boolean')
]) ])
module.exports = knex module.exports = knex

View File

@ -36,7 +36,7 @@ parseDeviceFields = (device) ->
device.environment = JSON.parse(device.environment ? '{}') device.environment = JSON.parse(device.environment ? '{}')
device.targetConfig = JSON.parse(device.targetConfig ? '{}') device.targetConfig = JSON.parse(device.targetConfig ? '{}')
device.targetEnvironment = JSON.parse(device.targetEnvironment ? '{}') device.targetEnvironment = JSON.parse(device.targetEnvironment ? '{}')
return device return _.omit(device, 'markedForDeletion', 'logs_channel')
router.get '/v1/devices', (req, res) -> router.get '/v1/devices', (req, res) ->
@ -101,6 +101,7 @@ router.get '/v1/devices/:uuid', (req, res) ->
knex('dependentDevice').select().where({ uuid }) knex('dependentDevice').select().where({ uuid })
.then ([ device ]) -> .then ([ device ]) ->
return res.status(404).send('Device not found') if !device? return res.status(404).send('Device not found') if !device?
return res.status(410).send('Device deleted') if device.markedForDeletion
res.json(parseDeviceFields(device)) res.json(parseDeviceFields(device))
.catch (err) -> .catch (err) ->
console.error("Error on #{req.method} #{url.parse(req.url).pathname}", err, err.stack) console.error("Error on #{req.method} #{url.parse(req.url).pathname}", err, err.stack)
@ -117,6 +118,7 @@ router.post '/v1/devices/:uuid/logs', (req, res) ->
knex('dependentDevice').select().where({ uuid }) knex('dependentDevice').select().where({ uuid })
.then ([ device ]) -> .then ([ device ]) ->
return res.status(404).send('Device not found') if !device? return res.status(404).send('Device not found') if !device?
return res.status(410).send('Device deleted') if device.markedForDeletion
pubnub.publish({ channel: "device-#{device.logs_channel}-logs", message: m }) pubnub.publish({ channel: "device-#{device.logs_channel}-logs", message: m })
res.status(202).send('OK') res.status(202).send('OK')
.catch (err) -> .catch (err) ->
@ -149,21 +151,31 @@ router.put '/v1/devices/:uuid', (req, res) ->
environment = JSON.stringify(environment) if isDefined(environment) environment = JSON.stringify(environment) if isDefined(environment)
config = JSON.stringify(config) if isDefined(config) config = JSON.stringify(config) if isDefined(config)
fieldsToUpdateOnDB = _.pickBy({ status, is_online, commit, config, environment }, isDefined)
fieldsToUpdateOnAPI = _.pick(fieldsToUpdateOnDB, 'status', 'is_online', 'commit')
if _.isEmpty(fieldsToUpdateOnDB)
res.status(400).send('At least one device attribute must be updated')
return
Promise.join( Promise.join(
utils.getConfig('apiKey') utils.getConfig('apiKey')
knex('dependentDevice').select().where({ uuid }) knex('dependentDevice').select().where({ uuid })
(apiKey, [ device ]) -> (apiKey, [ device ]) ->
throw new Error('apikey not found') if !apiKey? throw new Error('apikey not found') if !apiKey?
return res.status(404).send('Device not found') if !device? return res.status(404).send('Device not found') if !device?
return res.status(410).send('Device deleted') if device.markedForDeletion
throw new Error('Device is invalid') if !device.deviceId?
Promise.try ->
if !_.isEmpty(fieldsToUpdateOnAPI)
resinApi.patch resinApi.patch
resource: 'device' resource: 'device'
id: device.deviceId id: device.deviceId
body: _.pickBy({ status, is_online, commit }, isDefined) body: fieldsToUpdateOnAPI
customOptions: customOptions:
apikey: apiKey apikey: apiKey
.then -> .then ->
fieldsToUpdate = _.pickBy({ status, is_online, commit, config, environment }, isDefined) knex('dependentDevice').update(fieldsToUpdateOnDB).where({ uuid })
knex('dependentDevice').update(fieldsToUpdate).where({ uuid })
.then -> .then ->
res.json(parseDeviceFields(device)) res.json(parseDeviceFields(device))
) )
@ -230,6 +242,8 @@ exports.fetchAndSetTargetsForDependentApps = (state, fetchFn, apiKey) ->
return app.commit? and app.imageId? and !_.some(localApps, imageId: app.imageId) return app.commit? and app.imageId? and !_.some(localApps, imageId: app.imageId)
toBeRemoved = _.filter localApps, (app, appId) -> toBeRemoved = _.filter localApps, (app, appId) ->
return app.commit? and !_.some(remoteApps, imageId: app.imageId) return app.commit? and !_.some(remoteApps, imageId: app.imageId)
toBeDeletedFromDB = _.filter localApps, (app, appId) ->
return !remoteApps[appId]?
Promise.map toBeDownloaded, (app) -> Promise.map toBeDownloaded, (app) ->
fetchFn(app, false) fetchFn(app, false)
.then -> .then ->
@ -246,6 +260,10 @@ exports.fetchAndSetTargetsForDependentApps = (state, fetchFn, apiKey) ->
.then (n) -> .then (n) ->
knex('dependentApp').insert(app) if n == 0 knex('dependentApp').insert(app) if n == 0
) )
.then ->
knex('dependentApp').del().whereIn('appId', _.keys(toBeDeletedFromDB))
.then ->
knex('dependentDevice').update({ markedForDeletion: true }).whereNotIn('uuid', _.keys(state.devices))
.then -> .then ->
Promise.all _.map state.devices, (device, uuid) -> Promise.all _.map state.devices, (device, uuid) ->
# Only consider one app per dependent device for now # Only consider one app per dependent device for now
@ -283,21 +301,6 @@ exports.fetchAndSetTargetsForDependentApps = (state, fetchFn, apiKey) ->
.catch (err) -> .catch (err) ->
console.error('Error fetching dependent apps', err, err.stack) console.error('Error fetching dependent apps', err, err.stack)
sendUpdate = (device, endpoint) ->
request.putAsync "#{endpoint}#{device.uuid}", {
json: true
body:
appId: parseInt(device.appId)
commit: device.targetCommit
environment: JSON.parse(device.targetEnvironment)
config: JSON.parse(device.targetConfig)
}
.spread (response, body) ->
if response.statusCode != 200
throw new Error("Hook returned #{response.statusCode}: #{body}")
.catch (err) ->
return console.error("Error updating device #{device.uuid}", err, err.stack)
getHookEndpoint = (appId) -> getHookEndpoint = (appId) ->
knex('dependentApp').select('parentAppId').where({ appId }) knex('dependentApp').select('parentAppId').where({ appId })
.then ([ { parentAppId } ]) -> .then ([ { parentAppId } ]) ->
@ -310,6 +313,39 @@ getHookEndpoint = (appId) ->
conf.RESIN_DEPENDENT_DEVICES_HOOK_ADDRESS ? conf.RESIN_DEPENDENT_DEVICES_HOOK_ADDRESS ?
"#{appConfig.proxyvisorHookReceiver}/v1/devices/" "#{appConfig.proxyvisorHookReceiver}/v1/devices/"
do ->
acknowledgedState = {}
sendUpdate = (device, endpoint) ->
stateToSend = {
appId: parseInt(device.appId)
commit: device.targetCommit
environment: JSON.parse(device.targetEnvironment)
config: JSON.parse(device.targetConfig)
}
request.putAsync "#{endpoint}#{device.uuid}", {
json: true
body: stateToSend
}
.spread (response, body) ->
if response.statusCode == 200
acknowledgedState[device.uuid] = _.omit(stateToSend, 'appId')
else
acknowledgedState[device.uuid] = null
throw new Error("Hook returned #{response.statusCode}: #{body}") if response.statusCode != 202
.catch (err) ->
return console.error("Error updating device #{device.uuid}", err, err.stack)
sendDeleteHook = (device, endpoint) ->
uuid = device.uuid
request.delAsync("#{endpoint}#{uuid}")
.spread (response, body) ->
if response.statusCode == 200
knex('dependentDevice').del().where({ uuid })
else
throw new Error("Hook returned #{response.statusCode}: #{body}")
.catch (err) ->
return console.error("Error deleting device #{device.uuid}", err, err.stack)
exports.sendUpdates = -> exports.sendUpdates = ->
endpoints = {} endpoints = {}
knex('dependentDevice').select() knex('dependentDevice').select()
@ -320,8 +356,10 @@ exports.sendUpdates = ->
environment: device.targetEnvironment environment: device.targetEnvironment
config: device.targetConfig config: device.targetConfig
} }
if device.targetCommit? and !_.isEqual(targetState, currentState)
endpoints[device.appId] ?= getHookEndpoint(device.appId) endpoints[device.appId] ?= getHookEndpoint(device.appId)
endpoints[device.appId] endpoints[device.appId]
.then (endpoint) -> .then (endpoint) ->
if device.markedForDeletion
sendDeleteHook(device, endpoint)
else if device.targetCommit? and !_.isEqual(targetState, currentState) and !_.isEqual(targetState, acknowledgedState[device.uuid])
sendUpdate(device, endpoint) sendUpdate(device, endpoint)