diff --git a/src/apptask.js b/src/apptask.js index fcb2eda01..cc9db4b5a 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -31,6 +31,7 @@ var addons = require('./addons.js'), database = require('./database.js'), DatabaseError = require('./databaseerror.js'), debug = require('debug')('box:apptask'), + df = require('@sindresorhus/df'), docker = require('./docker.js'), domains = require('./domains.js'), DomainsError = domains.DomainsError, @@ -483,6 +484,23 @@ function migrateDataDir(app, sourceDir, callback) { shell.sudo('migrateDataDir', [ MV_VOLUME_CMD, resolvedSourceDir, resolvedTargetDir ], {}, callback); } +function downloadImage(manifest, callback) { + assert.strictEqual(typeof manifest, 'object'); + assert.strictEqual(typeof callback, 'function'); + + docker.info(function (error, info) { + if (error) return callback(error); + + const dfAsync = util.callbackify(df.file); + dfAsync(info.DockerRootDir, function (error, diskUsage) { + if (error) return callback(error); + if (diskUsage.available < (1024*1024*1024)) return callback(new Error('Not enough disk space to pull docker image. See https://cloudron.io/documentation/storage/#docker-image-location')); + + docker.downloadImage(manifest, callback); + }); + }); +} + // Ordering is based on the following rationale: // - configure nginx, icon, oauth // - register subdomain. @@ -539,7 +557,7 @@ function install(app, callback) { registerAlternateDomains.bind(null, app, isRestoring /* overwrite */), updateApp.bind(null, app, { installationProgress: '40, Downloading image' }), - docker.downloadImage.bind(null, app.manifest), + downloadImage.bind(null, app.manifest), updateApp.bind(null, app, { installationProgress: '50, Creating app data directory' }), createAppDir.bind(null, app), @@ -648,7 +666,7 @@ function configure(app, callback) { registerAlternateDomains.bind(null, app, true /* overwrite */), // figure out when to overwrite updateApp.bind(null, app, { installationProgress: '40, Downloading image' }), - docker.downloadImage.bind(null, app.manifest), + downloadImage.bind(null, app.manifest), updateApp.bind(null, app, { installationProgress: '45, Ensuring app data directory' }), createAppDir.bind(null, app), @@ -730,7 +748,7 @@ function update(app, callback) { // download new image before app is stopped. this is so we can reduce downtime // and also not remove the 'common' layers when the old image is deleted updateApp.bind(null, app, { installationProgress: '25, Downloading image' }), - docker.downloadImage.bind(null, app.updateConfig.manifest), + downloadImage.bind(null, app.updateConfig.manifest), // note: we cleanup first and then backup. this is done so that the app is not running should backup fail // we cannot easily 'recover' from backup failures because we have to revert manfest and portBindings diff --git a/src/docker.js b/src/docker.js index fbf3dfc82..11389d247 100644 --- a/src/docker.js +++ b/src/docker.js @@ -8,6 +8,7 @@ exports = module.exports = { ping: ping, + info: info, downloadImage: downloadImage, createContainer: createContainer, startContainer: startContainer, @@ -621,3 +622,15 @@ function removeVolume(app, name, callback) { callback(); }); } + +function info(callback) { + assert.strictEqual(typeof callback, 'function'); + + let docker = exports.connection; + + docker.info(function (error, result) { + if (error) return callback(new DockerError(DockerError.EXTERNAL_ERROR, 'Error connecting to docker. statusCode: ' + error.statusCode)); + + callback(null, result); + }); +}