Improve the docker compose API

- Validate the options in the YAML file
- Define bind mounts for each service as in Resin apps
- Keep the modified compose file inside the supervisor's /data folder
- Fix error reporting in the first stage of "up"
This commit is contained in:
Pablo Carranza Velez 2016-07-22 06:37:44 +00:00
parent b97fe634d5
commit 54288f036a
5 changed files with 100 additions and 19 deletions

View File

@ -24,6 +24,7 @@
"lodash": "^3.0.0",
"log-timestamp": "^0.1.2",
"mixpanel": "0.0.20",
"mkdirp": "^0.5.1",
"network-checker": "~0.0.5",
"pinejs-client": "^1.7.1",
"pubnub": "^3.7.13",

View File

@ -184,7 +184,7 @@ module.exports = (application) ->
utils.getKnexApp(appId)
.then (app) ->
res.status(200)
compose.up(application.composePath(appId), onStatus)
compose.up(appId, onStatus)
.catch (err) ->
console.log('Error on compose up:', err, err.stack)
.finally ->
@ -202,7 +202,7 @@ module.exports = (application) ->
utils.getKnexApp(appId)
.then (app) ->
res.status(200)
compose.down(application.composePath(appId), onStatus)
compose.down(appId, onStatus)
.catch (err) ->
console.log('Error on compose down:', err, err.stack)
.finally ->

View File

@ -309,9 +309,6 @@ killmePath = (app) ->
appId = app.appId ? app
return "/mnt/root#{config.dataPath}/#{appId}/resin-kill-me"
application.composePath = (appId) ->
return "/mnt/root#{config.dataPath}/#{appId}/docker-compose.yml"
# At boot, all apps should be unlocked *before* start to prevent a deadlock
application.unlockAndStart = unlockAndStart = (app) ->
lockFile.unlockAsync(lockPath(app))

View File

@ -4,14 +4,23 @@ _ = require 'lodash'
dockerUtils = require './docker-utils'
{ docker } = dockerUtils
fs = Promise.promisifyAll(require('fs'))
spawn = require('child_process').spawn
{ spawn, execAsync } = Promise.promisifyAll(require('child_process'))
mkdirp = Promise.promisify(require('mkdirp'))
path = require 'path'
utils = require './utils'
runComposeCommand = (composeArgs, path, onStatus) ->
onStatus ?= console.log.bind(console)
reportStatus = (status) ->
try onStatus(status)
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', path].concat(composeArgs), stdio: 'pipe')
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
@ -20,18 +29,30 @@ runComposeCommand = (composeArgs, path, onStatus) ->
reportStatus(status: '' + data)
child.stderr.on 'data', (data) ->
reportStatus(status: '' + data)
.catch (err) ->
msg = err?.message or err
reportStatus(error: msg)
throw err
writeComposeFile = (composeSpec, dstPath) ->
mkdirp(path.dirname(dstPath))
.then ->
YAML.stringify(composeSpec)
.then (yml) ->
fs.writeFileAsync(dstPath, yml)
.then ->
execAsync('sync')
validateServiceOptions = (service) ->
Promise.try ->
options = _.keys(service)
_.each options, (option) ->
throw new Error("Using #{option} is not allowed.") if !_.includes(utils.validComposeOptions, option)
# Runs docker-compose up using the compose YAML at "path".
# Reports status and errors in JSON to the onStatus function.
exports.up = (path, onStatus) ->
# 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(path)
fs.readFileAsync(composePathSrc(appId))
.then (data) ->
YAML.parse(data.toString())
.then (composeSpec) ->
@ -46,9 +67,27 @@ exports.up = (path, onStatus) ->
docker.getImage(service.image).inspectAsync()
.catch ->
dockerUtils.pullAndProtectImage(service.image, reportStatus)
.then ->
validateServiceOptions(service)
.then ->
services[serviceName].volumes = utils.defaultBinds(composeDataPath(appId, serviceName))
.then ->
writeComposeFile(composeSpec, dstPath)
.then ->
runComposeCommand(['up', '-d'], path, onStatus)
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 = _.partial(runComposeCommand, 'down')
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

@ -223,3 +223,47 @@ exports.defaultBinds = (dataPath) ->
'/etc/resolv.conf:/etc/resolv.conf:rw'
'/var/lib/connman:/host/var/lib/connman'
]
exports.validComposeOptions = [
'command'
'entrypoint'
'env_file'
'environment'
'expose'
'image'
'labels'
'ports'
'stop_signal'
'volumes'
'user'
'working_dir'
'cap_add'
'cap_drop'
'devices'
'dns'
'dns_search'
'tmpfs'
'extra_hosts'
'links'
'net'
'network_mode'
'ulimits'
'volumes_from'
'cpu_shares'
'cpu_quota'
'cpuset'
'domainname'
'hostname'
'mac_address'
'mem_limit'
'memswap_limit'
'privileged'
'tty'
'read_only'
'shm_size'
'ipc'
'restart'
'security_opt'
'networks'
'pid'
]