Remove the undocumented and unused sideload and compose APIs

This allows us to also remove a few npm dependencies and the docker compose binary.

Change-Type: major
Signed-off-by: Pablo Carranza Velez <pablo@resin.io>
This commit is contained in:
Pablo Carranza Velez 2017-06-06 17:07:14 -07:00
parent 4c30d0791f
commit 597a2c6b65
6 changed files with 8 additions and 386 deletions

View File

@ -16,27 +16,8 @@ RUN apt-get update \
wget \ wget \
&& rm -rf /var/lib/apt/lists/ && rm -rf /var/lib/apt/lists/
ENV DOCKER_COMPOSE_VERSION 1.7.1 RUN mkdir -p rootfs-overlay && \
ln -s /lib rootfs-overlay/lib64
ENV DOCKER_COMPOSE_SHA256_amd64 37df85ee18bf0e2a8d71cbfb8198b1c06cc388f19118be7bdfc4d6db112af834
ENV DOCKER_COMPOSE_SHA256_i386 b926fd9a2a9d89358f1353867706f94558a62caaf3aa72bf10bcbbe31e1a44f0
ENV DOCKER_COMPOSE_SHA256_rpi 3f0b8c69c66a2daa5fbb0c127cb76ca95d7125827a9c43dd3c36f9bc2ed6e0e5
ENV DOCKER_COMPOSE_SHA256_armv7hf 3f0b8c69c66a2daa5fbb0c127cb76ca95d7125827a9c43dd3c36f9bc2ed6e0e5
ENV DOCKER_COMPOSE_SHA256_armel a1025fed97536e2698798ea277a014ec5e1eae816a8cf3155ecbe9679e3e7bac
RUN set -x \
&& mkdir -p rootfs-overlay/usr/bin/ \
&& ln -s /lib rootfs-overlay/lib64 \
&& pkgname='docker-compose' \
&& arch=%%ARCH%% \
&& if [ $arch = 'rpi' -o $arch = 'armv7hf' ]; then arch=armhf; fi \
&& base="http://resin-packages.s3.amazonaws.com/${pkgname}" \
&& pkgver=$DOCKER_COMPOSE_VERSION \
&& checksum=$DOCKER_COMPOSE_SHA256_%%ARCH%% \
&& wget "${base}/${pkgver}/${pkgname}-linux-${arch}-${pkgver}.tar.gz" \
&& echo "$checksum ${pkgname}-linux-${arch}-${pkgver}.tar.gz" | sha256sum -c \
&& tar xzf "${pkgname}-linux-${arch}-${pkgver}.tar.gz" --strip-components=1 -C rootfs-overlay/usr/bin \
&& mv "rootfs-overlay/usr/bin/${pkgname}-linux-${arch}" rootfs-overlay/usr/bin/docker-compose
COPY package.json /usr/src/app/ COPY package.json /usr/src/app/

View File

@ -31,7 +31,6 @@
"log-timestamp": "^0.1.2", "log-timestamp": "^0.1.2",
"memoizee": "^0.4.1", "memoizee": "^0.4.1",
"mixpanel": "0.0.20", "mixpanel": "0.0.20",
"mkdirp": "^0.5.1",
"network-checker": "~0.0.5", "network-checker": "~0.0.5",
"pinejs-client": "^2.4.0", "pinejs-client": "^2.4.0",
"pubnub": "^3.7.13", "pubnub": "^3.7.13",
@ -43,11 +42,10 @@
"semver": "^5.3.0", "semver": "^5.3.0",
"semver-regex": "^1.0.0", "semver-regex": "^1.0.0",
"sqlite3": "^3.1.0", "sqlite3": "^3.1.0",
"typed-error": "~0.1.0", "typed-error": "~0.1.0"
"yamljs": "^0.2.7"
}, },
"engines": { "engines": {
"node": "0.10.22" "node": "^6.5.0"
}, },
"devDependencies": { "devDependencies": {
"coffee-script": "~1.11.0", "coffee-script": "~1.11.0",

View File

@ -5,9 +5,7 @@ bodyParser = require 'body-parser'
bufferEq = require 'buffer-equal-constant-time' bufferEq = require 'buffer-equal-constant-time'
config = require './config' config = require './config'
device = require './device' device = require './device'
dockerUtils = require './docker-utils'
_ = require 'lodash' _ = require 'lodash'
compose = require './compose'
proxyvisor = require './proxyvisor' proxyvisor = require './proxyvisor'
module.exports = (application) -> module.exports = (application) ->
@ -217,53 +215,6 @@ module.exports = (application) ->
unparsedRouter.get '/v1/device', (req, res) -> unparsedRouter.get '/v1/device', (req, res) ->
res.json(device.getState()) res.json(device.getState())
unparsedRouter.post '/v1/images/create', dockerUtils.createImage
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)
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()
.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)
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()
.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(unparsedRouter)
api.use(parsedRouter) api.use(parsedRouter)
api.use(proxyvisor.router) api.use(proxyvisor.router)

