diff --git a/src/docker.js b/src/docker.js index 120cc56a6..9ff6a0fc5 100644 --- a/src/docker.js +++ b/src/docker.js @@ -40,6 +40,7 @@ const apps = require('./apps.js'), delay = require('./delay.js'), Docker = require('dockerode'), paths = require('./paths.js'), + promiseRetry = require('./promise-retry.js'), services = require('./services.js'), settings = require('./settings.js'), shell = require('./shell.js'), @@ -113,24 +114,28 @@ async function pullImage(manifest) { if (error) throw new BoxError(BoxError.DOCKER_ERROR, `Unable to pull image ${manifest.dockerImage}. Please check the network or if the image needs authentication. statusCode: ${error.statusCode}`); return new Promise((resolve, reject) => { - // https://github.com/dotcloud/docker/issues/1074 says each status message - // is emitted as a chunk + // https://github.com/dotcloud/docker/issues/1074 says each status message is emitted as a chunk + let layerError = null; stream.on('data', function (chunk) { const data = safe.JSON.parse(chunk) || { }; debug('pullImage: %j', data); // The data.status here is useless because this is per layer as opposed to per image - if (!data.status && data.error) { - debug('pullImage error %s: %s', manifest.dockerImage, data.errorDetail.message); + if (!data.status && data.error) { // data is { errorDetail: { message: xx } , error: xx } + debug(`pullImage error ${manifest.dockerImage}: ${data.errorDetail.message}`); + layerError = data.errorDetail; } }); stream.on('end', function () { - debug('downloaded image %s', manifest.dockerImage); - resolve(); + debug(`downloaded image ${manifest.dockerImage} . error: ${!!layerError}`); + + if (!layerError) return resolve(); + + reject(new BoxError(layerError.includes('no space') ? BoxError.FS_ERROR : BoxError.DOCKER_ERROR, layerError.message)); }); - stream.on('error', function (error) { + stream.on('error', function (error) { // this is only hit for stream error and not for some download error debug('error pulling image %s: %j', manifest.dockerImage, error); reject(new BoxError(BoxError.DOCKER_ERROR, error.message)); }); @@ -147,14 +152,9 @@ async function downloadImage(manifest) { const [error, result] = await safe(image.inspect()); if (!error && result) return; // image is already present locally - for (let times = 0; times < 10; times++) { - debug(`downloadImage: pulling image. attempt ${times+1}`); - const [pullError] = await safe(pullImage(manifest)); - if (pullError && pullError.reason === BoxError.NOT_FOUND) throw pullError; - if (!pullError) break; - - await delay(5000); - } + await promiseRetry({ times: 10, interval: 5000, retry: (pullError) => pullError.reason !== BoxError.NOT_FOUND && pullError.reason !== BoxError.FS_ERROR }, async () => { + await pullImage(manifest); + }); } async function getVolumeMounts(app) {