From 9a2ed4f2c83425deba51465b91aa92bcc391d6b9 Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Thu, 16 Sep 2021 13:59:03 -0700 Subject: [PATCH] apptask: asyncify --- src/apptask.js | 874 ++++++++++++++++---------------------- src/backups.js | 7 +- src/backuptask.js | 242 ++++------- src/database.js | 18 +- src/provision.js | 2 +- src/taskworker.js | 2 +- src/test/apptask-test.js | 71 ++-- src/test/database-test.js | 31 +- src/updater.js | 2 +- 9 files changed, 505 insertions(+), 744 deletions(-) diff --git a/src/apptask.js b/src/apptask.js index 8a13646b6..95c6df11e 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -14,7 +14,6 @@ exports = module.exports = { const apps = require('./apps.js'), assert = require('assert'), - async = require('async'), auditSource = require('./auditsource.js'), backuptask = require('./backuptask.js'), BoxError = require('./boxerror.js'), @@ -101,51 +100,43 @@ async function createContainer(app) { await addCollectdProfile(app); } -function deleteContainers(app, options, callback) { +async function deleteContainers(app, options) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof options, 'object'); - assert.strictEqual(typeof callback, 'function'); debugApp(app, 'deleting app containers (app, scheduler)'); - async.series([ - // remove configs that rely on container id - removeCollectdProfile.bind(null, app), - removeLogrotateConfig.bind(null, app), - docker.stopContainers.bind(null, app.id), - docker.deleteContainers.bind(null, app.id, options), - updateApp.bind(null, app, { containerId: null }) - ], callback); + // remove configs that rely on container id + await removeCollectdProfile(app); + await removeLogrotateConfig(app); + await docker.stopContainers(app.id); + await docker.deleteContainers(app.id, options); + await updateApp(app, { containerId: null }); } -function createAppDir(app, callback) { +async function createAppDir(app) { assert.strictEqual(typeof app, 'object'); - assert.strictEqual(typeof callback, 'function'); const appDir = path.join(paths.APPS_DATA_DIR, app.id); - fs.mkdir(appDir, { recursive: true }, function (error) { - if (error) return callback(new BoxError(BoxError.FS_ERROR, `Error creating directory: ${error.message}`, { appDir })); - - callback(null); - }); + const [error] = await safe(fs.promises.mkdir(appDir, { recursive: true })); + if (error) throw new BoxError(BoxError.FS_ERROR, `Error creating directory: ${error.message}`, { appDir }); } -function deleteAppDir(app, options, callback) { +async function deleteAppDir(app, options) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof options, 'object'); - assert.strictEqual(typeof callback, 'function'); const appDataDir = path.join(paths.APPS_DATA_DIR, app.id); // resolve any symlinked data dir const stat = safe.fs.lstatSync(appDataDir); - if (!stat) return callback(null); + if (!stat) return; const resolvedAppDataDir = stat.isSymbolicLink() ? safe.fs.readlinkSync(appDataDir) : appDataDir; if (safe.fs.existsSync(resolvedAppDataDir)) { const entries = safe.fs.readdirSync(resolvedAppDataDir); - if (!entries) return callback(new BoxError(BoxError.FS_ERROR, `Error listing ${resolvedAppDataDir}: ${safe.error.message}`)); + if (!entries) throw 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 @@ -159,16 +150,14 @@ function deleteAppDir(app, options, callback) { if (options.removeDirectory) { if (stat.isSymbolicLink()) { if (!safe.fs.unlinkSync(appDataDir)) { - if (safe.error.code !== 'ENOENT') return callback(new BoxError(BoxError.FS_ERROR, `Error unlinking dir ${appDataDir} : ${safe.error.message}`)); + if (safe.error.code !== 'ENOENT') throw new BoxError(BoxError.FS_ERROR, `Error unlinking dir ${appDataDir} : ${safe.error.message}`); } } else { if (!safe.fs.rmdirSync(appDataDir)) { - if (safe.error.code !== 'ENOENT') return callback(new BoxError(BoxError.FS_ERROR, `Error removing dir ${appDataDir} : ${safe.error.message}`)); + if (safe.error.code !== 'ENOENT') throw new BoxError(BoxError.FS_ERROR, `Error removing dir ${appDataDir} : ${safe.error.message}`); } } } - - callback(null); } async function addCollectdProfile(app) { @@ -210,29 +199,22 @@ async function removeLogrotateConfig(app) { if (error) throw new BoxError(BoxError.LOGROTATE_ERROR, `Error removing logrotate config: ${error.message}`); } -function cleanupLogs(app, callback) { +async function cleanupLogs(app) { assert.strictEqual(typeof app, 'object'); - assert.strictEqual(typeof callback, 'function'); // note that redis container logs are cleaned up by the addon - fs.rm(path.join(paths.LOG_DIR, app.id), { force: true, recursive: true }, function (error) { - if (error) debugApp(app, 'cannot cleanup logs:', error); - - callback(null); - }); + const [error] = await safe(fs.promises.rm(path.join(paths.LOG_DIR, app.id), { force: true, recursive: true })); + if (error) debugApp(app, 'cannot cleanup logs:', error); } -function verifyManifest(manifest, callback) { +async function verifyManifest(manifest) { assert.strictEqual(typeof manifest, 'object'); - assert.strictEqual(typeof callback, 'function'); - var error = manifestFormat.parse(manifest); - if (error) return callback(new BoxError(BoxError.BAD_FIELD, `Manifest error: ${error.message}`, { field: 'manifest' })); + let error = manifestFormat.parse(manifest); + if (error) throw new BoxError(BoxError.BAD_FIELD, `Manifest error: ${error.message}`, { field: 'manifest' }); error = apps.checkManifestConstraints(manifest); - if (error) return callback(new BoxError(BoxError.CONFLICT, `Manifest constraint check failed: ${error.message}`, { field: 'manifest' })); - - callback(null); + if (error) throw new BoxError(BoxError.CONFLICT, `Manifest constraint check failed: ${error.message}`, { field: 'manifest' }); } async function downloadIcon(app) { @@ -277,23 +259,19 @@ async function waitForDnsPropagation(app) { } } -function moveDataDir(app, targetDir, callback) { +async function moveDataDir(app, targetDir) { assert.strictEqual(typeof app, 'object'); assert(targetDir === null || typeof targetDir === 'string'); - assert.strictEqual(typeof callback, 'function'); - let resolvedSourceDir = apps.getDataDir(app, app.dataDir); - let resolvedTargetDir = apps.getDataDir(app, targetDir); + const resolvedSourceDir = apps.getDataDir(app, app.dataDir); + const resolvedTargetDir = apps.getDataDir(app, targetDir); debugApp(app, `moveDataDir: migrating data from ${resolvedSourceDir} to ${resolvedTargetDir}`); - if (resolvedSourceDir === resolvedTargetDir) return callback(); + if (resolvedSourceDir === resolvedTargetDir) return; - 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); - }); + const [error] = await safe(shell.promises.sudo('moveDataDir', [ MV_VOLUME_CMD, resolvedSourceDir, resolvedTargetDir ], {})); + if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error migrating data directory: ${error.message}`); } async function downloadImage(manifest) { @@ -316,347 +294,258 @@ async function startApp(app) { await docker.startContainer(app.id); } -function install(app, args, progressCallback, callback) { +async function install(app, args, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); const restoreConfig = args.restoreConfig; // has to be set when restoring const overwriteDns = args.overwriteDns; const skipDnsSetup = args.skipDnsSetup; const oldManifest = args.oldManifest; - async.series([ - // this protects against the theoretical possibility of an app being marked for install/restore from - // a previous version of box code - verifyManifest.bind(null, app.manifest), + // this protects against the theoretical possibility of an app being marked for install/restore from + // a previous version of box code + await verifyManifest(app.manifest); - // teardown for re-installs - progressCallback.bind(null, { percent: 10, message: 'Cleaning up old install' }), - reverseProxy.unconfigureApp.bind(null, app), - deleteContainers.bind(null, app, { managedOnly: true }), - async function teardownAddons() { - // when restoring, app does not require these addons anymore. remove carefully to preserve the db passwords - let addonsToRemove; - if (oldManifest) { - addonsToRemove = _.omit(oldManifest.addons, Object.keys(app.manifest.addons)); - } else { - addonsToRemove = app.manifest.addons; - } + // teardown for re-installs + await progressCallback({ percent: 10, message: 'Cleaning up old install' }); + await reverseProxy.unconfigureApp(app); + await deleteContainers(app, { managedOnly: true }); - await services.teardownAddons(app, addonsToRemove); - }, + // when restoring, app does not require these addons anymore. remove carefully to preserve the db passwords + let addonsToRemove; + if (oldManifest) { + addonsToRemove = _.omit(oldManifest.addons, Object.keys(app.manifest.addons)); + } else { + addonsToRemove = app.manifest.addons; + } + await services.teardownAddons(app, addonsToRemove); - function deleteAppDirIfNeeded(done) { - if (restoreConfig && !restoreConfig.backupId) return done(); // in-place import should not delete data dir + if (!restoreConfig || restoreConfig.backupId) { // in-place import should not delete data dir + await deleteAppDir(app, { removeDirectory: false }); // do not remove any symlinked appdata dir + } - deleteAppDir(app, { removeDirectory: false }, done); // do not remove any symlinked appdata dir - }, + if (oldManifest && oldManifest.dockerImage === app.manifest.dockerImage) { + await docker.deleteImage(oldManifest); + } - async function deleteImageIfChanged() { - if (!oldManifest || oldManifest.dockerImage === app.manifest.dockerImage) return; + // allocating container ip here, lets the users "repair" an app if allocation fails at apps.add time + await allocateContainerIp(app); - await docker.deleteImage(oldManifest); - }, + await progressCallback({ percent: 20, message: 'Downloading icon' }); + await downloadIcon(app); - // allocating container ip here, lets the users "repair" an app if allocation fails at apps.add time - allocateContainerIp.bind(null, app), + if (!skipDnsSetup) { + await progressCallback({ percent: 30, message: 'Registering subdomains' }); - progressCallback.bind(null, { percent: 20, message: 'Downloading icon' }), - downloadIcon.bind(null, app), + await dns.registerLocations([ { subdomain: app.location, domain: app.domain }].concat(app.alternateDomains).concat(app.aliasDomains), { overwriteDns }, progressCallback); + } - function setupDnsIfNeeded(done) { - if (skipDnsSetup) return done(); + await progressCallback({ percent: 40, message: 'Downloading image' }); + await downloadImage(app.manifest); - async.series([ - progressCallback.bind(null, { percent: 30, message: 'Registering subdomains' }), + await progressCallback({ percent: 50, message: 'Creating app data directory' }); + await createAppDir(app); - dns.registerLocations.bind(null, [ { subdomain: app.location, domain: app.domain }].concat(app.alternateDomains).concat(app.aliasDomains), { overwriteDns }, progressCallback) - ], done); - }, + // restore from backup + if (!restoreConfig) { + await progressCallback({ percent: 60, message: 'Setting up addons' }); + await services.setupAddons(app, app.manifest.addons); + } else if (!restoreConfig.backupId) { // in-place import + await progressCallback({ percent: 60, message: 'Importing addons in-place' }); + await services.setupAddons(app, app.manifest.addons); + await services.clearAddons(app, _.omit(app.manifest.addons, 'localstorage')); + await apps.restoreConfig(app); + await services.restoreAddons(app, app.manifest.addons); + } else { + await progressCallback({ percent: 65, message: 'Download backup and restoring addons' }); + await services.setupAddons(app, app.manifest.addons); + await services.clearAddons(app, app.manifest.addons); + await backuptask.downloadApp(app, restoreConfig, (progress) => { progressCallback({ percent: 65, message: progress.message }); }); - progressCallback.bind(null, { percent: 40, message: 'Downloading image' }), - downloadImage.bind(null, app.manifest), + if (app.installationState === apps.ISTATE_PENDING_IMPORT) await apps.restoreConfig(app); + await progressCallback({ percent: 70, message: 'Restoring addons' }); + await services.restoreAddons(app, app.manifest.addons); + } - progressCallback.bind(null, { percent: 50, message: 'Creating app data directory' }), - createAppDir.bind(null, app), + await progressCallback({ percent: 80, message: 'Creating container' }); + await createContainer(app); - function restoreFromBackup(next) { - if (!restoreConfig) { - async.series([ - progressCallback.bind(null, { percent: 60, message: 'Setting up addons' }), - services.setupAddons.bind(null, app, app.manifest.addons), - ], next); - } else if (!restoreConfig.backupId) { // in-place import - async.series([ - progressCallback.bind(null, { percent: 60, message: 'Importing addons in-place' }), - services.setupAddons.bind(null, app, app.manifest.addons), - services.clearAddons.bind(null, app, _.omit(app.manifest.addons, 'localstorage')), - apps.restoreConfig.bind(null, app), - services.restoreAddons.bind(null, app, app.manifest.addons), - ], next); - } else { - async.series([ - progressCallback.bind(null, { percent: 65, message: 'Download backup and restoring addons' }), - services.setupAddons.bind(null, app, app.manifest.addons), - services.clearAddons.bind(null, app, app.manifest.addons), - backuptask.downloadApp.bind(null, app, restoreConfig, (progress) => { - progressCallback({ percent: 65, message: progress.message }); - }), - async () => { if (app.installationState === apps.ISTATE_PENDING_IMPORT) await apps.restoreConfig(app); }, - progressCallback.bind(null, { percent: 70, message: 'Restoring addons' }), - services.restoreAddons.bind(null, app, app.manifest.addons) - ], next); - } - }, + await startApp(app); - progressCallback.bind(null, { percent: 80, message: 'Creating container' }), - createContainer.bind(null, app), + if (!skipDnsSetup) { + await progressCallback({ percent: 85, message: 'Waiting for DNS propagation' }); + await exports._waitForDnsPropagation(app); + } - startApp.bind(null, app), + await progressCallback({ percent: 95, message: 'Configuring reverse proxy' }); + await reverseProxy.configureApp(app, auditSource.APPTASK); - function waitForDns(done) { - if (skipDnsSetup) return done(); - - async.series([ - progressCallback.bind(null, { percent: 85, message: 'Waiting for DNS propagation' }), - exports._waitForDnsPropagation.bind(null, app), - ], done); - }, - - progressCallback.bind(null, { percent: 95, message: 'Configuring reverse proxy' }), - reverseProxy.configureApp.bind(null, app, auditSource.APPTASK), - - progressCallback.bind(null, { percent: 100, message: 'Done' }), - updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }) - ], async function seriesDone(error) { - if (error) { - debugApp(app, 'error installing app:', error); - await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) })); - } - callback(error); - }); + await progressCallback({ percent: 100, message: 'Done' }); + await updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }); } -function backup(app, args, progressCallback, callback) { +async function backup(app, args, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - async.series([ - progressCallback.bind(null, { percent: 10, message: 'Backing up' }), - backuptask.backupApp.bind(null, app, { snapshotOnly: !!args.snapshotOnly }, (progress) => { - progressCallback({ percent: 30, message: progress.message }); - }), + await progressCallback({ percent: 10, message: 'Backing up' }); + await backuptask.backupApp(app, { snapshotOnly: !!args.snapshotOnly }, (progress) => { + progressCallback({ percent: 30, message: progress.message }); + }), - progressCallback.bind(null, { percent: 100, message: 'Done' }), - updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null }) - ], async function seriesDone(error) { - if (error) { - debugApp(app, 'error backing up app:', error); - // return to installed state intentionally. the error is stashed only in the task and not the app (the UI shows error state otherwise) - await safe(updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null })); - } - callback(error); - }); + await progressCallback({ percent: 100, message: 'Done' }); + await updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null }); } -function create(app, args, progressCallback, callback) { +async function create(app, args, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - async.series([ - progressCallback.bind(null, { percent: 10, message: 'Cleaning up old install' }), - deleteContainers.bind(null, app, { managedOnly: true }), + await progressCallback({ percent: 10, message: 'Cleaning up old install' }); + await deleteContainers(app, { managedOnly: true }); - // FIXME: re-setup addons only because sendmail addon to re-inject env vars on mailboxName change - progressCallback.bind(null, { percent: 30, message: 'Setting up addons' }), - services.setupAddons.bind(null, app, app.manifest.addons), + // FIXME: re-setup addons only because sendmail addon to re-inject env vars on mailboxName change + await progressCallback({ percent: 30, message: 'Setting up addons' }); + await services.setupAddons(app, app.manifest.addons); - progressCallback.bind(null, { percent: 60, message: 'Creating container' }), - createContainer.bind(null, app), + await progressCallback({ percent: 60, message: 'Creating container' }); + await createContainer(app); - startApp.bind(null, app), + await startApp(app); - progressCallback.bind(null, { percent: 100, message: 'Done' }), - updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }) - ], async function seriesDone(error) { - if (error) { - debugApp(app, 'error creating :', error); - await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) })); - } - callback(error); - }); + await progressCallback({ percent: 100, message: 'Done' }); + await updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }); } -function changeLocation(app, args, progressCallback, callback) { +async function changeLocation(app, args, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); const oldConfig = args.oldConfig; const locationChanged = oldConfig.fqdn !== app.fqdn; const skipDnsSetup = args.skipDnsSetup; const overwriteDns = args.overwriteDns; - async.series([ - progressCallback.bind(null, { percent: 10, message: 'Cleaning up old install' }), - reverseProxy.unconfigureApp.bind(null, app), - deleteContainers.bind(null, app, { managedOnly: true }), - async function () { - let obsoleteDomains = oldConfig.alternateDomains.filter(function (o) { - return !app.alternateDomains.some(function (n) { return n.subdomain === o.subdomain && n.domain === o.domain; }); - }); + await progressCallback({ percent: 10, message: 'Cleaning up old install' }); + await reverseProxy.unconfigureApp(app); + await deleteContainers(app, { managedOnly: true }); - if (oldConfig.aliasDomains) { - obsoleteDomains = obsoleteDomains.concat(oldConfig.aliasDomains.filter(function (o) { - return !app.aliasDomains.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; - - await dns.unregisterLocations(obsoleteDomains, progressCallback); - }, - - function setupDnsIfNeeded(done) { - if (skipDnsSetup) return done(); - - async.series([ - progressCallback.bind(null, { percent: 30, message: 'Registering subdomains' }), - dns.registerLocations.bind(null, [ { subdomain: app.location, domain: app.domain }].concat(app.alternateDomains).concat(app.aliasDomains), { overwriteDns }, progressCallback) - ], done); - }, - - // re-setup addons since they rely on the app's fqdn (e.g oauth) - progressCallback.bind(null, { percent: 50, message: 'Setting up addons' }), - services.setupAddons.bind(null, app, app.manifest.addons), - - progressCallback.bind(null, { percent: 60, message: 'Creating container' }), - createContainer.bind(null, app), - - startApp.bind(null, app), - - function waitForDns(done) { - if (skipDnsSetup) return done(); - - async.series([ - progressCallback.bind(null, { percent: 80, message: 'Waiting for DNS propagation' }), - exports._waitForDnsPropagation.bind(null, app), - ], done); - }, - - progressCallback.bind(null, { percent: 90, message: 'Configuring reverse proxy' }), - reverseProxy.configureApp.bind(null, app, auditSource.APPTASK), - - progressCallback.bind(null, { percent: 100, message: 'Done' }), - updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }) - ], async function seriesDone(error) { - if (error) { - debugApp(app, 'error changing location:', error); - await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) })); - } - callback(error); + // unregister old domains + let obsoleteDomains = oldConfig.alternateDomains.filter(function (o) { + return !app.alternateDomains.some(function (n) { return n.subdomain === o.subdomain && n.domain === o.domain; }); }); + + if (oldConfig.aliasDomains) { + obsoleteDomains = obsoleteDomains.concat(oldConfig.aliasDomains.filter(function (o) { + return !app.aliasDomains.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) await dns.unregisterLocations(obsoleteDomains, progressCallback); + + // setup dns + if (!skipDnsSetup) { + await progressCallback({ percent: 30, message: 'Registering subdomains' }); + await dns.registerLocations([ { subdomain: app.location, domain: app.domain }].concat(app.alternateDomains).concat(app.aliasDomains), { overwriteDns }, progressCallback); + } + + // re-setup addons since they rely on the app's fqdn (e.g oauth) + await progressCallback({ percent: 50, message: 'Setting up addons' }); + await services.setupAddons(app, app.manifest.addons); + + await progressCallback({ percent: 60, message: 'Creating container' }); + await createContainer(app); + + await startApp(app); + + if (!skipDnsSetup) { + await progressCallback({ percent: 80, message: 'Waiting for DNS propagation' }); + await exports._waitForDnsPropagation(app); + } + + await progressCallback({ percent: 90, message: 'Configuring reverse proxy' }); + await reverseProxy.configureApp(app, auditSource.APPTASK); + + await progressCallback({ percent: 100, message: 'Done' }); + await updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }); } -function migrateDataDir(app, args, progressCallback, callback) { +async function migrateDataDir(app, args, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - let newDataDir = args.newDataDir; + const newDataDir = args.newDataDir; assert(newDataDir === null || typeof newDataDir === 'string'); - async.series([ - progressCallback.bind(null, { percent: 10, message: 'Cleaning up old install' }), - deleteContainers.bind(null, app, { managedOnly: true }), + await progressCallback({ percent: 10, message: 'Cleaning up old install' }); + await deleteContainers(app, { managedOnly: true }); - progressCallback.bind(null, { percent: 45, message: 'Ensuring app data directory' }), - createAppDir.bind(null, app), + await progressCallback({ percent: 45, message: 'Ensuring app data directory' }); + await createAppDir(app); - // re-setup addons since this creates the localStorage volume - progressCallback.bind(null, { percent: 50, message: 'Setting up addons' }), - services.setupAddons.bind(null, _.extend({}, app, { dataDir: newDataDir }), app.manifest.addons), + // re-setup addons since this creates the localStorage volume + await progressCallback({ percent: 50, message: 'Setting up addons' }); + await services.setupAddons(_.extend({}, app, { dataDir: newDataDir }), app.manifest.addons); - progressCallback.bind(null, { percent: 60, message: 'Moving data dir' }), - moveDataDir.bind(null, app, newDataDir), + await progressCallback({ percent: 60, message: 'Moving data dir' }); + await moveDataDir(app, newDataDir); - progressCallback.bind(null, { percent: 90, message: 'Creating container' }), - createContainer.bind(null, app), + await progressCallback({ percent: 90, message: 'Creating container' }); + await createContainer(app); - startApp.bind(null, app), + await startApp(app); - progressCallback.bind(null, { percent: 100, message: 'Done' }), - updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null, dataDir: newDataDir }) - ], async function seriesDone(error) { - if (error) { - debugApp(app, 'error migrating data dir:', error); - await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) })); - } - - callback(error); - }); + await progressCallback({ percent: 100, message: 'Done' }); + await updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null, dataDir: newDataDir }); } // configure is called for an infra update and repair to re-create container, reverseproxy config. it's all "local" -function configure(app, args, progressCallback, callback) { +async function configure(app, args, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - async.series([ - progressCallback.bind(null, { percent: 10, message: 'Cleaning up old install' }), - reverseProxy.unconfigureApp.bind(null, app), - deleteContainers.bind(null, app, { managedOnly: true }), + await progressCallback({ percent: 10, message: 'Cleaning up old install' }); + await reverseProxy.unconfigureApp(app); + await deleteContainers({ managedOnly: true }); - progressCallback.bind(null, { percent: 20, message: 'Downloading icon' }), - downloadIcon.bind(null, app), + await progressCallback({ percent: 20, message: 'Downloading icon' }); + await downloadIcon(app); - progressCallback.bind(null, { percent: 40, message: 'Downloading image' }), - downloadImage.bind(null, app.manifest), + await progressCallback({ percent: 40, message: 'Downloading image' }); + await downloadImage(app.manifest); - progressCallback.bind(null, { percent: 45, message: 'Ensuring app data directory' }), - createAppDir.bind(null, app), + await progressCallback({ percent: 45, message: 'Ensuring app data directory' }); + await createAppDir(app); - // re-setup addons since they rely on the app's fqdn (e.g oauth) - progressCallback.bind(null, { percent: 50, message: 'Setting up addons' }), - services.setupAddons.bind(null, app, app.manifest.addons), + // re-setup addons since they rely on the app's fqdn (e.g oauth) + await progressCallback({ percent: 50, message: 'Setting up addons' }); + await services.setupAddons(app, app.manifest.addons); - progressCallback.bind(null, { percent: 60, message: 'Creating container' }), - createContainer.bind(null, app), + await progressCallback({ percent: 60, message: 'Creating container' }); + await createContainer(app); - startApp.bind(null, app), + await startApp(app); - progressCallback.bind(null, { percent: 90, message: 'Configuring reverse proxy' }), - reverseProxy.configureApp.bind(null, app, auditSource.APPTASK), + await progressCallback({ percent: 90, message: 'Configuring reverse proxy' }); + await reverseProxy.configureApp(app, auditSource.APPTASK); - progressCallback.bind(null, { percent: 100, message: 'Done' }), - updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }) - ], async function seriesDone(error) { - if (error) { - debugApp(app, 'error reconfiguring:', error); - await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) })); - } - - callback(error); - }); + await progressCallback({ percent: 100, message: 'Done' }); + await updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }); } -function update(app, args, progressCallback, callback) { +async function update(app, args, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); const updateConfig = args.updateConfig; debugApp(app, `Updating to ${updateConfig.manifest.version}`); @@ -668,264 +557,229 @@ function update(app, args, progressCallback, callback) { const httpPortChanged = app.manifest.httpPort !== updateConfig.manifest.httpPort; const proxyAuthChanged = !_.isEqual(safe.query(app.manifest, 'addons.proxyAuth'), safe.query(updateConfig.manifest, 'addons.proxyAuth')); - async.series([ - // this protects against the theoretical possibility of an app being marked for update from - // a previous version of box code - progressCallback.bind(null, { percent: 5, message: 'Verify manifest' }), - verifyManifest.bind(null, updateConfig.manifest), + // this protects against the theoretical possibility of an app being marked for update from + // a previous version of box code + await progressCallback({ percent: 5, message: 'Verify manifest' }); + await verifyManifest(updateConfig.manifest); - function (next) { - if (updateConfig.skipBackup) return next(null); - - async.series([ - progressCallback.bind(null, { percent: 15, message: 'Backing up app' }), - // preserve update backups for 3 weeks - backuptask.backupApp.bind(null, app, { preserveSecs: 3*7*24*60*60 }, (progress) => { - progressCallback({ percent: 15, message: `Backup - ${progress.message}` }); - }) - ], function (error) { - if (error) error.backupError = true; - next(error); - }); - }, - - // 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 - progressCallback.bind(null, { percent: 25, message: 'Downloading image' }), - downloadImage.bind(null, 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 - progressCallback.bind(null, { percent: 35, message: 'Cleaning up old install' }), - deleteContainers.bind(null, app, { managedOnly: true }), - async function deleteImageIfChanged() { - if (app.manifest.dockerImage === updateConfig.manifest.dockerImage) return; - - await docker.deleteImage(app.manifest); - }, - - // only delete unused addons after backup - services.teardownAddons.bind(null, app, unusedAddons), - - // free unused ports - function (next) { - const currentPorts = app.portBindings || {}; - const newTcpPorts = updateConfig.manifest.tcpPorts || {}; - const newUdpPorts = updateConfig.manifest.udpPorts || {}; - - async.each(Object.keys(currentPorts), function (portName, callback) { - if (newTcpPorts[portName] || newUdpPorts[portName]) return callback(null); // port still in use - - apps.delPortBinding(currentPorts[portName], apps.PORT_TYPE_TCP, function (error) { - if (error && error.reason === BoxError.NOT_FOUND) debugApp(app, 'update: portbinding does not exist in database', error); - else if (error) return next(error); - - // also delete from app object for further processing (the db is updated in the next step) - delete app.portBindings[portName]; - - callback(null); - }); - }, next); - }, - - updateApp.bind(null, app, _.pick(updateConfig, 'manifest', 'appStoreId', 'memoryLimit')), // switch over to the new config - - progressCallback.bind(null, { percent: 45, message: 'Downloading icon' }), - downloadIcon.bind(null, app), - - progressCallback.bind(null, { percent: 60, message: 'Updating addons' }), - services.setupAddons.bind(null, app, updateConfig.manifest.addons), - - progressCallback.bind(null, { percent: 70, message: 'Creating container' }), - createContainer.bind(null, app), - - startApp.bind(null, app), - - progressCallback.bind(null, { percent: 90, message: 'Configuring reverse proxy' }), - async function () { - if (!httpPathsChanged && !proxyAuthChanged && !httpPortChanged) return; - - await reverseProxy.configureApp(app, auditSource.APPTASK); - }, - - progressCallback.bind(null, { percent: 100, message: 'Done' }), - updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null, updateTime: new Date() }) - ], async function seriesDone(error) { - if (error && error.backupError) { - debugApp(app, 'update aborted because backup failed', error); - await safe(updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null })); - } else if (error) { - debugApp(app, 'Error updating app:', error); - await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) })); + if (!updateConfig.skipBackup) { + await progressCallback({ percent: 15, message: 'Backing up app' }); + // preserve update backups for 3 weeks + const [error] = await safe(backuptask.backupApp(app, { preserveSecs: 3*7*24*60*60 }, (progress) => { + progressCallback({ percent: 15, message: `Backup - ${progress.message}` }); + })); + if (error) { + error.backupError = true; + throw error; } + } - callback(error); - }); + // 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 + await progressCallback({ percent: 25, message: 'Downloading image' }); + await downloadImage(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 + await progressCallback({ percent: 35, message: 'Cleaning up old install' }); + await deleteContainers(app, { managedOnly: true }); + if (app.manifest.dockerImage !== updateConfig.manifest.dockerImage) await docker.deleteImage(app.manifest); + + // only delete unused addons after backup + await services.teardownAddons(app, unusedAddons); + + // free unused ports + const currentPorts = app.portBindings || {}; + const newTcpPorts = updateConfig.manifest.tcpPorts || {}; + const newUdpPorts = updateConfig.manifest.udpPorts || {}; + + for (const portName of Object.keys(currentPorts)) { + if (newTcpPorts[portName] || newUdpPorts[portName]) continue; // port still in use + + const [error] = await safe(apps.delPortBinding(currentPorts[portName], apps.PORT_TYPE_TCP)); + if (error && error.reason === BoxError.NOT_FOUND) debugApp(app, 'update: portbinding does not exist in database', error); + else if (error) throw error; + + // also delete from app object for further processing (the db is updated in the next step) + delete app.portBindings[portName]; + } + + await updateApp(app, _.pick(updateConfig, 'manifest', 'appStoreId', 'memoryLimit')), // switch over to the new config + + await progressCallback({ percent: 45, message: 'Downloading icon' }); + await downloadIcon(app); + + await progressCallback({ percent: 60, message: 'Updating addons' }); + await services.setupAddons(app, updateConfig.manifest.addons); + + await progressCallback({ percent: 70, message: 'Creating container' }); + await createContainer(app); + + await startApp(app); + + await progressCallback({ percent: 90, message: 'Configuring reverse proxy' }); + if (httpPathsChanged || proxyAuthChanged || httpPortChanged) { + await reverseProxy.configureApp(app, auditSource.APPTASK); + } + + await progressCallback({ percent: 100, message: 'Done' }); + await updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null, updateTime: new Date() }); } -function start(app, args, progressCallback, callback) { +async function start(app, args, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - async.series([ - progressCallback.bind(null, { percent: 10, message: 'Starting app services' }), - services.startAppServices.bind(null, app), + await progressCallback({ percent: 10, message: 'Starting app services' }); + await services.startAppServices(app); - progressCallback.bind(null, { percent: 35, message: 'Starting container' }), - docker.startContainer.bind(null, app.id), + await progressCallback({ percent: 35, message: 'Starting container' }); + await docker.startContainer(app.id); - progressCallback.bind(null, { percent: 60, message: 'Adding collectd profile' }), - addCollectdProfile.bind(null, app), + await progressCallback({ percent: 60, message: 'Adding collectd profile' }); + await addCollectdProfile(app); - // stopped apps do not renew certs. currently, we don't do DNS to not overwrite existing user settings - progressCallback.bind(null, { percent: 80, message: 'Configuring reverse proxy' }), - reverseProxy.configureApp.bind(null, app, auditSource.APPTASK), + // stopped apps do not renew certs. currently, we don't do DNS to not overwrite existing user settings + await progressCallback({ percent: 80, message: 'Configuring reverse proxy' }); + await reverseProxy.configureApp(app, auditSource.APPTASK); - progressCallback.bind(null, { percent: 100, message: 'Done' }), - updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }) - ], async function seriesDone(error) { - if (error) { - debugApp(app, 'error starting app:', error); - await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) })); - } - callback(error); - }); + await progressCallback({ percent: 100, message: 'Done' }); + await updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }); } -function stop(app, args, progressCallback, callback) { +async function stop(app, args, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - async.series([ - progressCallback.bind(null, { percent: 20, message: 'Stopping container' }), - docker.stopContainers.bind(null, app.id), + await progressCallback({ percent: 20, message: 'Stopping container' }); + await docker.stopContainers(app.id); - progressCallback.bind(null, { percent: 50, message: 'Stopping app services' }), - services.stopAppServices.bind(null, app), + await progressCallback({ percent: 50, message: 'Stopping app services' }); + await services.stopAppServices(app); - progressCallback.bind(null, { percent: 80, message: 'Removing collectd profile' }), - removeCollectdProfile.bind(null, app), + await progressCallback({ percent: 80, message: 'Removing collectd profile' }); + await removeCollectdProfile(app); - progressCallback.bind(null, { percent: 100, message: 'Done' }), - updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }) - ], async function seriesDone(error) { - if (error) { - debugApp(app, 'error starting app:', error); - await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) })); - } - callback(error); - }); + await progressCallback({ percent: 100, message: 'Done' }); + await updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }); } -function restart(app, args, progressCallback, callback) { +async function restart(app, args, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - async.series([ - progressCallback.bind(null, { percent: 20, message: 'Restarting container' }), - docker.restartContainer.bind(null, app.id), + await progressCallback({ percent: 20, message: 'Restarting container' }); + await docker.restartContainer(app.id); - progressCallback.bind(null, { percent: 100, message: 'Done' }), - updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }) - ], async function seriesDone(error) { - if (error) { - debugApp(app, 'error starting app:', error); - await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) })); - } - callback(error); - }); + await progressCallback({ percent: 100, message: 'Done' }); + await updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }); } -function uninstall(app, args, progressCallback, callback) { +async function uninstall(app, args, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - async.series([ - progressCallback.bind(null, { percent: 20, message: 'Deleting container' }), - reverseProxy.unconfigureApp.bind(null, app), - deleteContainers.bind(null, app, {}), + await progressCallback({ percent: 20, message: 'Deleting container' }); + await reverseProxy.unconfigureApp(app); + await deleteContainers(app, {}); - progressCallback.bind(null, { percent: 30, message: 'Teardown addons' }), - services.teardownAddons.bind(null, app, app.manifest.addons), + await progressCallback({ percent: 30, message: 'Teardown addons' }); + await services.teardownAddons(app, app.manifest.addons); - progressCallback.bind(null, { percent: 40, message: 'Cleanup file manager' }), + await progressCallback({ percent: 40, message: 'Cleanup file manager' }); - progressCallback.bind(null, { percent: 50, message: 'Deleting app data directory' }), - deleteAppDir.bind(null, app, { removeDirectory: true }), + await progressCallback({ percent: 50, message: 'Deleting app data directory' }); + await deleteAppDir(app, { removeDirectory: true }); - progressCallback.bind(null, { percent: 60, message: 'Deleting image' }), - docker.deleteImage.bind(null, app.manifest), + await progressCallback({ percent: 60, message: 'Deleting image' }); + await docker.deleteImage(app.manifest); - progressCallback.bind(null, { percent: 70, message: 'Unregistering domains' }), - dns.unregisterLocations.bind(null, [ { subdomain: app.location, domain: app.domain } ].concat(app.alternateDomains).concat(app.aliasDomains), progressCallback), + await progressCallback({ percent: 70, message: 'Unregistering domains' }); + await dns.unregisterLocations([ { subdomain: app.location, domain: app.domain } ].concat(app.alternateDomains).concat(app.aliasDomains), progressCallback); - progressCallback.bind(null, { percent: 90, message: 'Cleanup logs' }), - cleanupLogs.bind(null, app), + await progressCallback({ percent: 90, message: 'Cleanup logs' }); + await cleanupLogs(app); - progressCallback.bind(null, { percent: 95, message: 'Remove app from database' }), - apps.del.bind(null, app.id) - ], async function seriesDone(error) { - if (error) { - debugApp(app, 'error uninstalling app:', error); - await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) })); - } - callback(error); - }); + await progressCallback({ percent: 95, message: 'Remove app from database' }); + await apps.del(app.id); } -function run(appId, args, progressCallback, callback) { +async function run(appId, args, progressCallback) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - // determine what to do - util.callbackify(apps.get)(appId, function (error, app) { - if (error) return callback(error); + const app = await apps.get(appId); - debugApp(app, `startTask installationState: ${app.installationState} runState: ${app.runState}`); + debugApp(app, `startTask installationState: ${app.installationState} runState: ${app.runState}`); - switch (app.installationState) { - case apps.ISTATE_PENDING_INSTALL: - case apps.ISTATE_PENDING_CLONE: - case apps.ISTATE_PENDING_RESTORE: - case apps.ISTATE_PENDING_IMPORT: - return install(app, args, progressCallback, callback); - case apps.ISTATE_PENDING_CONFIGURE: - return configure(app, args, progressCallback, callback); - case apps.ISTATE_PENDING_RECREATE_CONTAINER: - case apps.ISTATE_PENDING_RESIZE: - case apps.ISTATE_PENDING_DEBUG: - return create(app, args, progressCallback, callback); - case apps.ISTATE_PENDING_LOCATION_CHANGE: - return changeLocation(app, args, progressCallback, callback); - case apps.ISTATE_PENDING_DATA_DIR_MIGRATION: - return migrateDataDir(app, args, progressCallback, callback); - case apps.ISTATE_PENDING_UNINSTALL: - return uninstall(app, args, progressCallback, callback); - case apps.ISTATE_PENDING_UPDATE: - return update(app, args, progressCallback, callback); - case apps.ISTATE_PENDING_BACKUP: - return backup(app, args, progressCallback, callback); - case apps.ISTATE_PENDING_START: - return start(app, args, progressCallback, callback); - case apps.ISTATE_PENDING_STOP: - return stop(app, args, progressCallback, callback); - case apps.ISTATE_PENDING_RESTART: - return restart(app, args, progressCallback, callback); - case apps.ISTATE_INSTALLED: // can only happen when we have a bug in our code while testing/development - return updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }, callback); - default: - debugApp(app, 'apptask launched with invalid command'); - return callback(new BoxError(BoxError.INTERNAL_ERROR, 'Unknown install command in apptask:' + app.installationState)); + let cmd; + + switch (app.installationState) { + case apps.ISTATE_PENDING_INSTALL: + case apps.ISTATE_PENDING_CLONE: + case apps.ISTATE_PENDING_RESTORE: + case apps.ISTATE_PENDING_IMPORT: + cmd = install(app, args, progressCallback); + break; + case apps.ISTATE_PENDING_CONFIGURE: + cmd = configure(app, args, progressCallback); + break; + case apps.ISTATE_PENDING_RECREATE_CONTAINER: + case apps.ISTATE_PENDING_RESIZE: + case apps.ISTATE_PENDING_DEBUG: + cmd = create(app, args, progressCallback); + break; + case apps.ISTATE_PENDING_LOCATION_CHANGE: + cmd = changeLocation(app, args, progressCallback); + break; + case apps.ISTATE_PENDING_DATA_DIR_MIGRATION: + cmd = migrateDataDir(app, args, progressCallback); + break; + case apps.ISTATE_PENDING_UNINSTALL: + cmd = uninstall(app, args, progressCallback); + break; + case apps.ISTATE_PENDING_UPDATE: + cmd = update(app, args, progressCallback); + break; + case apps.ISTATE_PENDING_BACKUP: + cmd = backup(app, args, progressCallback); + break; + case apps.ISTATE_PENDING_START: + cmd = start(app, args, progressCallback); + break; + case apps.ISTATE_PENDING_STOP: + cmd = stop(app, args, progressCallback); + break; + case apps.ISTATE_PENDING_RESTART: + cmd = restart(app, args, progressCallback); + break; + case apps.ISTATE_INSTALLED: // can only happen when we have a bug in our code while testing/development + cmd = updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }); + break; + default: + debugApp(app, 'apptask launched with invalid command'); + throw new BoxError(BoxError.INTERNAL_ERROR, 'Unknown install command in apptask:' + app.installationState); + } + + const [error] = await safe(cmd); + if (error) { + debugApp(app, `app error for state ${app.installationState}:`, error); + + if (app.installationState === apps.ISTATE_PENDING_BACKUP) { + // return to installed state intentionally. the error is stashed only in the task and not the app (the UI shows error state otherwise) + await safe(updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null }), { debug }); + } else if (app.installationState === apps.ISTATE_PENDING_UPDATE && error.backupError) { + debugApp(app, 'update aborted because backup failed'); + await safe(updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }, { debug })); + } else { + await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) }), { debug }); } - }); + + throw error; + } } diff --git a/src/backups.js b/src/backups.js index f37c2c8f1..c39519a7b 100644 --- a/src/backups.js +++ b/src/backups.js @@ -237,19 +237,16 @@ function getSnapshotInfo(id) { return info[id] || { }; } -function setSnapshotInfo(id, info, callback) { +async function setSnapshotInfo(id, info) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof info, 'object'); - assert.strictEqual(typeof callback, 'function'); const contents = safe.fs.readFileSync(paths.SNAPSHOT_INFO_FILE, 'utf8'); const data = safe.JSON.parse(contents) || { }; if (info) data[id] = info; else delete data[id]; if (!safe.fs.writeFileSync(paths.SNAPSHOT_INFO_FILE, JSON.stringify(data, null, 4), 'utf8')) { - return callback(new BoxError(BoxError.FS_ERROR, safe.error.message)); + throw new BoxError(BoxError.FS_ERROR, safe.error.message); } - - callback(); } async function startCleanupTask(auditSource) { diff --git a/src/backuptask.js b/src/backuptask.js index 4c64d4b8d..ca83e9cfb 100644 --- a/src/backuptask.js +++ b/src/backuptask.js @@ -645,54 +645,41 @@ function download(backupConfig, backupId, format, dataLayout, progressCallback, } } -function restore(backupConfig, backupId, progressCallback, callback) { +async function restore(backupConfig, backupId, progressCallback) { assert.strictEqual(typeof backupConfig, 'object'); assert.strictEqual(typeof backupId, 'string'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); const boxDataDir = safe.fs.realpathSync(paths.BOX_DATA_DIR); - if (!boxDataDir) return callback(new BoxError(BoxError.FS_ERROR, `Error resolving boxdata: ${safe.error.message}`)); + if (!boxDataDir) throw new BoxError(BoxError.FS_ERROR, `Error resolving boxdata: ${safe.error.message}`); const dataLayout = new DataLayout(boxDataDir, []); - download(backupConfig, backupId, backupConfig.format, dataLayout, progressCallback, function (error) { - if (error) return callback(error); + await util.promisify(download)(backupConfig, backupId, backupConfig.format, dataLayout, progressCallback); - debug('restore: download completed, importing database'); + debug('restore: download completed, importing database'); - database.importFromFile(`${dataLayout.localRoot()}/box.mysqldump`, async function (error) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); + await database.importFromFile(`${dataLayout.localRoot()}/box.mysqldump`); - debug('restore: database imported'); + debug('restore: database imported'); - [error] = await safe(settings.initCache()); - callback(error); - }); - }); + await settings.initCache(); } -function downloadApp(app, restoreConfig, progressCallback, callback) { +async function downloadApp(app, restoreConfig, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof restoreConfig, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); const appDataDir = safe.fs.realpathSync(path.join(paths.APPS_DATA_DIR, app.id)); - if (!appDataDir) return callback(new BoxError(BoxError.FS_ERROR, safe.error.message)); + if (!appDataDir) throw new BoxError(BoxError.FS_ERROR, safe.error.message); const dataLayout = new DataLayout(appDataDir, app.dataDir ? [{ localDir: app.dataDir, remoteDir: 'data' }] : []); const startTime = new Date(); - const getBackupConfigFunc = restoreConfig.backupConfig ? (next) => next(null, restoreConfig.backupConfig) : getBackupConfig; + const backupConfig = restoreConfig.backupConfig || await settings.getBackupConfig(); - getBackupConfigFunc(function (error, backupConfig) { - if (error) return callback(error); - - download(backupConfig, restoreConfig.backupId, restoreConfig.backupFormat, dataLayout, progressCallback, function (error) { - debug('downloadApp: time: %s', (new Date() - startTime)/1000); - - callback(error); - }); - }); + const downloadAsync = util.promisify(download); + await downloadAsync(backupConfig, restoreConfig.backupId, restoreConfig.backupFormat, dataLayout, progressCallback); + debug('downloadApp: time: %s', (new Date() - startTime)/1000); } function runBackupUpload(uploadConfig, progressCallback, callback) { @@ -731,53 +718,42 @@ function runBackupUpload(uploadConfig, progressCallback, callback) { }); } -function snapshotBox(progressCallback, callback) { +async function snapshotBox(progressCallback) { assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); progressCallback({ message: 'Snapshotting box' }); const startTime = new Date(); - database.exportToFile(`${paths.BOX_DATA_DIR}/box.mysqldump`, function (error) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - debug(`snapshotBox: took ${(new Date() - startTime)/1000} seconds`); - - return callback(); - }); + await database.exportToFile(`${paths.BOX_DATA_DIR}/box.mysqldump`); + debug(`snapshotBox: took ${(new Date() - startTime)/1000} seconds`); } -function uploadBoxSnapshot(backupConfig, progressCallback, callback) { +async function uploadBoxSnapshot(backupConfig, progressCallback) { assert.strictEqual(typeof backupConfig, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - snapshotBox(progressCallback, function (error) { - if (error) return callback(error); + await snapshotBox(progressCallback); - const boxDataDir = safe.fs.realpathSync(paths.BOX_DATA_DIR); - if (!boxDataDir) return callback(new BoxError(BoxError.FS_ERROR, `Error resolving boxdata: ${safe.error.message}`)); + const boxDataDir = safe.fs.realpathSync(paths.BOX_DATA_DIR); + if (!boxDataDir) throw new BoxError(BoxError.FS_ERROR, `Error resolving boxdata: ${safe.error.message}`); - const uploadConfig = { - backupId: 'snapshot/box', - backupConfig, - dataLayout: new DataLayout(boxDataDir, []), - progressTag: 'box' - }; + const uploadConfig = { + backupId: 'snapshot/box', + backupConfig, + dataLayout: new DataLayout(boxDataDir, []), + progressTag: 'box' + }; - progressCallback({ message: 'Uploading box snapshot' }); + progressCallback({ message: 'Uploading box snapshot' }); - const startTime = new Date(); + const startTime = new Date(); - runBackupUpload(uploadConfig, progressCallback, function (error) { - if (error) return callback(error); + await util.promisify(runBackupUpload)(uploadConfig, progressCallback); - debug(`uploadBoxSnapshot: took ${(new Date() - startTime)/1000} seconds`); + debug(`uploadBoxSnapshot: took ${(new Date() - startTime)/1000} seconds`); - backups.setSnapshotInfo('box', { timestamp: new Date().toISOString(), format: backupConfig.format }, callback); - }); - }); + await backups.setSnapshotInfo('box', { timestamp: new Date().toISOString(), format: backupConfig.format }); } async function rotateBoxBackup(backupConfig, tag, options, appBackupIds, progressCallback) { @@ -822,23 +798,18 @@ async function rotateBoxBackup(backupConfig, tag, options, appBackupIds, progres }); } -function backupBoxWithAppBackupIds(appBackupIds, tag, options, progressCallback, callback) { +async function backupBoxWithAppBackupIds(appBackupIds, tag, options, progressCallback) { assert(Array.isArray(appBackupIds)); assert.strictEqual(typeof tag, 'string'); assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - getBackupConfig(function (error, backupConfig) { - if (error) return callback(error); + const backupConfig = await settings.getBackupConfig(); - uploadBoxSnapshot(backupConfig, progressCallback, async function (error) { - if (error) return callback(error); + await uploadBoxSnapshot(backupConfig, progressCallback); - const [rotateError, backupId] = await safe(rotateBoxBackup(backupConfig, tag, options, appBackupIds, progressCallback)); - callback(rotateError, backupId); - }); - }); + const backupId = await rotateBoxBackup(backupConfig, tag, options, appBackupIds, progressCallback); + return backupId; } async function rotateAppBackup(backupConfig, app, tag, options, progressCallback) { @@ -888,150 +859,115 @@ async function rotateAppBackup(backupConfig, app, tag, options, progressCallback }); } -function backupApp(app, options, progressCallback, callback) { +async function backupApp(app, options, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - if (options.snapshotOnly) return snapshotApp(app, progressCallback, callback); + if (options.snapshotOnly) return await snapshotApp(app, progressCallback); const tag = (new Date()).toISOString().replace(/[T.]/g, '-').replace(/[:Z]/g,''); debug(`backupApp - Backing up ${app.fqdn} with tag ${tag}`); - backupAppWithTag(app, tag, options, progressCallback, callback); + await backupAppWithTag(app, tag, options, progressCallback); } - -function snapshotApp(app, progressCallback, callback) { +async function snapshotApp(app, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); const startTime = new Date(); progressCallback({ message: `Snapshotting app ${app.fqdn}` }); - const appsBackupConfig = util.callbackify(apps.backupConfig); + await apps.backupConfig(app); + await services.backupAddons(app, app.manifest.addons); - appsBackupConfig(app, async function (error) { - if (error) return callback(error); - - [error] = await safe(services.backupAddons(app, app.manifest.addons)); - if (error) return callback(error); - - debugApp(app, `snapshotApp: took ${(new Date() - startTime)/1000} seconds`); - - return callback(null); - }); + debugApp(app, `snapshotApp: took ${(new Date() - startTime)/1000} seconds`); } -function uploadAppSnapshot(backupConfig, app, progressCallback, callback) { +async function uploadAppSnapshot(backupConfig, app, progressCallback) { assert.strictEqual(typeof backupConfig, 'object'); assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); - snapshotApp(app, progressCallback, function (error) { - if (error) return callback(error); + await snapshotApp(app, progressCallback); - const backupId = util.format('snapshot/app_%s', app.id); - const appDataDir = safe.fs.realpathSync(path.join(paths.APPS_DATA_DIR, app.id)); - if (!appDataDir) return callback(new BoxError(BoxError.FS_ERROR, `Error resolving appsdata: ${safe.error.message}`)); + const backupId = util.format('snapshot/app_%s', app.id); + const appDataDir = safe.fs.realpathSync(path.join(paths.APPS_DATA_DIR, app.id)); + if (!appDataDir) throw new BoxError(BoxError.FS_ERROR, `Error resolving appsdata: ${safe.error.message}`); - const dataLayout = new DataLayout(appDataDir, app.dataDir ? [{ localDir: app.dataDir, remoteDir: 'data' }] : []); + const dataLayout = new DataLayout(appDataDir, app.dataDir ? [{ localDir: app.dataDir, remoteDir: 'data' }] : []); - progressCallback({ message: `Uploading app snapshot ${app.fqdn}`}); + progressCallback({ message: `Uploading app snapshot ${app.fqdn}`}); - const uploadConfig = { - backupId, - backupConfig, - dataLayout, - progressTag: app.fqdn - }; + const uploadConfig = { + backupId, + backupConfig, + dataLayout, + progressTag: app.fqdn + }; - const startTime = new Date(); + const startTime = new Date(); - runBackupUpload(uploadConfig, progressCallback, function (error) { - if (error) return callback(error); + await util.promisify(runBackupUpload)(uploadConfig, progressCallback); - debugApp(app, `uploadAppSnapshot: ${backupId} done. ${(new Date() - startTime)/1000} seconds`); + debugApp(app, `uploadAppSnapshot: ${backupId} done. ${(new Date() - startTime)/1000} seconds`); - backups.setSnapshotInfo(app.id, { timestamp: new Date().toISOString(), manifest: app.manifest, format: backupConfig.format }, callback); - }); - }); + await backups.setSnapshotInfo(app.id, { timestamp: new Date().toISOString(), manifest: app.manifest, format: backupConfig.format }); } -async function backupAppWithTag(app, tag, options, progressCallback, callback) { +async function backupAppWithTag(app, tag, options, progressCallback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof tag, 'string'); assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); if (!canBackupApp(app)) { // if we cannot backup, reuse it's most recent backup - const [error, results] = await safe(backups.getByIdentifierAndStatePaged(app.id, backups.BACKUP_STATE_NORMAL, 1, 1)); - if (error) return callback(error); - if (results.length === 0) return callback(null, null); // no backup to re-use + const results = await backups.getByIdentifierAndStatePaged(app.id, backups.BACKUP_STATE_NORMAL, 1, 1); + if (results.length === 0) return null; // no backup to re-use - return callback(null, results[0].id); + return results[0].id; } - getBackupConfig(function (error, backupConfig) { - if (error) return callback(error); + const backupConfig = await settings.getBackupConfig(); - uploadAppSnapshot(backupConfig, app, progressCallback, async function (error) { - if (error) return callback(error); - - const [rotateError, backupId] = await safe(rotateAppBackup(backupConfig, app, tag, options, progressCallback)); - callback(rotateError, backupId); - }); - }); + await uploadAppSnapshot(backupConfig, app, progressCallback); + const backupId = await rotateAppBackup(backupConfig, app, tag, options, progressCallback); + return backupId; } // this function expects you to have a lock. Unlike other progressCallback this also has a progress field -function backupBoxAndApps(options, progressCallback, callback) { +async function backupBoxAndApps(options, progressCallback) { assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - assert.strictEqual(typeof callback, 'function'); const tag = (new Date()).toISOString().replace(/[T.]/g, '-').replace(/[:Z]/g,''); - util.callbackify(apps.list)(function (error, allApps) { - if (error) return callback(error); + const allApps = await apps.list(); - let percent = 1; - let step = 100/(allApps.length+2); + let percent = 1; + let step = 100/(allApps.length+2); - async.mapSeries(allApps, function iterator(app, iteratorCallback) { - progressCallback({ percent: percent, message: `Backing up ${app.fqdn}` }); - percent += step; + const appBackupIds = []; + for (const app of allApps) { + progressCallback({ percent: percent, message: `Backing up ${app.fqdn}` }); + percent += step; - if (!app.enableBackup) { - debug(`Skipped backup ${app.fqdn}`); - return iteratorCallback(null, null); // nothing to backup - } + if (!app.enableBackup) { + debug(`Skipped backup ${app.fqdn}`); + return; // nothing to backup + } - const startTime = new Date(); - backupAppWithTag(app, tag, options, (progress) => progressCallback({ percent: percent, message: progress.message }), function (error, backupId) { - if (error) { - debugApp(app, 'Unable to backup', error); - return iteratorCallback(error); - } + const startTime = new Date(); + const backupId = await backupAppWithTag(app, tag, options, (progress) => progressCallback({ percent: percent, message: progress.message })); + debugApp(app, `Backed up. Took ${(new Date() - startTime)/1000} seconds`); + if (backupId) appBackupIds.push(backupId); // backupId can be null if in BAD_STATE and never backed up + } - debugApp(app, `Backed up. Took ${(new Date() - startTime)/1000} seconds`); + progressCallback({ percent: percent, message: 'Backing up system data' }); + percent += step; - iteratorCallback(null, backupId || null); // clear backupId if is in BAD_STATE and never backed up - }); - }, function appsBackedUp(error, backupIds) { - if (error) return callback(error); - - backupIds = backupIds.filter(function (id) { return id !== null; }); // remove apps in bad state that were never backed up - - progressCallback({ percent: percent, message: 'Backing up system data' }); - percent += step; - - backupBoxWithAppBackupIds(backupIds, tag, options, (progress) => progressCallback({ percent: percent, message: progress.message }), callback); - }); - }); + const backupId = await backupBoxWithAppBackupIds(appBackupIds, tag, options, (progress) => progressCallback({ percent: percent, message: progress.message })); + return backupId; } diff --git a/src/database.js b/src/database.js index c988b4499..50c8b50d7 100644 --- a/src/database.js +++ b/src/database.js @@ -19,7 +19,7 @@ const assert = require('assert'), constants = require('./constants.js'), debug = require('debug')('box:database'), mysql = require('mysql'), - once = require('once'), + safe = require('safetydance'), shell = require('./shell.js'), util = require('util'); @@ -131,21 +131,18 @@ async function transaction(queries) { }); } -function importFromFile(file, callback) { +async function importFromFile(file) { assert.strictEqual(typeof file, 'string'); - assert.strictEqual(typeof callback, 'function'); const cmd = `/usr/bin/mysql -h "${gDatabase.hostname}" -u ${gDatabase.username} -p${gDatabase.password} ${gDatabase.name} < ${file}`; - async.series([ - query.bind(null, 'CREATE DATABASE IF NOT EXISTS box'), - child_process.exec.bind(null, cmd) - ], callback); + await query('CREATE DATABASE IF NOT EXISTS box'); + const [error] = await safe(shell.promises.exec('importFromFile', cmd)); + if (error) throw new BoxError(BoxError.DATABASE_ERROR, error); } -function exportToFile(file, callback) { +async function exportToFile(file) { assert.strictEqual(typeof file, 'string'); - assert.strictEqual(typeof callback, 'function'); // latest mysqldump enables column stats by default which is not present in MySQL 5.7 server // this option must not be set in production cloudrons which still use the old mysqldump @@ -153,5 +150,6 @@ function exportToFile(file, callback) { const cmd = `/usr/bin/mysqldump -h "${gDatabase.hostname}" -u root -p${gDatabase.password} ${disableColStats} --single-transaction --routines --triggers ${gDatabase.name} > "${file}"`; - child_process.exec(cmd, callback); + const [error] = await safe(shell.promises.exec('exportToFile', cmd)); + if (error) throw new BoxError(BoxError.DATABASE_ERROR, error); } diff --git a/src/provision.js b/src/provision.js index 7011d10ed..9a1dbb29e 100644 --- a/src/provision.js +++ b/src/provision.js @@ -154,7 +154,7 @@ async function restoreTask(backupConfig, backupId, sysinfoConfig, options, audit try { setProgress('restore', 'Downloading backup'); - await util.promisify(backuptask.restore)(backupConfig, backupId, (progress) => setProgress('restore', progress.message)); + await backuptask.restore(backupConfig, backupId, (progress) => setProgress('restore', progress.message)); await settings.setSysinfoConfig(sysinfoConfig); await reverseProxy.restoreFallbackCertificates(); diff --git a/src/taskworker.js b/src/taskworker.js index 0ab9d9de1..d973e11d2 100755 --- a/src/taskworker.js +++ b/src/taskworker.js @@ -92,7 +92,7 @@ async.series([ if (updateError) return exitSync({ error: updateError, code: 50 }); const progressCallback = async function (progress, callback) { - await safe(tasks.update(taskId, progress)); + await safe(tasks.update(taskId, progress), { debug }); if (callback) callback(); }; diff --git a/src/test/apptask-test.js b/src/test/apptask-test.js index dcdb5c800..85e832ca8 100644 --- a/src/test/apptask-test.js +++ b/src/test/apptask-test.js @@ -10,6 +10,7 @@ const apptask = require('../apptask.js'), expect = require('expect.js'), fs = require('fs'), paths = require('../paths.js'), + safe = require('safetydance'), _ = require('underscore'); describe('apptask', function () { @@ -18,70 +19,52 @@ describe('apptask', function () { before(setup); after(cleanup); - it('create volume', function (done) { - apptask._createAppDir(app, function (error) { - expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + app.id)).to.be(true); - expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + app.id + '/data')).to.be(false); - expect(error).to.be(null); - done(); - }); + it('create volume', async function () { + await apptask._createAppDir(app); + expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + app.id)).to.be(true); + expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + app.id + '/data')).to.be(false); }); - it('delete volume - removeDirectory (false) ', function (done) { - apptask._deleteAppDir(app, { removeDirectory: false }, function (error) { - expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + app.id)).to.be(true); - expect(fs.readdirSync(paths.APPS_DATA_DIR + '/' + app.id).length).to.be(0); // empty - expect(error).to.be(null); - done(); - }); + it('delete volume - removeDirectory (false) ', async function () { + await apptask._deleteAppDir(app, { removeDirectory: false }); + expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + app.id)).to.be(true); + expect(fs.readdirSync(paths.APPS_DATA_DIR + '/' + app.id).length).to.be(0); // empty }); - it('delete volume - removeDirectory (true) ', function (done) { - apptask._deleteAppDir(app, { removeDirectory: true }, function (error) { - expect(!fs.existsSync(paths.APPS_DATA_DIR + '/' + app.id)).to.be(true); - expect(error).to.be(null); - done(); - }); + it('delete volume - removeDirectory (true) ', async function () { + await apptask._deleteAppDir(app, { removeDirectory: true }); + expect(!fs.existsSync(paths.APPS_DATA_DIR + '/' + app.id)).to.be(true); }); - it('barfs on empty manifest', function (done) { - var badApp = _.extend({ }, app); + it('barfs on empty manifest', async function () { + const badApp = _.extend({ }, app); badApp.manifest = { }; - apptask._verifyManifest(badApp.manifest, function (error) { - expect(error).to.be.ok(); - done(); - }); + const [error] = await safe(apptask._verifyManifest(badApp.manifest)); + expect(error).to.be.ok(); }); - it('fails on bad manifest', function (done) { - var badApp = _.extend({ }, app); + it('fails on bad manifest', async function () { + const badApp = _.extend({ }, app); badApp.manifest = _.extend({ }, app.manifest); delete badApp.manifest.httpPort; - apptask._verifyManifest(badApp.manifest, function (error) { - expect(error).to.be.ok(); - done(); - }); + const [error] = await safe(apptask._verifyManifest(badApp.manifest)); + expect(error).to.be.ok(); }); - it('barfs on incompatible manifest', function (done) { - var badApp = _.extend({ }, app); + it('barfs on incompatible manifest', async function () { + const badApp = _.extend({ }, app); badApp.manifest = _.extend({ }, app.manifest); badApp.manifest.maxBoxVersion = '0.0.0'; // max box version is too small - apptask._verifyManifest(badApp.manifest, function (error) { - expect(error).to.be.ok(); - done(); - }); + const [error] = await safe(apptask._verifyManifest(badApp.manifest)); + expect(error).to.be.ok(); }); - it('verifies manifest', function (done) { - var goodApp = _.extend({ }, app); + it('verifies manifest', async function () { + const goodApp = _.extend({ }, app); - apptask._verifyManifest(goodApp.manifest, function (error) { - expect(error).to.be(null); - done(); - }); + await apptask._verifyManifest(goodApp.manifest); }); }); diff --git a/src/test/database-test.js b/src/test/database-test.js index 5dd0de659..6f449e62b 100644 --- a/src/test/database-test.js +++ b/src/test/database-test.js @@ -5,7 +5,9 @@ 'use strict'; const database = require('../database'), - expect = require('expect.js'); + expect = require('expect.js'), + fs = require('fs'), + safe = require('safetydance'); describe('database', function () { describe('init', function () { @@ -28,32 +30,23 @@ describe('database', function () { await database._clear(); }); - it('cannot import from non-existent file', function (done) { - database.importFromFile('/does/not/exist', function (error) { - expect(error).to.be.ok(); - done(); - }); + it('cannot import from non-existent file', async function () { + const [error] = await safe(database.importFromFile('/does/not/exist')); + expect(error).to.be.ok(); }); - it('can export to file', function (done) { + it('can export to file', async function () { // arch only has maria db which lacks some mysqldump options we need, this is only here to allow running the tests :-/ - if (require('child_process').execSync('/usr/bin/mysqldump --version').toString().indexOf('MariaDB') !== -1) return done(); + if (!fs.readFileSync('/etc/lsb-release', 'utf8').includes('Ubuntu')) return; - database.exportToFile('/tmp/box.mysqldump', function (error) { - expect(error).to.be(null); - done(); - }); + await database.exportToFile('/tmp/box.mysqldump'); }); - it('can import from file', function (done) { + it('can import from file', async function () { // arch only has maria db which lacks some mysqldump options we need, this is only here to allow running the tests :-/ - if (require('child_process').execSync('/usr/bin/mysqldump --version').toString().indexOf('MariaDB') !== -1) return done(); + if (!fs.readFileSync('/etc/lsb-release', 'utf8').includes('Ubuntu')) return; - database.importFromFile('/tmp/box.mysqldump', function (error) { - expect(error).to.be(null); - done(); - }); + await database.importFromFile('/tmp/box.mysqldump'); }); - }); }); diff --git a/src/updater.js b/src/updater.js index 70b820b04..bbc7bab99 100644 --- a/src/updater.js +++ b/src/updater.js @@ -148,7 +148,7 @@ async function update(boxUpdateInfo, options, progressCallback) { if (!options.skipBackup) { progressCallback({ percent: 10, message: 'Backing up' }); - await util.promisify(backuptask.backupBoxAndApps)({ preserveSecs: 3*7*24*60*60 }, (progress) => progressCallback({ percent: 10+progress.percent*70/100, message: progress.message })); + await backuptask.backupBoxAndApps({ preserveSecs: 3*7*24*60*60 }, (progress) => progressCallback({ percent: 10+progress.percent*70/100, message: progress.message })); } debug('updating box %s', boxUpdateInfo.sourceTarballUrl);