lib/index.js
'use strict'
var app = require('express')()
var BaseStore = require('./models/base-store')
var bodyParser = require('body-parser')
var ContainerStore = require('./models/container-store')
var createCount = require('callback-count')
var envIs = require('101/env-is')
var fs = require('fs')
var ImageStore = require('./models/image-store')
var mw = require('./middleware')
var not = require('101/not')
var Promise = require('bluebird')
var stream = require('stream')
var StringStream = require('./utils/string-stream')
var tarStream = require('tar-stream')
var ConflictError = BaseStore.ConflictError
var NotFoundError = BaseStore.NotFoundError
var NotModifiedError = BaseStore.NotModifiedError
var images = new ImageStore()
var containers = new ContainerStore()
// setup event stream
var eventStream = new stream.Stream()
function emitContainerEvent (eventType, container) {
if (!container) { throw new Error('emitEvent needs a container') }
eventStream.emit('data', JSON.stringify({
status: eventType,
time: Date.now(),
id: container.Id,
// TODO: ideally it shouldn't be hardcoded,
// but it seem that we can't get it now
from: 'ubuntu:latest'
}))
}
containers.on('event', emitContainerEvent)
app.use(function (req, res, next) {
req.url = req.originalUrl.replace(/\/v\d+\.\d+/, '')
next()
})
app.get('/containers/json',
function (req, res, next) {
// this can possibly throw, so wrapping in Promise.
Promise.try(function () {
if (req.query && req.query.filters) {
// need to JSON.parse the filters, first.
req.query.filters = JSON.parse(req.query.filters)
req.query.filters = ContainerStore._formatQueryFilters(req.query.filters)
}
})
.asCallback(next)
},
function (req, res, next) {
containers
.listContainers(req.query.filters)
.then(res.json.bind(res))
.catch(next)
})
app.post('/containers/create',
bodyParser.json(),
function (req, res, next) {
// Create api supports adding name as a query parameter
// https://docs.docker.com/engine/reference/api/docker_remote_api_v1.21/#create-a-container
if (req.query.name) {
req.body.name = req.query.name
}
containers
.createContainer(req.body)
.then(function (container) {
res.status(201).json(container)
})
.catch(ConflictError, function (err) { res.status(err.statusCode).end(err.message) })
.catch(next)
})
app.get('/containers/:id/json', function (req, res, next) {
containers
.findOneByIdOrName(req.params.id)
.then(res.json.bind(res))
.catch(NotFoundError, function (err) { res.status(404).end(err.message) })
.catch(next)
})
app.get('/containers/:id/stats', function (req, res, next) {
containers
.findOneByIdOrName(req.params.id)
.then(function (container) {
return container.getStats(req, res)
})
.catch(NotFoundError, function (err) { res.status(404).end(err.message) })
.catch(next)
})
/**
* This GET is to retrieve the Docker Log stream from the container
*/
app.get('/containers/:id/logs', function (req, res) {
var stringStream = new StringStream('Just a bunch of text')
res.status(200)
stringStream.pipe(res)
})
app.get('/containers/:id/top', mw.notYetImplemented)
app.get('/containers/:id/changes', mw.notYetImplemented)
app.get('/containers/:id/export', mw.notYetImplemented)
app.post('/containers/:id/start', function (req, res, next) {
containers
.findOneByIdOrName(req.params.id)
.then(function (container) {
return container.start()
})
.then(function () {
res.sendStatus(204)
})
.catch(next)
})
app.post('/containers/:id/stop', function (req, res, next) {
containers
.findOneByIdOrName(req.params.id)
.then(function (container) {
return container.stop()
})
.then(function () { res.sendStatus(204) })
.catch(NotModifiedError, function (err) {
res.status(304).end(err.message)
})
.catch(next)
})
app.post('/containers/:id/restart', function (req, res, next) {
containers
.findOneByIdOrName(req.params.id)
.then(function (container) {
return container.stop('restart')
})
.then(function (container) {
return container.start(true)
})
.then(function () { res.sendStatus(204) })
.catch(NotModifiedError, function (err) {
res.status(304).end(err.message)
})
.catch(next)
})
app.post('/containers/:id/kill', function (req, res, next) {
var signal = req.query.signal || 'SIGKILL'
containers
.findOneByIdOrName(req.params.id)
.then(function (container) {
return container.stop('kill', signal)
})
.then(function () { res.sendStatus(204) })
.catch(NotModifiedError, function (err) {
res.status(304).end(err.message)
})
.catch(next)
})
app.post('/containers/:id/attach', function (req, res, next) {
containers
.findOneByIdOrName(req.params.id)
.then(function () {
setTimeout(function () {
res.sendStatus(200)
}, 10)
})
.catch(next)
})
app.post('/containers/:id/wait', function (req, res, next) {
containers
.findOneByIdOrName(req.params.id)
.then(function (container) {
return container.stop()
})
.then(function () {
setTimeout(function () {
res.json({ StatusCode: 0 })
}, 10)
})
.catch(next)
})
app.delete('/containers/:id', function (req, res, next) {
containers
.deleteById(req.params.id)
.then(function () { res.sendStatus(204) })
.catch(next)
})
app.post('/containers/:id/copy', mw.notYetImplemented)
app.post('/containers/:id/resize', mw.notYetImplemented)
app.get('/images/json', function (req, res, next) {
images
.listImages()
.then(res.json.bind(res))
.catch(next)
})
app.post('/images/create', function (req, res, next) {
// this function is NOT promisified
images.create(req, res, function (err) {
if (err) { next(err) }
res.end()
})
})
// app.post(/\/images\/(.+)\/insert/, mw.notYetImplemented)
app.get(/\/images\/(.+)\/json/, function (req, res, next) {
images
.findOneByName(req.params[0])
.then(res.json.bind(res))
.catch(next)
})
app.get(/\/images\/(.+)\/history/, function (req, res, next) {
images
.findOneByName(req.params[0])
.then(function (image) {
return images.getHistory(image.Id)
})
.then(res.json.bind(res))
.catch(NotFoundError, function (err) { res.status(404).end(err.message) })
.catch(next)
})
app.post(/\/images\/(.+)\/push/, function (req, res, next) {
images
.findOneByName(req.params[0])
.then(function () {
res.status(200).json({ stream: 'Successfully pushed' })
})
.catch(NotFoundError, function (err) { res.status(404).end(err.message) })
.catch(next)
})
app.post(/\/images\/(.+)\/tag/, mw.notYetImplemented)
app.delete(/\/images\/(.+)/, function (req, res, next) {
images
.deleteByName(req.params[0])
.then(function () { res.sendStatus(200) })
.catch(next)
})
app.get('/images/search', mw.notYetImplemented)
app.post('/build', function (req, res, next) {
images
.build(req)
.then(function (newImage) {
res.status(200).json(newImage)
})
.catch(next)
})
app.get('/auth', mw.notYetImplemented)
app.get('/info', function (req, res, next) {
// TODO: any other information we need?
var data = {
Mock: true
}
images
.listImages()
.then(function (images) {
data.Images = images.length
return containers.listContainers()
})
.then(function (containers) {
data.Containers = containers.length
res.json(data)
})
.catch(next)
})
app.get('/version', function (req, res) {
res.json({
Arch: 'amd64',
GitCommit: 3600720,
GoVersion: 'go1.2.1',
KernelVersion: '3.13.3-tinycore64',
Os: 'linux',
Version: '0.9.1'
})
})
app.post('/commit', function (req, res, next) {
containers
.findOneByIdOrName(req.query.container)
.then(function (container) {
return images.commitContainer(container, req.query)
})
.then(function (image) {
res.status(201).json(image)
})
.catch(next)
})
app.get('/events', mw.getEvents(eventStream))
app.get(/\/images\/(.+)\/get/, function (req, res, next) {
images
.findOneByName(req.params[0])
.then(function () {
res.writeHead(200, { 'content-type': 'application/x-tar' })
fs.createReadStream('resources/busybox.tar').pipe(res)
})
.catch(NotFoundError, function (err) {
res.status(404).end(err.message)
})
.catch(next)
})
app.post('/images/load', function (req, res) {
var extract = tarStream.extract()
extract.on('entry', function (header, extractStream, cb) {
var count = createCount(cb)
// if json part, save image
if (/[0-9a-zA-Z]{32}\/json/.test(header.name)) {
count.inc()
extractStream.on('data', function (d) {
var image = JSON.parse(d)
// have to rename some things to fit
image.Id = image.id
image.Created = image.created
image.RepoTags = image.RepoTags || []
images.loadImage(image, count.next)
})
}
extractStream.on('end', count.next)
extractStream.resume()
})
req.on('end', function () {
res.writeHead(200, { 'content-type': 'text/plain' })
res.end()
})
req.pipe(extract)
})
app.all('*', mw.notYetImplemented)
app.use(function (err, req, res, next) { // eslint-disable-line no-unused-vars
if (not(envIs('test'))) { console.error(err.stack) }
res.status(500).end(err.message)
})
app.events = {
stream: eventStream,
generateEvent: mw.generateEvent
}
module.exports = app