diff --git a/src/apptask.js b/src/apptask.js index 0a7f3d101..d39420503 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -26,6 +26,7 @@ var addons = require('./addons.js'), async = require('async'), auditsource = require('./auditsource.js'), backups = require('./backups.js'), + BoxError = require('./boxerror.js'), constants = require('./constants.js'), DatabaseError = require('./databaseerror.js'), debug = require('debug')('box:apptask'), @@ -52,7 +53,7 @@ var addons = require('./addons.js'), util = require('util'), _ = require('underscore'); -var COLLECTD_CONFIG_EJS = fs.readFileSync(__dirname + '/collectd.config.ejs', { encoding: 'utf8' }), +const COLLECTD_CONFIG_EJS = fs.readFileSync(__dirname + '/collectd.config.ejs', { encoding: 'utf8' }), CONFIGURE_COLLECTD_CMD = path.join(__dirname, 'scripts/configurecollectd.sh'), MV_VOLUME_CMD = path.join(__dirname, 'scripts/mvvolume.sh'), LOGROTATE_CONFIG_EJS = fs.readFileSync(__dirname + '/logrotate.ejs', { encoding: 'utf8' }), @@ -240,12 +241,12 @@ function verifyManifest(manifest, callback) { assert.strictEqual(typeof callback, 'function'); var error = manifestFormat.parse(manifest); - if (error) return callback(new Error(util.format('Manifest error: %s', error.message))); + if (error) return callback(new BoxError(BoxError.BAD_FIELD, `Manifest error: ${error.message}`, { field: 'manifest' })); error = apps.checkManifestConstraints(manifest); - if (error) return callback(error); + if (error) return callback(new BoxError(BoxError.CONFLICT, `Manifest constraint check failed: ${error.message}`, { field: 'manifest' })); - return callback(null); + callback(null); } function downloadIcon(app, callback) { @@ -285,30 +286,31 @@ function registerSubdomains(app, overwrite, callback) { const allDomains = [ { subdomain: app.location, domain: app.domain }].concat(app.alternateDomains); - console.dir(allDomains); async.eachSeries(allDomains, function (domain, iteratorDone) { async.retry({ times: 200, interval: 5000 }, function (retryCallback) { debugApp(app, 'Registering subdomain: %s%s', domain.subdomain ? (domain.subdomain + '.') : '', domain.domain); // get the current record before updating it domains.getDnsRecords(domain.subdomain, domain.domain, 'A', function (error, values) { - if (error && error.reason === DomainsError.EXTERNAL_ERROR) return retryCallback(error); // try again - if (error) return retryCallback(null, error); // give up for access and other errors + if (error && error.reason === DomainsError.EXTERNAL_ERROR) return retryCallback(new BoxError(BoxError.EXTERNAL_ERROR, error.message, domain)); // try again + if (error && error.reason === DomainsError.ACCESS_DENIED) return retryCallback(null, new BoxError(BoxError.ACCESS_DENIED, error.message, domain)); + if (error && error.reason === DomainsError.NOT_FOUND) return retryCallback(null, new BoxError(BoxError.NOT_FOUND, error.message, domain)); + if (error) return retryCallback(null, new BoxError(BoxError.EXTERNAL_ERROR, error.message, domain)); // give up for access and other errors // refuse to update any existing DNS record for custom domains that we did not create - if (values.length !== 0 && !overwrite) return retryCallback(null, new Error('DNS Record already exists')); + if (values.length !== 0 && !overwrite) return retryCallback(null, new BoxError(BoxError.ALREADY_EXISTS, 'DNS Record already exists', domain)); domains.upsertDnsRecords(domain.subdomain, domain.domain, 'A', [ ip ], function (error) { if (error && (error.reason === DomainsError.STILL_BUSY || error.reason === DomainsError.EXTERNAL_ERROR)) { - debug('Upsert error. Will retry.', error.message); - return retryCallback(error); // try again + debug('registerSubdomains: Upsert error. Will retry.', error.message); + return retryCallback(new BoxError(BoxError.EXTERNAL_ERROR, error.message, domain)); // try again } - retryCallback(null, error); + retryCallback(null, error ? new BoxError(BoxError.EXTERNAL_ERROR, error.message, domain) : null); }); }); }, function (error, result) { - if (error || result instanceof Error) return iteratorDone(error || result); + if (error || result) return iteratorDone(error || result); iteratorDone(null); }); @@ -330,12 +332,15 @@ function unregisterSubdomains(app, allDomains, callback) { domains.removeDnsRecords(domain.subdomain, domain.domain, 'A', [ ip ], function (error) { if (error && error.reason === DomainsError.NOT_FOUND) return retryCallback(null, null); - if (error && (error.reason === DomainsError.STILL_BUSY || error.reason === DomainsError.EXTERNAL_ERROR)) return retryCallback(error); // try again + if (error && (error.reason === DomainsError.STILL_BUSY || error.reason === DomainsError.EXTERNAL_ERROR)) { + debug('registerSubdomains: Remove error. Will retry.', error.message); + return retryCallback(new BoxError(BoxError.EXTERNAL_ERROR, error.message, domain)); // try again + } - retryCallback(null, error); + retryCallback(null, error ? new BoxError(BoxError.EXTERNAL_ERROR, error.message, domain) : null); }); }, function (error, result) { - if (error || result instanceof Error) return iteratorDone(error || result); + if (error || result) return iteratorDone(error || result); iteratorDone(); }); @@ -525,7 +530,8 @@ function install(app, restoreConfig, progressCallback, callback) { ], function seriesDone(error) { if (error) { debugApp(app, 'error installing app: %s', error); - return updateApp(app, { installationState: apps.ISTATE_ERROR, error: { message: error.message } }, callback.bind(null, error)); + const appError = _.extend({ message: error.message, reason: error.reason }, error.details); + return updateApp(app, { installationState: apps.ISTATE_ERROR, error: appError }, callback.bind(null, error)); } callback(null); }); diff --git a/src/boxerror.js b/src/boxerror.js new file mode 100644 index 000000000..040ef1a17 --- /dev/null +++ b/src/boxerror.js @@ -0,0 +1,38 @@ +/* jslint node:true */ + +'use strict'; + +const assert = require('assert'), + util = require('util'); + +exports = module.exports = BoxError; + +function BoxError(reason, errorOrMessage, details) { + assert.strictEqual(typeof reason, 'string'); + assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined'); + assert(typeof details === 'object' || typeof details === 'undefined'); + + Error.call(this); + Error.captureStackTrace(this, this.constructor); + + this.name = this.constructor.name; + this.reason = reason; + if (typeof errorOrMessage === 'undefined') { + this.message = reason; + } else if (typeof errorOrMessage === 'string') { + this.message = errorOrMessage; + } else { + this.message = 'Internal error'; + this.nestedError = errorOrMessage; + } + + this.details = details || {}; +} +util.inherits(BoxError, Error); +BoxError.ACCESS_DENIED = 'Access Denied'; +BoxError.ALREADY_EXISTS = 'Already Exists'; +BoxError.BAD_FIELD = 'Bad Field'; +BoxError.CONFLICT = 'Conflict'; +BoxError.EXTERNAL_ERROR = 'External Error'; +BoxError.INTERNAL_ERROR = 'Internal Error'; +BoxError.NOT_FOUND = 'Not found';