Break down the configure route
This commit is contained in:
+371
@@ -17,6 +17,21 @@ exports = module.exports = {
|
||||
configure: configure,
|
||||
uninstall: uninstall,
|
||||
|
||||
setAccessRestriction: setAccessRestriction,
|
||||
setLabel: setLabel,
|
||||
setIcon: setIcon,
|
||||
setTags: setTags,
|
||||
setMemoryLimit: setMemoryLimit,
|
||||
setAutomaticBackup: setAutomaticBackup,
|
||||
setAutomaticUpdate: setAutomaticUpdate,
|
||||
setRobotsTxt: setRobotsTxt,
|
||||
setCertificate: setCertificate,
|
||||
setDebugMode: setDebugMode,
|
||||
setEnvironment: setEnvironment,
|
||||
setMailbox: setMailbox,
|
||||
setLocation: setLocation,
|
||||
setDataDir: setDataDir,
|
||||
|
||||
restore: restore,
|
||||
clone: clone,
|
||||
|
||||
@@ -58,6 +73,9 @@ exports = module.exports = {
|
||||
ISTATE_PENDING_INSTALL: 'pending_install', // installs and fresh reinstalls
|
||||
ISTATE_PENDING_CLONE: 'pending_clone', // clone
|
||||
ISTATE_PENDING_CONFIGURE: 'pending_configure', // config (location, port) changes and on infra update
|
||||
ISTATE_PENDING_CREATE_CONTAINER: 'pending_create_container',
|
||||
ISTATE_PENDING_LOCATION_CHANGE: 'pending_location_change',
|
||||
ISTATE_PENDING_DATA_DIR_MIGRATION: 'pending_data_dir_migration',
|
||||
ISTATE_PENDING_UNINSTALL: 'pending_uninstall', // uninstallation
|
||||
ISTATE_PENDING_RESTORE: 'pending_restore', // restore to previous backup or on upgrade
|
||||
ISTATE_PENDING_UPDATE: 'pending_update', // update from installed state preserving data
|
||||
@@ -803,6 +821,359 @@ function install(data, user, auditSource, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function setAccessRestriction(appId, accessRestriction, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof accessRestriction, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateAccessRestriction(accessRestriction);
|
||||
if (error) return callback(error);
|
||||
|
||||
appdb.update(appId, { accessRestriction: accessRestriction }, function (error) {
|
||||
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));
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, accessRestriction: accessRestriction });
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setLabel(appId, label, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof label, 'string');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateLabel(label);
|
||||
if (error) return callback(error);
|
||||
|
||||
appdb.update(appId, { label: label }, function (error) {
|
||||
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));
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, label: label });
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setTags(appId, tags, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert(Array.isArray(tags));
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateTags(tags);
|
||||
if (error) return callback(error);
|
||||
|
||||
appdb.update(appId, { tags: tags }, function (error) {
|
||||
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));
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, tags: tags });
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setIcon(appId, icon, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert(icon === null || typeof icon === 'string');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
if (icon) {
|
||||
if (!validator.isBase64(icon)) return callback(new AppsError(AppsError.BAD_FIELD, 'icon is not base64', { field: 'icon' }));
|
||||
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_ICONS_DIR, appId + '.user.png'), Buffer.from(icon, 'base64'))) {
|
||||
return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving icon:' + safe.error.message));
|
||||
}
|
||||
} else {
|
||||
safe.fs.unlinkSync(path.join(paths.APP_ICONS_DIR, appId + '.user.png'));
|
||||
}
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, icon: icon });
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function setMemoryLimit(appId, memoryLimit, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof memoryLimit, 'number');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateMemoryLimit(app.manifest, memoryLimit);
|
||||
if (error) return callback(error);
|
||||
|
||||
scheduleTask(appId, {}, { installationState: exports.ISTATE_PENDING_CREATE_CONTAINER, memoryLimit: memoryLimit }, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, memoryLimit: memoryLimit, taskId: result.taskId });
|
||||
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setEnvironment(appId, env, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof env, 'object');
|
||||
assert.strictEqual(typeof memoryLimit, 'number');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateEnv(env);
|
||||
if (error) return callback(error);
|
||||
|
||||
scheduleTask(appId, {}, { installationState: exports.ISTATE_PENDING_CREATE, env: env }, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, env: env, taskId: result.taskId });
|
||||
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setDebugMode(appId, debugMode, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof debugMode, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateDebugMode(debugMode);
|
||||
if (error) return callback(error);
|
||||
|
||||
scheduleTask(appId, {}, { installationState: exports.ISTATE_PENDING_CREATE, debugMode: debugMode }, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, debugMode: debugMode, taskId: result.taskId });
|
||||
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setMailbox(appId, mailboxName, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert(!mailboxName || typeof mailboxName === 'string');
|
||||
assert.strictEqual(typeof memoryLimit, 'number');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
if (mailboxName) {
|
||||
error = mail.validateName(mailboxName);
|
||||
if (error) return callback(new AppsError(AppsError.BAD_FIELD, error.message, { field: 'mailboxName' }));
|
||||
} else {
|
||||
mailboxName = mailboxNameForLocation(app.location, app.manifest);
|
||||
}
|
||||
|
||||
scheduleTask(appId, {}, { installationState: exports.ISTATE_PENDING_CREATE, mailboxName: mailboxName }, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, mailboxName: mailboxName, taskId: result.taskId });
|
||||
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setAutomaticBackup(appId, enable, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof enable, 'boolean');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
appdb.update(appId, { enableBackup: enable }, function (error) {
|
||||
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));
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, enableBackup: enable });
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setAutomaticUpdate(appId, enable, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof enable, 'boolean');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
appdb.update(appId, { enableAutomaticUpdate: enable }, function (error) {
|
||||
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));
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, enableAutomaticUpdate: enable });
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setRobotsTxt(appId, robotsTxt, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert(!robotsTxt || typeof robotsTxt === 'string');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateRobotsTxt(robotsTxt);
|
||||
if (error) return callback(error);
|
||||
|
||||
appdb.update(appId, { robotsTxt: robotsTxt }, function (error) {
|
||||
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));
|
||||
|
||||
// TODO: call reverseProxy config re-write here
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, robotsTxt: robotsTxt });
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setCertificate(appId, bundle, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert(bundle && typeof bundle === 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
domains.get(app.domain, function (error, domainObject) {
|
||||
if (error && error.reason === DomainsError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such domain'));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Could not get domain info:' + error.message));
|
||||
|
||||
|
||||
if (bundle.cert && bundle.key) {
|
||||
error = reverseProxy.validateCertificate(app.location, domainObject, { cert: bundle.cert, key: bundle.key });
|
||||
if (error) return callback(new AppsError(AppsError.BAD_FIELD, error.message, { field: 'cert' }));
|
||||
}
|
||||
|
||||
error = reverseProxy.setAppCertificateSync(app.location, domainObject, { cert: bundle.cert, key: bundle.key });
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error setting cert: ' + error.message));
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, cert: bundle.cert, key: bundle.key });
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setLocation(appId, data, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
let values = {
|
||||
installationState: exports.ISTATE_PENDING_LOCATION_CHANGE,
|
||||
// these are intentionally reset, if not set
|
||||
portBindings: null,
|
||||
alternateDomains: []
|
||||
};
|
||||
|
||||
values.location = data.location.toLowerCase();
|
||||
values.domain = data.domain.toLowerCase();
|
||||
|
||||
if ('portBindings' in data) {
|
||||
error = validatePortBindings(data.portBindings, app.manifest);
|
||||
if (error) return callback(error);
|
||||
|
||||
values.portBindings = translatePortBindings(data.portBindings || null, app.manifest);
|
||||
}
|
||||
|
||||
if ('alternateDomains' in data) {
|
||||
// TODO validate all subdomains [{ domain: '', subdomain: ''}]
|
||||
values.alternateDomains = data.alternateDomains;
|
||||
}
|
||||
|
||||
domains.get(values.domain, function (error, domainObject) {
|
||||
if (error && error.reason === DomainsError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such domain'));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Could not get domain info:' + error.message));
|
||||
|
||||
error = domains.validateHostname(values.location, domainObject);
|
||||
if (error) return callback(new AppsError(AppsError.BAD_FIELD, 'Bad location: ' + error.message, { field: 'location' }));
|
||||
|
||||
scheduleTask(appId, { oldConfig: getAppConfig(app) }, values, function (error, result) {
|
||||
if (error && error.reason === AppsError.ALREADY_EXISTS) error = getDuplicateErrorDetails(error.message, values.location, domainObject, data.portBindings, app.alternateDomains);
|
||||
if (error) return callback(error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, config: values, taskId: result.taskId });
|
||||
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setDataDir(appId, dataDir, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof dataDir, 'string');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateDataDir(dataDir);
|
||||
if (error) return callback(error);
|
||||
|
||||
scheduleTask(appId, { oldConfig: getAppConfig(app) }, { installationState: exports.ISTATE_PENDING_DATA_DIR_MIGRATION, dataDir: dataDir }, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: app, dataDir: dataDir, taskId: result.taskId });
|
||||
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function configure(appId, data, user, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert(data && typeof data === 'object');
|
||||
|
||||
+128
-4
@@ -438,7 +438,7 @@ function waitForDnsPropagation(app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function migrateDataDir(app, sourceDir, callback) {
|
||||
function moveDataDir(app, sourceDir, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof sourceDir, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
@@ -446,9 +446,9 @@ function migrateDataDir(app, sourceDir, callback) {
|
||||
let resolvedSourceDir = apps.getDataDir(app, sourceDir);
|
||||
let resolvedTargetDir = apps.getDataDir(app, app.dataDir);
|
||||
|
||||
debug(`migrateDataDir: migrating data from ${resolvedSourceDir} to ${resolvedTargetDir}`);
|
||||
debug(`moveDataDir: migrating data from ${resolvedSourceDir} to ${resolvedTargetDir}`);
|
||||
|
||||
shell.sudo('migrateDataDir', [ MV_VOLUME_CMD, resolvedSourceDir, resolvedTargetDir ], {}, function (error) {
|
||||
shell.sudo('moveDataDir', [ MV_VOLUME_CMD, resolvedSourceDir, resolvedTargetDir ], {}, function (error) {
|
||||
if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `Error migrating data directory: ${error.message}`));
|
||||
|
||||
callback(null);
|
||||
@@ -607,6 +607,127 @@ function backup(app, progressCallback, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function create(app, progressCallback, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof progressCallback, 'function');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
async.series([
|
||||
progressCallback.bind(null, { percent: 10, message: 'Cleaning up old install' }),
|
||||
stopApp.bind(null, app, progressCallback),
|
||||
deleteContainers.bind(null, app, { managedOnly: true }),
|
||||
|
||||
progressCallback.bind(null, { percent: 60, message: 'Creating container' }),
|
||||
createContainer.bind(null, app),
|
||||
|
||||
progressCallback.bind(null, { percent: 80, message: 'Starting app' }),
|
||||
runApp.bind(null, app, progressCallback),
|
||||
|
||||
progressCallback.bind(null, { percent: 100, message: 'Done' }),
|
||||
updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null })
|
||||
], function seriesDone(error) {
|
||||
if (error) {
|
||||
debugApp(app, 'error creating : %s', error);
|
||||
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: error.toPlainObject ? error.toPlainObject() : error.message }, callback.bind(null, error));
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function changeLocation(app, oldConfig, progressCallback, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof oldConfig, 'object');
|
||||
assert.strictEqual(typeof progressCallback, 'function');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const locationChanged = oldConfig.fqdn !== app.fqdn;
|
||||
|
||||
async.series([
|
||||
progressCallback.bind(null, { percent: 10, message: 'Cleaning up old install' }),
|
||||
unconfigureReverseProxy.bind(null, app),
|
||||
stopApp.bind(null, app, progressCallback),
|
||||
deleteContainers.bind(null, app, { managedOnly: true }),
|
||||
function (next) {
|
||||
let obsoleteDomains = oldConfig.alternateDomains.filter(function (o) {
|
||||
return !app.alternateDomains.some(function (n) { return n.subdomain === o.subdomain && n.domain === o.domain; });
|
||||
});
|
||||
|
||||
if (locationChanged) obsoleteDomains.push({ subdomain: oldConfig.location, domain: oldConfig.domain });
|
||||
|
||||
if (obsoleteDomains.length === 0) return next();
|
||||
|
||||
unregisterSubdomains(app, obsoleteDomains, next);
|
||||
},
|
||||
|
||||
progressCallback.bind(null, { percent: 30, message: 'Registering subdomains' }),
|
||||
registerSubdomains.bind(null, app, !locationChanged /* overwrite */), // if location changed, do not overwrite to detect conflicts
|
||||
|
||||
// re-setup addons since they rely on the app's fqdn (e.g oauth)
|
||||
progressCallback.bind(null, { percent: 50, message: 'Setting up addons' }),
|
||||
addons.setupAddons.bind(null, app, app.manifest.addons),
|
||||
|
||||
progressCallback.bind(null, { percent: 60, message: 'Creating container' }),
|
||||
createContainer.bind(null, app),
|
||||
|
||||
runApp.bind(null, app, progressCallback),
|
||||
|
||||
progressCallback.bind(null, { percent: 80, message: 'Waiting for DNS propagation' }),
|
||||
exports._waitForDnsPropagation.bind(null, app),
|
||||
|
||||
progressCallback.bind(null, { percent: 90, message: 'Configuring reverse proxy' }),
|
||||
configureReverseProxy.bind(null, app),
|
||||
|
||||
progressCallback.bind(null, { percent: 100, message: 'Done' }),
|
||||
updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null })
|
||||
], function seriesDone(error) {
|
||||
if (error) {
|
||||
debugApp(app, 'error reconfiguring : %s', error);
|
||||
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: error.toPlainObject ? error.toPlainObject() : error.message }, callback.bind(null, error));
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function migrateDataDir(app, oldConfig, progressCallback, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof oldConfig, 'object');
|
||||
assert.strictEqual(typeof progressCallback, 'function');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const dataDirChanged = oldConfig.dataDir !== app.dataDir;
|
||||
|
||||
async.series([
|
||||
progressCallback.bind(null, { percent: 10, message: 'Cleaning up old install' }),
|
||||
stopApp.bind(null, app, progressCallback),
|
||||
deleteContainers.bind(null, app, { managedOnly: true }),
|
||||
|
||||
progressCallback.bind(null, { percent: 45, message: 'Ensuring app data directory' }),
|
||||
createAppDir.bind(null, app),
|
||||
|
||||
// migrate dataDir
|
||||
function (next) {
|
||||
if (!dataDirChanged) return next();
|
||||
|
||||
moveDataDir(app, oldConfig.dataDir, next);
|
||||
},
|
||||
|
||||
progressCallback.bind(null, { percent: 60, message: 'Creating container' }),
|
||||
createContainer.bind(null, app),
|
||||
|
||||
progressCallback.bind(null, { percent: 60, message: 'Starting app' }),
|
||||
runApp.bind(null, app, progressCallback),
|
||||
|
||||
progressCallback.bind(null, { percent: 100, message: 'Done' }),
|
||||
updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null })
|
||||
], function seriesDone(error) {
|
||||
if (error) {
|
||||
debugApp(app, 'error reconfiguring : %s', error);
|
||||
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: error.toPlainObject ? error.toPlainObject() : error.message }, callback.bind(null, error));
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
// note that configure is called after an infra update as well
|
||||
function configure(app, oldConfig, progressCallback, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
@@ -658,7 +779,7 @@ function configure(app, oldConfig, progressCallback, callback) {
|
||||
function (next) {
|
||||
if (!dataDirChanged) return next();
|
||||
|
||||
migrateDataDir(app, oldConfig.dataDir, next);
|
||||
moveDataDir(app, oldConfig.dataDir, next);
|
||||
},
|
||||
|
||||
progressCallback.bind(null, { percent: 60, message: 'Creating container' }),
|
||||
@@ -894,6 +1015,9 @@ function run(appId, args, progressCallback, callback) {
|
||||
switch (app.installationState) {
|
||||
case apps.ISTATE_PENDING_INSTALL: return install(app, args.restoreConfig || {}, progressCallback, callback);
|
||||
case apps.ISTATE_PENDING_CONFIGURE: return configure(app, args.oldConfig, progressCallback, callback);
|
||||
case apps.ISTATE_PENDING_CREATE_CONTAINER: return create(app, progressCallback, callback);
|
||||
case apps.ISTATE_PENDING_LOCATION_CHANGE: return changeLocation(app, args.oldConfig, progressCallback, callback);
|
||||
case apps.ISTATE_PENDING_DATA_DIR_MIGRATION: return migrateDataDir(app, args.oldConfig, progressCallback, callback);
|
||||
case apps.ISTATE_PENDING_UNINSTALL: return uninstall(app, progressCallback, callback);
|
||||
case apps.ISTATE_PENDING_CLONE: return install(app, args.restoreConfig || {}, progressCallback, callback);
|
||||
case apps.ISTATE_PENDING_RESTORE: return install(app, args.restoreConfig || {}, progressCallback, callback);
|
||||
|
||||
+216
-4
@@ -14,6 +14,21 @@ exports = module.exports = {
|
||||
getLogStream: getLogStream,
|
||||
listBackups: listBackups,
|
||||
|
||||
setAccessRestriction: setAccessRestriction,
|
||||
setLabel: setLabel,
|
||||
setTags: setTags,
|
||||
setIcon: setIcon,
|
||||
setMemoryLimit: setMemoryLimit,
|
||||
setAutomaticBackup: setAutomaticBackup,
|
||||
setAutomaticUpdate: setAutomaticUpdate,
|
||||
setRobotsTxt: setRobotsTxt,
|
||||
setCertificate: setCertificate,
|
||||
setDebugMode: setDebugMode,
|
||||
setEnvironment: setEnvironment,
|
||||
setMailbox: setMailbox,
|
||||
setLocation: setLocation,
|
||||
setDataDir: setDataDir,
|
||||
|
||||
stopApp: stopApp,
|
||||
startApp: startApp,
|
||||
exec: exec,
|
||||
@@ -146,6 +161,203 @@ function installApp(req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
function setAccessRestriction(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (typeof req.body.accessRestriction !== 'object') return next(new HttpError(400, 'accessRestriction must be an object'));
|
||||
|
||||
apps.setAccessRestriction(req.params.id, req.body.accessRestriction, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, {}));
|
||||
});
|
||||
}
|
||||
|
||||
function setLabel(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (typeof req.body.label !== 'string') return next(new HttpError(400, 'label must be a string'));
|
||||
|
||||
apps.setLabel(req.params.id, req.body.label, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, {}));
|
||||
});
|
||||
}
|
||||
|
||||
function setTags(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (!Array.isArray(req.body.tags)) return next(new HttpError(400, 'tags must be an array'));
|
||||
if (req.body.tags.some((t) => typeof t !== 'string')) return next(new HttpError(400, 'tags array must contain strings'));
|
||||
|
||||
apps.setTags(req.params.id, req.body.tags, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, {}));
|
||||
});
|
||||
}
|
||||
|
||||
function setIcon(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (req.body.icon !== null && typeof req.body.icon !== 'string') return next(new HttpError(400, 'icon is null or a base-64 image string'));
|
||||
|
||||
apps.setIcon(req.params.id, req.body.icon, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, {}));
|
||||
});
|
||||
}
|
||||
|
||||
function setMemoryLimit(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (typeof req.body.memoryLimit !== 'number') return next(new HttpError(400, 'memoryLimit is not a number'));
|
||||
|
||||
apps.setMemoryLimit(req.params.id, req.body.memoryLimit, auditSource.fromRequest(req), function (error, result) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, { taskId: result.taskId }));
|
||||
});
|
||||
}
|
||||
|
||||
function setAutomaticBackup(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean'));
|
||||
|
||||
apps.setAutomaticBackup(req.params.id, req.body.enable, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, {}));
|
||||
});
|
||||
}
|
||||
|
||||
function setAutomaticUpdate(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean'));
|
||||
|
||||
apps.setAutomaticUpdate(req.params.id, req.body.enable, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, {}));
|
||||
});
|
||||
}
|
||||
|
||||
function setRobotsTxt(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (req.body.robotsTxt !== null && typeof req.body.robotsTxt !== 'string') return next(new HttpError(400, 'robotsTxt is not a string'));
|
||||
|
||||
apps.setRobotsTxt(req.params.id, req.body.robotsTxt, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, {}));
|
||||
});
|
||||
}
|
||||
|
||||
function setCertificate(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (req.body.key !== null && typeof req.body.cert !== 'string') return next(new HttpError(400, 'cert must be a string'));
|
||||
if (req.body.cert !== null && typeof req.body.key !== 'string') return next(new HttpError(400, 'key must be a string'));
|
||||
if (req.body.cert && !req.body.key) return next(new HttpError(400, 'key must be provided'));
|
||||
if (!req.body.cert && req.body.key) return next(new HttpError(400, 'cert must be provided'));
|
||||
|
||||
apps.setCertificate(req.params.id, req.body, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, {}));
|
||||
});
|
||||
}
|
||||
|
||||
function setEnvironment(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (!req.body.env || typeof req.body.env !== 'object') return next(new HttpError(400, 'env must be an object'));
|
||||
if (Object.keys(req.body.env).some(function (key) { return typeof req.body.env[key] !== 'string'; })) return next(new HttpError(400, 'env must contain values as strings'));
|
||||
|
||||
apps.setEnvironment(req.params.id, req.body.env, auditSource.fromRequest(req), function (error, result) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, { taskId: result.taskId }));
|
||||
});
|
||||
}
|
||||
|
||||
function setDebugMode(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (req.body.debugMode !== null && typeof req.body.debugMode !== 'object') return next(new HttpError(400, 'debugMode must be an object'));
|
||||
|
||||
apps.setDebugMode(req.params.id, req.body.debugMode, auditSource.fromRequest(req), function (error, result) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, { taskId: result.taskId }));
|
||||
});
|
||||
}
|
||||
|
||||
function setMailbox(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (req.body.mailboxName !== null && typeof req.body.mailboxName !== 'string') return next(new HttpError(400, 'mailboxName must be a string'));
|
||||
|
||||
apps.setMailbox(req.params.id, req.body.mailboxName, auditSource.fromRequest(req), function (error, result) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, { taskId: result.taskId }));
|
||||
});
|
||||
}
|
||||
|
||||
function setLocation(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (!req.body.location) return next(new HttpError(400, 'location is required'));
|
||||
if (typeof req.body.location !== 'string') return next(new HttpError(400, 'location must be string'));
|
||||
if (!req.body.domain) return next(new HttpError(400, 'domain is required'));
|
||||
if (typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be string'));
|
||||
|
||||
if ('portBindings' in req.body && typeof req.body.portBindings !== 'object') return next(new HttpError(400, 'portBindings must be an object'));
|
||||
|
||||
if ('alternateDomains' in req.body) {
|
||||
if (!Array.isArray(req.body.alternateDomains)) return next(new HttpError(400, 'alternateDomains must be an array'));
|
||||
if (req.body.alternateDomains.some(function (d) { return (typeof d.domain !== 'string' || typeof d.subdomain !== 'string'); })) return next(new HttpError(400, 'alternateDomains array must contain objects with domain and subdomain strings'));
|
||||
}
|
||||
|
||||
apps.setLocation(req.params.id, req.body, auditSource.fromRequest(req), function (error, result) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, { taskId: result.taskId }));
|
||||
});
|
||||
}
|
||||
|
||||
function setDataDir(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
if (typeof req.body.dataDir !== 'string') return next(new HttpError(400, 'dataDir must be a string'));
|
||||
|
||||
apps.setDataDir(req.params.id, req.body.dataDir, auditSource.fromRequest(req), function (error, result) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, { taskId: result.taskId }));
|
||||
});
|
||||
}
|
||||
|
||||
function configureApp(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
@@ -268,10 +480,10 @@ function startApp(req, res, next) {
|
||||
|
||||
debug('Start app id:%s', req.params.id);
|
||||
|
||||
apps.start(req.params.id, function (error) {
|
||||
apps.start(req.params.id, function (error, result) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, { }));
|
||||
next(new HttpSuccess(202, { taskId: result.taskId }));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -280,10 +492,10 @@ function stopApp(req, res, next) {
|
||||
|
||||
debug('Stop app id:%s', req.params.id);
|
||||
|
||||
apps.stop(req.params.id, function (error) {
|
||||
apps.stop(req.params.id, function (error, result) {
|
||||
if (error) return next(toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, { }));
|
||||
next(new HttpSuccess(202, { taskId: result.taskId }));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+1030
-831
File diff suppressed because it is too large
Load Diff
@@ -226,7 +226,23 @@ function initializeExpressSync() {
|
||||
|
||||
router.post('/api/v1/apps/install', appsManageScope, routes.apps.installApp);
|
||||
router.post('/api/v1/apps/:id/uninstall', appsManageScope, routes.apps.uninstallApp);
|
||||
|
||||
router.post('/api/v1/apps/:id/configure', appsManageScope, routes.apps.configureApp);
|
||||
router.post('/api/v1/apps/:id/configure/access_restriction', appsManageScope, routes.apps.setAccessRestriction);
|
||||
router.post('/api/v1/apps/:id/configure/label', appsManageScope, routes.apps.setLabel);
|
||||
router.post('/api/v1/apps/:id/configure/tags', appsManageScope, routes.apps.setTags);
|
||||
router.post('/api/v1/apps/:id/configure/icon', appsManageScope, routes.apps.setIcon);
|
||||
router.post('/api/v1/apps/:id/configure/memory_limit', appsManageScope, routes.apps.setMemoryLimit);
|
||||
router.post('/api/v1/apps/:id/configure/automatic_backup', appsManageScope, routes.apps.setAutomaticBackup);
|
||||
router.post('/api/v1/apps/:id/configure/automatic_update', appsManageScope, routes.apps.setAutomaticUpdate);
|
||||
router.post('/api/v1/apps/:id/configure/robots_txt', appsManageScope, routes.apps.setRobotsTxt);
|
||||
router.post('/api/v1/apps/:id/configure/cert', appsManageScope, routes.apps.setCertificate);
|
||||
router.post('/api/v1/apps/:id/configure/debug_mode', appsManageScope, routes.apps.setDebugMode);
|
||||
router.post('/api/v1/apps/:id/configure/mailbox', appsManageScope, routes.apps.setMailbox);
|
||||
router.post('/api/v1/apps/:id/configure/env', appsManageScope, routes.apps.setEnvironment);
|
||||
router.post('/api/v1/apps/:id/configure/data_dir', appsManageScope, routes.apps.setDataDir);
|
||||
router.post('/api/v1/apps/:id/configure/location', appsManageScope, routes.apps.setLocation);
|
||||
|
||||
router.post('/api/v1/apps/:id/update', appsManageScope, routes.apps.updateApp);
|
||||
router.post('/api/v1/apps/:id/restore', appsManageScope, routes.apps.restoreApp);
|
||||
router.post('/api/v1/apps/:id/backup', appsManageScope, routes.apps.backupApp);
|
||||
|
||||
Reference in New Issue
Block a user