View File

@ -1,88 +0,0 @@
Promise = require 'bluebird'
YAML = require 'yamljs'
_ = require 'lodash'
dockerUtils = require './docker-utils'
{ docker } = dockerUtils
fs = Promise.promisifyAll(require('fs'))
{ spawn, execAsync } = Promise.promisifyAll(require('child_process'))
mkdirp = Promise.promisify(require('mkdirp'))
path = require 'path'
utils = require './utils'
config = require './config'
composePathSrc = (appId) ->
return "/mnt/root#{config.dataPath}/#{appId}/docker-compose.yml"
composePathDst = (appId) ->
return "/mnt/root#{config.dataPath}/resin-supervisor/compose/#{appId}/docker-compose.yml"
composeDataPath = (appId, serviceName) ->
return "compose/#{appId}/#{serviceName}"
runComposeCommand = (composeArgs, appId, reportStatus) ->
new Promise (resolve, reject) ->
child = spawn('docker-compose', ['-f', composePathDst(appId)].concat(composeArgs), stdio: 'pipe')
.on 'error', reject
.on 'exit', (code) ->
return reject(new Error("docker-compose exited with code #{code}")) if code isnt 0
resolve()
child.stdout.on 'data', (data) ->
reportStatus(status: '' + data)
child.stderr.on 'data', (data) ->
reportStatus(status: '' + data)
writeComposeFile = (composeSpec, dstPath) ->
mkdirp(path.dirname(dstPath))
.then ->
YAML.stringify(composeSpec)
.then (yml) ->
fs.writeFileAsync(dstPath, yml)
.then ->
execAsync('sync')
# Runs docker-compose up using the compose YAML at "path".
# Reports status and errors in JSON to the onStatus function.
# Copies the compose file from srcPath to dstPath adding default volumes
exports.up = (appId, onStatus) ->
onStatus ?= console.log.bind(console)
reportStatus = (status) ->
try onStatus(status)
fs.readFileAsync(composePathSrc(appId))
.then (data) ->
YAML.parse(data.toString())
.then (composeSpec) ->
if composeSpec.version? && composeSpec.version == '2'
services = composeSpec.services
else
services = composeSpec
throw new Error('No services found') if !_.isObject(services)
servicesArray = _.toPairs(services)
Promise.each servicesArray, ([ serviceName, service ]) ->
throw new Error("Service #{serviceName} has no image specified.") if !service.image
docker.getImage(service.image).inspectAsync()
.catch ->
dockerUtils.pullAndProtectImage(service.image, reportStatus)
.then ->
utils.validateKeys(service, utils.validComposeOptions)
.then ->
services[serviceName].volumes = utils.defaultBinds(composeDataPath(appId, serviceName))
.then ->
writeComposeFile(composeSpec, composePathDst(appId))
.then ->
runComposeCommand(['up', '-d'], appId, reportStatus)
.catch (err) ->
msg = err?.message or err
reportStatus(error: msg)
throw err
# Runs docker-compose down using the compose YAML at "path".
# Reports status and errors in JSON to the onStatus function.
exports.down = (appId, onStatus) ->
onStatus ?= console.log.bind(console)
reportStatus = (status) ->
try onStatus(status)
runComposeCommand([ 'down' ], appId, reportStatus)
.catch (err) ->
msg = err?.message or err
reportStatus(error: msg)
throw err

View File

