balena-supervisor/src/api.coffee

240 lines
7.8 KiB
CoffeeScript
Raw Normal View History

2013-12-23 04:22:54 +00:00
Promise = require 'bluebird'
utils = require './utils'
2015-03-03 13:55:14 +00:00
express = require 'express'
bodyParser = require 'body-parser'
bufferEq = require 'buffer-equal-constant-time'
request = require 'request'
config = require './config'
device = require './device'
dockerUtils = require './docker-utils'
2016-04-18 19:04:06 +03:00
_ = require 'lodash'
compose = require './compose'
module.exports = (application) ->
api = express()
unparsedRouter = express.Router()
parsedRouter = express.Router()
parsedRouter.use(bodyParser())
api.use (req, res, next) ->
2016-08-15 18:25:31 -03:00
queryKey = req.query.apikey
header = req.get('Authorization') ? ''
match = header.match(/^ApiKey (\w+)$/)
headerKey = match?[1]
utils.getOrGenerateSecret('api')
.then (secret) ->
2016-08-15 18:25:31 -03:00
if queryKey? && bufferEq(new Buffer(queryKey), new Buffer(secret))
next()
else if headerKey? && bufferEq(new Buffer(headerKey), new Buffer(secret))
next()
else
res.sendStatus(401)
.catch (err) ->
# This should never happen...
res.status(503).send('Invalid API key in supervisor')
unparsedRouter.get '/ping', (req, res) ->
res.send('OK')
unparsedRouter.post '/v1/blink', (req, res) ->
utils.mixpanelTrack('Device blink')
utils.blink.pattern.start()
setTimeout(utils.blink.pattern.stop, 15000)
res.sendStatus(200)
parsedRouter.post '/v1/update', (req, res) ->
utils.mixpanelTrack('Update notification')
application.update(req.body.force)
res.sendStatus(204)
unparsedRouter.post '/v1/reboot', (req, res) ->
new Promise (resolve, reject) ->
application.logSystemMessage('Rebooting', {}, 'Reboot')
utils.gosuper.post('/v1/reboot')
.on('error', reject)
.on('response', -> resolve())
.pipe(res)
.catch (err) ->
res.status(503).send(err?.message or err or 'Unknown error')
unparsedRouter.post '/v1/shutdown', (req, res) ->
new Promise (resolve, reject) ->
application.logSystemMessage('Shutting down', {}, 'Shutdown')
utils.gosuper.post('/v1/shutdown')
.on('error', reject)
.on('response', -> resolve())
.pipe(res)
.catch (err) ->
res.status(503).send(err?.message or err or 'Unknown error')
parsedRouter.post '/v1/purge', (req, res) ->
appId = req.body.appId
application.logSystemMessage('Purging /data', { appId }, 'Purge /data')
if !appId?
return res.status(400).send('Missing app id')
Promise.using application.lockUpdates(appId, true), ->
2016-06-20 11:46:56 -07:00
utils.getKnexApp(appId)
.then (app) ->
application.kill(app)
.then ->
new Promise (resolve, reject) ->
utils.gosuper.post('/v1/purge', { json: true, body: applicationId: appId })
.on('error', reject)
.on('response', -> resolve())
.pipe(res)
.then ->
application.logSystemMessage('Purged /data', { appId }, 'Purge /data success')
.finally ->
application.start(app)
.catch (err) ->
application.logSystemMessage("Error purging /data: #{err}", { appId, error: err }, 'Purge /data error')
throw err
2016-06-20 11:46:56 -07:00
.catch utils.AppNotFoundError, (e) ->
return res.status(400).send(e.message)
.catch (err) ->
res.status(503).send(err?.message or err or 'Unknown error')
unparsedRouter.post '/v1/tcp-ping', (req, res) ->
utils.disableCheck(false)
res.sendStatus(204)
2015-08-26 19:10:22 +05:30
unparsedRouter.delete '/v1/tcp-ping', (req, res) ->
utils.disableCheck(true)
res.sendStatus(204)
2015-08-26 19:10:22 +05:30
parsedRouter.post '/v1/restart', (req, res) ->
2015-08-20 22:42:13 +00:00
appId = req.body.appId
force = req.body.force
2015-08-20 22:42:13 +00:00
utils.mixpanelTrack('Restart container', appId)
if !appId?
return res.status(400).send('Missing app id')
Promise.using application.lockUpdates(appId, force), ->
2016-06-20 11:46:56 -07:00
utils.getKnexApp(appId)
.then (app) ->
2015-08-20 22:42:13 +00:00
application.kill(app)
.then ->
application.start(app)
.then ->
res.status(200).send('OK')
2016-06-20 11:46:56 -07:00
.catch utils.AppNotFoundError, (e) ->
return res.status(400).send(e.message)
2015-08-20 22:42:13 +00:00
.catch (err) ->
res.status(503).send(err?.message or err or 'Unknown error')
parsedRouter.post '/v1/apps/:appId/stop', (req, res) ->
2016-04-20 19:08:35 +03:00
{ appId } = req.params
{ force } = req.body
2016-04-08 16:51:39 -03:00
utils.mixpanelTrack('Stop container', appId)
if !appId?
return res.status(400).send('Missing app id')
Promise.using application.lockUpdates(appId, force), ->
2016-06-20 11:46:56 -07:00
utils.getKnexApp(appId)
.tap (app) ->
2016-04-08 16:51:39 -03:00
application.kill(app, true, false)
2016-06-20 11:46:56 -07:00
.then (app) ->
res.json(_.pick(app, 'containerId'))
.catch utils.AppNotFoundError, (e) ->
return res.status(400).send(e.message)
2016-04-08 16:51:39 -03:00
.catch (err) ->
res.status(503).send(err?.message or err or 'Unknown error')
unparsedRouter.post '/v1/apps/:appId/start', (req, res) ->
2016-04-20 19:08:35 +03:00
{ appId } = req.params
2016-04-08 16:51:39 -03:00
utils.mixpanelTrack('Start container', appId)
if !appId?
return res.status(400).send('Missing app id')
Promise.using application.lockUpdates(appId), ->
2016-06-20 11:46:56 -07:00
utils.getKnexApp(appId)
.tap (app) ->
2016-04-08 16:51:39 -03:00
application.start(app)
2016-06-20 11:46:56 -07:00
.then (app) ->
res.json(_.pick(app, 'containerId'))
.catch utils.AppNotFoundError, (e) ->
return res.status(400).send(e.message)
2016-04-08 16:51:39 -03:00
.catch (err) ->
res.status(503).send(err?.message or err or 'Unknown error')
unparsedRouter.get '/v1/apps/:appId', (req, res) ->
2016-04-20 19:08:35 +03:00
{ appId } = req.params
utils.mixpanelTrack('GET app', appId)
if !appId?
return res.status(400).send('Missing app id')
Promise.using application.lockUpdates(appId, true), ->
columns = [ 'appId', 'containerId', 'commit', 'imageId', 'env' ]
2016-06-20 11:46:56 -07:00
utils.getKnexApp(appId, columns)
.then (app) ->
# Don't return keys on the endpoint
app.env = _.omit(JSON.parse(app.env), config.privateAppEnvVars)
# Don't return data that will be of no use to the user
res.json(app)
2016-06-20 11:46:56 -07:00
.catch utils.AppNotFoundError, (e) ->
return res.status(400).send(e.message)
.catch (err) ->
res.status(503).send(err?.message or err or 'Unknown error')
# Expires the supervisor's API key and generates a new one.
# It also communicates the new key to the Resin API.
unparsedRouter.post '/v1/regenerate-api-key', (req, res) ->
utils.newSecret('api')
.then (secret) ->
2015-10-28 20:02:00 -03:00
device.updateState(api_secret: secret)
res.status(200).send(secret)
.catch (err) ->
res.status(503).send(err?.message or err or 'Unknown error')
unparsedRouter.get '/v1/device', (req, res) ->
2016-02-15 20:34:11 +00:00
res.json(device.getState())
unparsedRouter.post '/v1/images/create', dockerUtils.createImage
2016-05-18 13:47:54 -03:00
unparsedRouter.post '/v1/images/load', dockerUtils.loadImage
unparsedRouter.delete '/v1/images/*', dockerUtils.deleteImage
unparsedRouter.get '/v1/images', dockerUtils.listImages
parsedRouter.post '/v1/containers/create', dockerUtils.createContainer
parsedRouter.post '/v1/containers/update', dockerUtils.updateContainer
parsedRouter.post '/v1/containers/:id/start', dockerUtils.startContainer
unparsedRouter.post '/v1/containers/:id/stop', dockerUtils.stopContainer
unparsedRouter.delete '/v1/containers/:id', dockerUtils.deleteContainer
unparsedRouter.get '/v1/containers', dockerUtils.listContainers
unparsedRouter.post '/v1/apps/:appId/compose/up', (req, res) ->
appId = req.params.appId
onStatus = (status) ->
status = JSON.stringify(status) if _.isObject(status)
res.write(status)
2016-06-20 11:46:56 -07:00
utils.getKnexApp(appId)
.then (app) ->
res.status(200)
compose.up(appId, onStatus)
.catch (err) ->
console.log('Error on compose up:', err, err.stack)
.finally ->
res.end()
2016-06-20 11:46:56 -07:00
.catch utils.AppNotFoundError, (e) ->
return res.status(400).send(e.message)
.catch (err) ->
res.status(503).send(err?.message or err or 'Unknown error')
unparsedRouter.post '/v1/apps/:appId/compose/down', (req, res) ->
appId = req.params.appId
onStatus = (status) ->
status = JSON.stringify(status) if _.isObject(status)
res.write(status)
2016-06-20 11:46:56 -07:00
utils.getKnexApp(appId)
.then (app) ->
res.status(200)
compose.down(appId, onStatus)
.catch (err) ->
console.log('Error on compose down:', err, err.stack)
.finally ->
res.end()
2016-06-20 11:46:56 -07:00
.catch utils.AppNotFoundError, (e) ->
return res.status(400).send(e.message)
.catch (err) ->
res.status(503).send(err?.message or err or 'Unknown error')
api.use(unparsedRouter)
api.use(parsedRouter)
return api