diff --git a/src/apps.js b/src/apps.js index 1474849ae..e2c3b3080 100644 --- a/src/apps.js +++ b/src/apps.js @@ -52,7 +52,7 @@ var addons = require('./addons.js'), constants = require('./constants.js'), DatabaseError = require('./databaseerror.js'), debug = require('debug')('box:apps'), - docker = require('./docker.js').connection, + docker = require('./docker.js'), fs = require('fs'), manifestFormat = require('cloudron-manifestformat'), path = require('path'), @@ -72,6 +72,8 @@ var BACKUP_APP_CMD = path.join(__dirname, 'scripts/backupapp.sh'), RESTORE_APP_CMD = path.join(__dirname, 'scripts/restoreapp.sh'), BACKUP_SWAP_CMD = path.join(__dirname, 'scripts/backupswap.sh'); +var NOOP_CALLBACK = function (error) { if (error) debug(error); }; + function debugApp(app, args) { assert(!app || typeof app === 'object'); @@ -643,31 +645,38 @@ function exec(appId, options, callback) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such app')); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - var container = docker.getContainer(app.containerId); - - var execOptions = { + var createOptions = { AttachStdin: true, AttachStdout: true, AttachStderr: true, - Tty: true, - Cmd: cmd + OpenStdin: true, + StdinOnce: false, + Tty: true }; - container.exec(execOptions, function (error, exec) { + docker.createSubcontainer(app, app.id + '-exec-' + Date.now(), cmd, createOptions, function (error, container) { if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - var startOptions = { - Detach: false, - Tty: true, - stdin: true // this is a dockerode option that enabled openStdin in the modem - }; - exec.start(startOptions, function(error, stream) { + + container.attach({ stream: true, stdin: true, stdout: true, stderr: true }, function (error, stream) { if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - if (options.rows && options.columns) { - exec.resize({ h: options.rows, w: options.columns }, function (error) { if (error) debug('Error resizing console', error); }); - } + docker.startContainer(container.id, function (error) { + if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); - return callback(null, stream); + if (options.rows && options.columns) { + container.resize({ h: options.rows, w: options.columns }, NOOP_CALLBACK); + } + + container.wait(function (error) { + if (error) debug('Error waiting on container', error); + + debug('exec: container finished', container.id); + + docker.deleteContainer(container.id, NOOP_CALLBACK); + }); + + return callback(null, stream); + }); }); }); }); diff --git a/src/docker.js b/src/docker.js index 63729da61..e26193797 100644 --- a/src/docker.js +++ b/src/docker.js @@ -8,7 +8,8 @@ var addons = require('./addons.js'), Docker = require('dockerode'), safe = require('safetydance'), semver = require('semver'), - util = require('util'); + util = require('util'), + _ = require('underscore'); exports = module.exports = { connection: connectionInstance(), @@ -117,10 +118,11 @@ function downloadImage(manifest, callback) { }, callback); } -function createSubcontainer(app, name, cmd, callback) { +function createSubcontainer(app, name, cmd, options, callback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof name, 'string'); assert(!cmd || util.isArray(cmd)); + assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof callback, 'function'); var docker = exports.connection, @@ -192,18 +194,19 @@ function createSubcontainer(app, name, cmd, callback) { SecurityOpt: config.CLOUDRON ? [ "apparmor:docker-cloudron-app" ] : null // profile available only on cloudron } }; + containerOptions = _.extend(containerOptions, options); // older versions wanted a writable /var/log if (semver.lte(targetBoxVersion(app.manifest), '0.0.71')) containerOptions.Volumes['/var/log'] = {}; - debugApp(app, 'Creating container for %s', app.manifest.dockerImage); + debugApp(app, 'Creating container for %s with options %j', app.manifest.dockerImage, containerOptions); docker.createContainer(containerOptions, callback); }); } function createContainer(app, callback) { - createSubcontainer(app, app.id /* name */, null /* cmd */, callback); + createSubcontainer(app, app.id /* name */, null /* cmd */, { } /* options */, callback); } function startContainer(containerId, callback) { diff --git a/src/scheduler.js b/src/scheduler.js index c9eb3147d..eb79dec5d 100644 --- a/src/scheduler.js +++ b/src/scheduler.js @@ -181,7 +181,7 @@ function doTask(appId, taskName, callback) { debug('Creating createSubcontainer for %s/%s : %s', app.id, taskName, gState[appId].schedulerConfig[taskName].command); // NOTE: if you change container name here, fix addons.js to return correct container names - docker.createSubcontainer(app, app.id + '-' + taskName, [ '/bin/sh', '-c', gState[appId].schedulerConfig[taskName].command ], function (error, container) { + docker.createSubcontainer(app, app.id + '-' + taskName, [ '/bin/sh', '-c', gState[appId].schedulerConfig[taskName].command ], { } /* options */, function (error, container) { appState.containerIds[taskName] = container.id; saveState(gState);