@ -56,19 +56,6 @@ knex.init = Promise.all([
# When updating from older supervisors, config can be null # When updating from older supervisors, config can be null
knex('app').update({ config: '{}' }).whereNull('config') knex('app').update({ config: '{}' }).whereNull('config')
knex.schema.hasTable('image')
.then (exists) ->
if not exists
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')
knex.schema.hasTable('dependentApp') knex.schema.hasTable('dependentApp')
.then (exists) -> .then (exists) ->
if not exists if not exists

View File

@ -142,9 +142,6 @@ do ->
exports.cleanupContainersAndImages = (extraImagesToIgnore = []) -> exports.cleanupContainersAndImages = (extraImagesToIgnore = []) ->
Promise.using writeLockImages(), -> Promise.using writeLockImages(), ->
Promise.join( Promise.join(
knex('image').select('repoTag')
.map ({ repoTag }) ->
normalizeRepoTag(repoTag)
knex('app').select() knex('app').select()
.map ({ imageId }) -> .map ({ imageId }) ->
normalizeRepoTag(imageId) normalizeRepoTag(imageId)
@ -157,7 +154,7 @@ do ->
image.NormalizedRepoTags = Promise.map(image.RepoTags, normalizeRepoTag) image.NormalizedRepoTags = Promise.map(image.RepoTags, normalizeRepoTag)
Promise.props(image) Promise.props(image)
Promise.map(extraImagesToIgnore, normalizeRepoTag) Promise.map(extraImagesToIgnore, normalizeRepoTag)
(locallyCreatedTags, apps, dependentApps, supervisorTag, images, normalizedExtraImages) -> (apps, dependentApps, supervisorTag, images, normalizedExtraImages) ->
imageTags = _.map(images, 'NormalizedRepoTags') imageTags = _.map(images, 'NormalizedRepoTags')
supervisorTags = _.filter imageTags, (tags) -> supervisorTags = _.filter imageTags, (tags) ->
_.includes(tags, supervisorTag) _.includes(tags, supervisorTag)
@ -170,11 +167,10 @@ do ->
supervisorTags = _.flatten(supervisorTags) supervisorTags = _.flatten(supervisorTags)
appTags = _.flatten(appTags) appTags = _.flatten(appTags)
extraTags = _.flatten(extraTags) extraTags = _.flatten(extraTags)
locallyCreatedTags = _.flatten(locallyCreatedTags)
return { images, supervisorTags, appTags, locallyCreatedTags, extraTags } return { images, supervisorTags, appTags, extraTags }
) )
.then ({ images, supervisorTags, appTags, locallyCreatedTags, extraTags }) -> .then ({ images, supervisorTags, appTags, extraTags }) ->
# Cleanup containers first, so that they don't block image removal. # Cleanup containers first, so that they don't block image removal.
docker.listContainersAsync(all: true) docker.listContainersAsync(all: true)
.filter (containerInfo) -> .filter (containerInfo) ->
@ -183,8 +179,6 @@ do ->
.then (repoTag) -> .then (repoTag) ->
if _.includes(appTags, repoTag) if _.includes(appTags, repoTag)
return false return false
if _.includes(locallyCreatedTags, repoTag)
return false
if _.includes(extraTags, repoTag) if _.includes(extraTags, repoTag)
return false return false
if !_.includes(supervisorTags, repoTag) if !_.includes(supervisorTags, repoTag)
@ -198,7 +192,7 @@ do ->
.then -> .then ->
imagesToClean = _.reject images, (image) -> imagesToClean = _.reject images, (image) ->
_.some image.NormalizedRepoTags, (tag) -> _.some image.NormalizedRepoTags, (tag) ->
return _.includes(appTags, tag) or _.includes(supervisorTags, tag) or _.includes(locallyCreatedTags, tag) or _.includes(extraTags, tag) return _.includes(appTags, tag) or _.includes(supervisorTags, tag) or _.includes(extraTags, tag)
Promise.map imagesToClean, (image) -> Promise.map imagesToClean, (image) ->
Promise.map image.RepoTags.concat(image.Id), (tag) -> Promise.map image.RepoTags.concat(image.Id), (tag) ->
docker.getImage(tag).removeAsync(force: true) docker.getImage(tag).removeAsync(force: true)
@ -222,207 +216,6 @@ do ->
repoTag += ':latest' repoTag += ':latest'
return repoTag return repoTag
sanitizeQuery = (query) ->
_.omit(query, 'apikey')
exports.createImage = (req, res) ->
{ registry, repo, tag, fromImage } = req.query
if fromImage?
repoTag = buildRepoTag(fromImage, tag)
else
repoTag = buildRepoTag(repo, tag, registry)
Promise.using writeLockImages(), ->
knex('image').select().where({ repoTag })
.then ([ img ]) ->
knex('image').insert({ repoTag }) if !img?
.then ->
if fromImage?
docker.createImageAsync({ fromImage, tag })
else
docker.importImageAsync(req, { repo, tag, registry })
.then (stream) ->
new Promise (resolve, reject) ->
stream.on('error', reject)
.on('response', -> resolve())
.pipe(res)
.catch (err) ->
res.status(500).send(err?.message or err or 'Unknown error')
exports.pullAndProtectImage = (image, onProgress) ->
repoTag = buildRepoTag(image)
Promise.using writeLockImages(), ->
knex('image').select().where({ repoTag })
.then ([ img ]) ->
knex('image').insert({ repoTag }) if !img?
.then ->
dockerProgress.pull(repoTag, onProgress)
exports.getImageTarStream = (image) ->
docker.getImage(image).getAsync()
exports.loadImage = (req, res) ->
Promise.using writeLockImages(), ->
docker.listImagesAsync()
.then (oldImages) ->
docker.loadImageAsync(req)
.then ->
docker.listImagesAsync()
.then (newImages) ->
oldTags = _.flatten(_.map(oldImages, 'RepoTags'))
newTags = _.flatten(_.map(newImages, 'RepoTags'))
createdTags = _.difference(newTags, oldTags)
Promise.map createdTags, (repoTag) ->
knex('image').insert({ repoTag })
.then ->
res.sendStatus(200)
.catch (err) ->
res.status(500).send(err?.message or err or 'Unknown error')
exports.deleteImage = (req, res) ->
imageName = req.params[0]
Promise.using writeLockImages(), ->
knex('image').select().where('repoTag', imageName)
.then (images) ->
throw new Error('Only images created via the Supervisor can be deleted.') if images.length == 0
knex('image').where('repoTag', imageName).delete()
.then ->
docker.getImage(imageName).removeAsync(sanitizeQuery(req.query))
.then (data) ->
res.json(data)
.catch (err) ->
res.status(500).send(err?.message or err or 'Unknown error')
exports.listImages = (req, res) ->
docker.listImagesAsync(sanitizeQuery(req.query))
.then (images) ->
res.json(images)
.catch (err) ->
res.status(500).send(err?.message or err or 'Unknown error')
docker.modem.dialAsync = Promise.promisify(docker.modem.dial)
createContainer = (options, internalId) ->
Promise.using writeLockImages(), ->
Promise.join(
knex('image').select().where('repoTag', options.Image)
device.isResinOSv1()
(images, isV1) ->
throw new Error('Only images created via the Supervisor can be used for creating containers.') if images.length == 0
knex.transaction (tx) ->
Promise.try ->
return internalId if internalId?
tx.insert({}, 'id').into('container')
.then ([ id ]) ->
return id
.then (id) ->
options.HostConfig ?= {}
options.Volumes ?= {}
_.assign(options.Volumes, utils.defaultVolumes(isV1))
options.HostConfig.Binds = utils.defaultBinds("containers/#{id}", isV1)
query = ''
query = "name=#{options.Name}&" if options.Name?
optsf =
path: "/containers/create?#{query}"
method: 'POST'
options: options
statusCodes:
200: true
201: true
404: 'no such container'
406: 'impossible to attach'
500: 'server error'
utils.validateKeys(options, utils.validContainerOptions)
.then ->
utils.validateKeys(options.HostConfig, utils.validHostConfigOptions)
.then ->
docker.modem.dialAsync(optsf)
.then (data) ->
containerId = data.Id
tx('container').update({ containerId }).where({ id })
.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')
startContainer = (containerId, options) ->
utils.validateKeys(options, utils.validHostConfigOptions)
.then ->
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 _.some(apps, { containerId })
container.inspectAsync()
.then (cont) ->
throw new Error('Cannot stop supervisor container') if cont.Name == '/resin_supervisor' or _.some(cont.Names, (n) -> n == '/resin_supervisor')
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')
deleteContainer = (containerId, options) ->
container = docker.getContainer(containerId)
knex('app').select()
.then (apps) ->
throw new Error('Cannot remove an app container') if _.some(apps, { containerId })
container.inspectAsync()
.then (cont) ->
throw new Error('Cannot remove supervisor container') if cont.Name == '/resin_supervisor' or _.some(cont.Names, (n) -> n == '/resin_supervisor')
if options.purge
knex('container').select().where({ containerId })
.then (contFromDB) ->
# This will also be affected by #115. Should fix when we fix that.
rimraf(utils.getDataPath("containers/#{contFromDB.id}"))
.then ->
knex('container').where({ containerId }).del()
.then ->
container.removeAsync(options)
exports.deleteContainer = (req, res) ->
deleteContainer(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.listContainers = (req, res) ->
docker.listContainersAsync(sanitizeQuery(req.query))
.then (containers) ->
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)
.tap (data) ->
startContainer(data.Id)
.then (data) ->
res.json(data)
.catch (err) ->
res.status(500).send(err?.message or err or 'Unknown error')
exports.getImageEnv = (id) -> exports.getImageEnv = (id) ->
docker.getImage(id).inspectAsync() docker.getImage(id).inspectAsync()
.get('Config').get('Env') .get('Config').get('Env')