From b97fe634d5e87e7c774f16d75d3f05aa1d057c9e Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Wed, 20 Jul 2016 20:01:49 +0000 Subject: [PATCH] Add default binds to containers created with API, add /v1/containers/update endpoint, and add a /data bind with an internal id --- src/api.coffee | 1 + src/application.coffee | 17 +------ src/db.coffee | 6 +++ src/docker-utils.coffee | 98 +++++++++++++++++++++++++++++------------ src/utils.coffee | 19 ++++++++ 5 files changed, 98 insertions(+), 43 deletions(-) diff --git a/src/api.coffee b/src/api.coffee index ce05a536..63bd33ed 100644 --- a/src/api.coffee +++ b/src/api.coffee @@ -170,6 +170,7 @@ module.exports = (application) -> 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 diff --git a/src/application.coffee b/src/application.coffee index 67d87cea..cd481cd6 100644 --- a/src/application.coffee +++ b/src/application.coffee @@ -176,21 +176,8 @@ shouldMountKmod = (image) -> return false application.start = start = (app) -> - volumes = - '/data': {} - '/lib/modules': {} - '/lib/firmware': {} - '/host/var/lib/connman': {} - '/host/run/dbus': {} - binds = [ - config.dataPath + '/' + app.appId + ':/data' - '/lib/modules:/lib/modules' - '/lib/firmware:/lib/firmware' - '/run/dbus:/host_run/dbus' - '/run/dbus:/host/run/dbus' - '/etc/resolv.conf:/etc/resolv.conf:rw' - '/var/lib/connman:/host/var/lib/connman' - ] + volumes = utils.defaultVolumes + binds = utils.defaultBinds(app.appId) Promise.try -> # Parse the env vars before trying to access them, that's because they have to be stringified for knex.. JSON.parse(app.env) diff --git a/src/db.coffee b/src/db.coffee index 333bd95a..7b6d5089 100644 --- a/src/db.coffee +++ b/src/db.coffee @@ -46,6 +46,12 @@ knex.init = Promise.all([ knex.schema.createTable 'image', (t) -> t.increments('id').primary() t.string('repoTag') + knex.schema.hasTable('container') + .then (exists) -> + if not exists + knex.schema.createTable 'container', (t) -> + t.increments('id').primary() + t.string('containerId') ]) diff --git a/src/docker-utils.coffee b/src/docker-utils.coffee index 05ff8fc2..69417e81 100644 --- a/src/docker-utils.coffee +++ b/src/docker-utils.coffee @@ -244,57 +244,81 @@ do -> res.status(500).send(err?.message or err or 'Unknown error') docker.modem.dialAsync = Promise.promisify(docker.modem.dial) - exports.createContainer = (req, res) -> + createContainer = (options, internalId) -> Promise.using writeLockImages(), -> - knex('image').select().where('repoTag', req.body.Image) + knex('image').select().where('repoTag', options.Image) .then (images) -> throw new Error('Only images created via the Supervisor can be used for creating containers.') if images.length == 0 - optsf = - path: '/containers/create?' - method: 'POST' - options: req.body - statusCodes: - 200: true - 201: true - 404: 'no such container' - 406: 'impossible to attach' - 500: 'server error' - docker.modem.dialAsync(optsf) - .then (data) -> - res.json(data) - .catch (err) -> - res.status(500).send(err?.message or err or 'Unknown error') - - exports.startContainer = (req, res) -> - docker.getContainer(req.params.id).startAsync(req.body) + knex.transaction (trx) -> + Promise.try -> + return internalId if internalId? + trx.insert({}, 'id').into('container') + .then ([ id ]) -> + return id + .then (id) -> + options.HostConfig ?= {} + options.Volumes ?= {} + _.assign(options.Volumes, utils.defaultVolumes) + options.HostConfig.Binds = utils.defaultBinds("containers/#{id}") + optsf = + path: '/containers/create?' + method: 'POST' + options: options + statusCodes: + 200: true + 201: true + 404: 'no such container' + 406: 'impossible to attach' + 500: 'server error' + docker.modem.dialAsync(optsf) + .then (data) -> + containerId = data.Id + trx('container').update({ containerId }).where({ id }) + .then -> + return data + exports.createContainer = (req, res) -> + createContainer(req.body) .then (data) -> res.json(data) .catch (err) -> res.status(500).send(err?.message or err or 'Unknown error') - exports.stopContainer = (req, res) -> - container = docker.getContainer(req.params.id) + startContainer = (containerId, options) -> + docker.getContainer(containerId).startAsync(options) + exports.startContainer = (req, res) -> + startContainer(req.params.id, req.body) + .then (data) -> + res.json(data) + .catch (err) -> + res.status(500).send(err?.message or err or 'Unknown error') + + stopContainer = (containerId, options) -> + container = docker.getContainer(containerId) knex('app').select() .then (apps) -> - throw new Error('Cannot stop an app container') if _.any(apps, containerId: req.params.id) + throw new Error('Cannot stop an app container') if _.any(apps, { containerId }) container.inspectAsync() .then (cont) -> throw new Error('Cannot stop supervisor container') if cont.Name == '/resin_supervisor' or _.any(cont.Names, (n) -> n == '/resin_supervisor') - container.stopAsync(sanitizeQuery(req.query)) + container.stopAsync(options) + exports.stopContainer = (req, res) -> + stopContainer(req.params.id, sanitizeQuery(req.query)) .then (data) -> res.json(data) .catch (err) -> res.status(500).send(err?.message or err or 'Unknown error') - exports.deleteContainer = (req, res) -> - container = docker.getContainer(req.params.id) + deleteContainer = (containerId, options) -> + container = docker.getContainer(containerId) knex('app').select() .then (apps) -> - throw new Error('Cannot remove an app container') if _.any(apps, containerId: req.params.id) + throw new Error('Cannot remove an app container') if _.any(apps, { containerId }) container.inspectAsync() .then (cont) -> throw new Error('Cannot remove supervisor container') if cont.Name == '/resin_supervisor' or _.any(cont.Names, (n) -> n == '/resin_supervisor') - container.removeAsync(sanitizeQuery(req.query)) + container.removeAsync(options) + exports.deleteContainer = (req, res) -> + deleteContainer(req.params.id, sanitizeQuery(req.query)) .then (data) -> res.json(data) .catch (err) -> @@ -306,3 +330,21 @@ do -> res.json(containers) .catch (err) -> res.status(500).send(err?.message or err or 'Unknown error') + + exports.updateContainer = (req, res) -> + { oldContainerId } = req.query + return res.status(400).send('Missing oldContainerId') if !oldContainerId? + knex('container').select().where({ containerId: oldContainerId }) + .then ([ oldContainer ]) -> + return res.status(404).send('Old container not found') if !oldContainer? + stopContainer(oldContainerId, t: 10) + .then -> + deleteContainer(oldContainerId, v: true) + .then -> + createContainer(req.body, oldContainer.id) + .then -> + startContainer(data.Id) + .then (data) -> + res.json(data) + .catch (err) -> + res.status(500).send(err?.message or err or 'Unknown error') \ No newline at end of file diff --git a/src/utils.coffee b/src/utils.coffee index d6119f05..13ec0138 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -204,3 +204,22 @@ exports.getOSVersion = (path) -> .catch (err) -> console.log('Could not get OS Version: ', err, err.stack) return undefined + +exports.defaultVolumes = { + '/data': {} + '/lib/modules': {} + '/lib/firmware': {} + '/host/var/lib/connman': {} + '/host/run/dbus': {} +} + +exports.defaultBinds = (dataPath) -> + return [ + config.dataPath + '/' + dataPath + ':/data' + '/lib/modules:/lib/modules' + '/lib/firmware:/lib/firmware' + '/run/dbus:/host_run/dbus' + '/run/dbus:/host/run/dbus' + '/etc/resolv.conf:/etc/resolv.conf:rw' + '/var/lib/connman:/host/var/lib/connman' + ]