Use BoxError everywhere in apptask

This commit is contained in:
Girish Ramakrishnan
2019-09-06 11:19:58 -07:00
parent 791981c2f2
commit 551fe4d846
2 changed files with 122 additions and 66 deletions

View File

@@ -74,13 +74,13 @@ function updateApp(app, values, callback) {
debugApp(app, 'updating app with values: %j', values);
appdb.update(app.id, values, function (error) {
if (error) return callback(error);
if (error) return callback(new BoxError(BoxError.INTERNAL_ERROR, error));
for (var value in values) {
app[value] = values[value];
}
return callback(null);
callback(null);
});
}
@@ -88,16 +88,16 @@ function reserveHttpPort(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
var server = net.createServer();
let server = net.createServer();
server.listen(0, function () {
var port = server.address().port;
updateApp(app, { httpPort: port }, function (error) {
if (error) {
server.close();
return callback(error);
}
let port = server.address().port;
server.close(callback);
updateApp(app, { httpPort: port }, function (error) {
server.close(function (/* closeError */) {
if (error) return callback(new BoxError(BoxError.NETWORK_ERROR, `Failed to allocate http port ${port}: ${error.message}`));
callback(null);
});
});
});
}
@@ -106,14 +106,22 @@ function configureReverseProxy(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
reverseProxy.configureApp(app, { userId: null, username: 'apptask' }, callback);
reverseProxy.configureApp(app, { userId: null, username: 'apptask' }, function (error) {
if (error) return callback(new BoxError(BoxError.REVERSEPROXY_ERROR, `Error configuring nginx: ${error.message}`));
callback(null);
});
}
function unconfigureReverseProxy(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
reverseProxy.unconfigureApp(app, callback);
reverseProxy.unconfigureApp(app, function (error) {
if (error) return callback(new BoxError(BoxError.REVERSEPROXY_ERROR, `Error unconfiguring nginx: ${error.message}`));
callback(null);
});
}
function createContainer(app, callback) {
@@ -124,7 +132,7 @@ function createContainer(app, callback) {
debugApp(app, 'creating container');
docker.createContainer(app, function (error, container) {
if (error) return callback(new Error('Error creating container: ' + error));
if (error) return callback(new BoxError(BoxError.DOCKER_ERROR, `Error creating container: ${error.message}`));
updateApp(app, { containerId: container.id }, callback);
});
@@ -138,7 +146,7 @@ function deleteContainers(app, options, callback) {
debugApp(app, 'deleting app containers (app, scheduler)');
docker.deleteContainers(app.id, options, function (error) {
if (error) return callback(new Error('Error deleting container: ' + error));
if (error) return callback(new BoxError(BoxError.DOCKER_ERROR, `Error deleting container: ${error.message}`));
updateApp(app, { containerId: null }, callback);
});
@@ -148,7 +156,12 @@ function createAppDir(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
mkdirp(path.join(paths.APPS_DATA_DIR, app.id), callback);
const appDir = path.join(paths.APPS_DATA_DIR, app.id);
mkdirp(appDir, function (error) {
if (error) return callback(new BoxError(BoxError.FS_ERROR, `Error creating directory: ${error.message}`, { appDir }));
callback(null);
});
}
function deleteAppDir(app, options, callback) {
@@ -166,7 +179,7 @@ function deleteAppDir(app, options, callback) {
if (safe.fs.existsSync(resolvedAppDataDir)) {
const entries = safe.fs.readdirSync(resolvedAppDataDir);
if (!entries) return callback(`Error listing ${resolvedAppDataDir}: ${safe.error.message}`);
if (!entries) return callback(new BoxError(BoxError.FS_ERROR, `Error listing ${resolvedAppDataDir}: ${safe.error.message}`));
// remove only files. directories inside app dir are currently volumes managed by the addons
// we cannot delete those dirs anyway because of perms
@@ -179,9 +192,13 @@ function deleteAppDir(app, options, callback) {
// if this fails, it's probably because the localstorage/redis addons have not cleaned up properly
if (options.removeDirectory) {
if (stat.isSymbolicLink()) {
if (!safe.fs.unlinkSync(appDataDir)) return callback(safe.error.code === 'ENOENT' ? null : safe.error);
if (!safe.fs.unlinkSync(appDataDir)) {
if (safe.error.code !== 'ENOENT') return callback(new BoxError(BoxError.FS_ERROR, `Error unlinking dir ${appDataDir} : ${safe.error.message}`));
}
} else {
if (!safe.fs.rmdirSync(appDataDir)) return callback(safe.error.code === 'ENOENT' ? null : safe.error);
if (!safe.fs.rmdirSync(appDataDir)) {
if (safe.error.code !== 'ENOENT') return callback(new BoxError(BoxError.FS_ERROR, `Error removing dir ${appDataDir} : ${safe.error.message}`));
}
}
}
@@ -194,8 +211,13 @@ function addCollectdProfile(app, callback) {
var collectdConf = ejs.render(COLLECTD_CONFIG_EJS, { appId: app.id, containerId: app.containerId, appDataDir: apps.getDataDir(app, app.dataDir) });
fs.writeFile(path.join(paths.COLLECTD_APPCONFIG_DIR, app.id + '.conf'), collectdConf, function (error) {
if (error) return callback(error);
shell.sudo('addCollectdProfile', [ CONFIGURE_COLLECTD_CMD, 'add', app.id ], {}, callback);
if (error) return callback(new BoxError(BoxError.FS_ERROR, `Error writing collectd config: ${error.message}`));
shell.sudo('addCollectdProfile', [ CONFIGURE_COLLECTD_CMD, 'add', app.id ], {}, function (error) {
if (error) return callback(new BoxError(BoxError.COLLECTD_ERROR, 'Culd not add collectd config'));
callback(null);
});
});
}
@@ -205,7 +227,11 @@ function removeCollectdProfile(app, callback) {
fs.unlink(path.join(paths.COLLECTD_APPCONFIG_DIR, app.id + '.conf'), function (error) {
if (error && error.code !== 'ENOENT') debugApp(app, 'Error removing collectd profile', error);
shell.sudo('removeCollectdProfile', [ CONFIGURE_COLLECTD_CMD, 'remove', app.id ], {}, callback);
shell.sudo('removeCollectdProfile', [ CONFIGURE_COLLECTD_CMD, 'remove', app.id ], {}, function (error) {
if (error) return callback(new BoxError(BoxError.COLLECTD_ERROR, 'Culd not remove collectd config'));
callback(null);
});
});
}
@@ -214,17 +240,22 @@ function addLogrotateConfig(app, callback) {
assert.strictEqual(typeof callback, 'function');
docker.inspect(app.containerId, function (error, result) {
if (error) return callback(error);
if (error) return callback(new BoxError(BoxError.DOCKER_ERROR, `Error inspecting app container: ${error.message}`, { containerId: app.containerId }));
var runVolume = result.Mounts.find(function (mount) { return mount.Destination === '/run'; });
if (!runVolume) return callback(new Error('App does not have /run mounted'));
if (!runVolume) return callback(new BoxError(BoxError.DOCKER_ERROR, 'App does not have /run mounted'));
// logrotate configs can have arbitrary commands, so the config files must be owned by root
var logrotateConf = ejs.render(LOGROTATE_CONFIG_EJS, { volumePath: runVolume.Source, appId: app.id });
var tmpFilePath = path.join(os.tmpdir(), app.id + '.logrotate');
fs.writeFile(tmpFilePath, logrotateConf, function (error) {
if (error) return callback(error);
shell.sudo('addLogrotateConfig', [ CONFIGURE_LOGROTATE_CMD, 'add', app.id, tmpFilePath ], {}, callback);
if (error) return callback(new BoxError(BoxError.FS_ERROR, `Error writing logrotate config: ${error.message}`));
shell.sudo('addLogrotateConfig', [ CONFIGURE_LOGROTATE_CMD, 'add', app.id, tmpFilePath ], {}, function (error) {
if (error) return callback(new BoxError(BoxError.LOGROTATE_ERROR, `Error adding logrotate config: ${error.message}`));
callback(null);
});
});
});
}
@@ -233,7 +264,23 @@ function removeLogrotateConfig(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
shell.sudo('removeLogrotateConfig', [ CONFIGURE_LOGROTATE_CMD, 'remove', app.id ], {}, callback);
shell.sudo('removeLogrotateConfig', [ CONFIGURE_LOGROTATE_CMD, 'remove', app.id ], {}, function (error) {
if (error) return callback(new BoxError(BoxError.LOGROTATE_ERROR, `Error removing logrotate config: ${error.message}`));
callback(null);
});
}
function cleanupLogs(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
// note that redis container logs are cleaned up by the addon
rimraf(path.join(paths.LOG_DIR, app.id), function (error) {
if (error) debugApp(app, 'cannot cleanup logs: %s', error);
callback(null);
});
}
function verifyManifest(manifest, callback) {
@@ -266,16 +313,32 @@ function downloadIcon(app, callback) {
.buffer(true)
.timeout(30 * 1000)
.end(function (error, res) {
if (error && !error.response) return retryCallback(new Error('Network error downloading icon : ' + error.message));
if (error && !error.response) return retryCallback(new BoxError(BoxError.NETWORK_ERROR, `Network error downloading icon : ${error.message}`));
if (res.statusCode !== 200) return retryCallback(null); // ignore error. this can also happen for apps installed with cloudron-cli
if (!safe.fs.writeFileSync(path.join(paths.APP_ICONS_DIR, app.id + '.png'), res.body)) return retryCallback(new Error('Error saving icon:' + safe.error.message));
const iconPath = path.join(paths.APP_ICONS_DIR, app.id + '.png');
if (!safe.fs.writeFileSync(iconPath, res.body)) return retryCallback(new BoxError(BoxError.FS_ERROR, `Error saving icon to ${iconPath}: ${safe.error.message}`));
retryCallback(null);
});
}, callback);
}
function removeIcon(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
if (!safe.fs.unlinkSync(path.join(paths.APP_ICONS_DIR, app.id + '.png'))) {
if (safe.error.code !== 'ENOENT') debugApp(app, 'cannot remove icon : %s', safe.error);
}
if (!safe.fs.unlinkSync(path.join(paths.APP_ICONS_DIR, app.id + '.user.png'))) {
if (safe.error.code !== 'ENOENT') debugApp(app, 'cannot remove user icon : %s', safe.error);
}
callback(null);
}
function registerSubdomains(app, overwrite, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof overwrite, 'boolean');
@@ -348,33 +411,6 @@ function unregisterSubdomains(app, allDomains, callback) {
});
}
function removeIcon(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
if (!safe.fs.unlinkSync(path.join(paths.APP_ICONS_DIR, app.id + '.png'))) {
if (safe.error.code !== 'ENOENT') debugApp(app, 'cannot remove icon : %s', safe.error);
}
if (!safe.fs.unlinkSync(path.join(paths.APP_ICONS_DIR, app.id + '.user.png'))) {
if (safe.error.code !== 'ENOENT') debugApp(app, 'cannot remove user icon : %s', safe.error);
}
callback(null);
}
function cleanupLogs(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
// note that redis container logs are cleaned up by the addon
rimraf(path.join(paths.LOG_DIR, app.id), function (error) {
if (error) debugApp(app, 'cannot cleanup logs: %s', error);
callback(null);
});
}
function waitForDnsPropagation(app, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof callback, 'function');
@@ -385,14 +421,18 @@ function waitForDnsPropagation(app, callback) {
}
sysinfo.getPublicIp(function (error, ip) {
if (error) return callback(error);
if (error) return callback(new BoxError(BoxError.NETWORK_ERROR, `Error getting public IP: ${error.message}`));
domains.waitForDnsRecord(app.location, app.domain, 'A', ip, { interval: 5000, times: 240 }, function (error) {
if (error) return callback(error);
if (error) return callback(new BoxError(BoxError.DNS_ERROR, `DNS Record is not synced yet: ${error.message}`, { ip: ip, subdomain: app.location, domain: app.domain }));
// now wait for alternateDomains, if any
async.eachSeries(app.alternateDomains, function (domain, iteratorCallback) {
domains.waitForDnsRecord(domain.subdomain, domain.domain, 'A', ip, { interval: 5000, times: 240 }, iteratorCallback);
domains.waitForDnsRecord(domain.subdomain, domain.domain, 'A', ip, { interval: 5000, times: 240 }, function (error) {
if (error) return callback(new BoxError(BoxError.DNS_ERROR, `DNS Record is not synced yet: ${error.message}`, { ip: ip, subdomain: domain.subdomain, domain: domain.domain }));
iteratorCallback();
});
}, callback);
});
});
@@ -408,7 +448,11 @@ function migrateDataDir(app, sourceDir, callback) {
debug(`migrateDataDir: migrating data from ${resolvedSourceDir} to ${resolvedTargetDir}`);
shell.sudo('migrateDataDir', [ MV_VOLUME_CMD, resolvedSourceDir, resolvedTargetDir ], {}, callback);
shell.sudo('migrateDataDir', [ MV_VOLUME_CMD, resolvedSourceDir, resolvedTargetDir ], {}, function (error) {
if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `Error migrating data directory: ${error.message}`));
callback(null);
});
}
function downloadImage(manifest, callback) {
@@ -416,14 +460,18 @@ function downloadImage(manifest, callback) {
assert.strictEqual(typeof callback, 'function');
docker.info(function (error, info) {
if (error) return callback(error);
if (error) return callback(new BoxError(BoxError.DOCKER_ERROR, `Error getting docker info: ${error.message}`));
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'));
if (error) return callback(new BoxError(BoxError.FS_ERROR, `Error getting file system info: ${error.message}`));
if (diskUsage.available < (1024*1024*1024)) return callback(new BoxError(BoxError.DOCKER_ERROR, 'Not enough disk space to pull docker image', { diskUsage: diskUsage, dockerRootDir: info.DockerRootDir }));
docker.downloadImage(manifest, callback);
docker.downloadImage(manifest, function (error) {
if (error) return callback(new BoxError(BoxError.DOCKER_ERROR, `Error downloading image: ${error.message}`, { image: manifest.dockerImage }));
callback(null);
});
});
});
}
@@ -703,7 +751,7 @@ function update(app, updateConfig, progressCallback, callback) {
const newUdpPorts = updateConfig.manifest.udpPorts || {};
async.each(Object.keys(currentPorts), function (portName, callback) {
if (newTcpPorts[portName] || newUdpPorts[portName]) return callback(); // port still in use
if (newTcpPorts[portName] || newUdpPorts[portName]) return callback(null); // port still in use
appdb.delPortBinding(currentPorts[portName], apps.PORT_TYPE_TCP, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) console.error('Portbinding does not exist in database.');
@@ -712,7 +760,7 @@ function update(app, updateConfig, progressCallback, callback) {
// also delete from app object for further processing (the db is updated in the next step)
delete app.portBindings[portName];
callback();
callback(null);
});
}, next);
},
@@ -746,7 +794,7 @@ function update(app, updateConfig, progressCallback, callback) {
debugApp(app, 'Error updating app: %s', error);
updateApp(app, { installationState: apps.ISTATE_ERROR, error: error.toPlainObject ? error.toPlainObject() : error.message, updateTime: new Date() }, callback.bind(null, error));
} else {
if (updateConfig.skipNotification) return callback();
if (updateConfig.skipNotification) return callback(null);
eventlog.add(eventlog.ACTION_APP_UPDATE_FINISH, auditsource.APP_TASK, { app: app, success: true }, callback);
}