diff --git a/src/docker.js b/src/docker.js index b2a572a69..969d514fb 100644 --- a/src/docker.js +++ b/src/docker.js @@ -49,7 +49,6 @@ const apps = require('./apps.js'), shell = require('./shell.js'), safe = require('safetydance'), system = require('./system.js'), - util = require('util'), volumes = require('./volumes.js'), _ = require('underscore'); @@ -194,33 +193,30 @@ function downloadImage(manifest, callback) { }); } -function getVolumeMounts(app, callback) { +async function getVolumeMounts(app) { assert.strictEqual(typeof app, 'object'); - assert.strictEqual(typeof callback, 'function'); let mounts = []; - if (app.mounts.length === 0) return callback(null, []); + if (app.mounts.length === 0) return []; - volumes.list(function (error, result) { - if (error) return callback(error); + const result = await volumes.list(); - let volumesById = {}; - result.forEach(r => volumesById[r.id] = r); + let volumesById = {}; + result.forEach(r => volumesById[r.id] = r); - for (const mount of app.mounts) { - const volume = volumesById[mount.volumeId]; + for (const mount of app.mounts) { + const volume = volumesById[mount.volumeId]; - mounts.push({ - Source: volume.hostPath, - Target: `/media/${volume.name}`, - Type: 'bind', - ReadOnly: mount.readOnly - }); - } + mounts.push({ + Source: volume.hostPath, + Target: `/media/${volume.name}`, + Type: 'bind', + ReadOnly: mount.readOnly + }); + } - callback(null, mounts); - }); + return mounts; } function getAddonMounts(app, callback) { @@ -273,18 +269,17 @@ function getAddonMounts(app, callback) { }); } -function getMounts(app, callback) { +async function getMounts(app, callback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof callback, 'function'); - getVolumeMounts(app, function (error, volumeMounts) { + const [error, volumeMounts] = await safe(getVolumeMounts()); + if (error) return callback(error); + + getAddonMounts(app, function (error, addonMounts) { if (error) return callback(error); - getAddonMounts(app, function (error, addonMounts) { - if (error) return callback(error); - - callback(null, volumeMounts.concat(addonMounts)); - }); + callback(null, volumeMounts.concat(addonMounts)); }); } diff --git a/src/routes/test/cloudron-test.js b/src/routes/test/cloudron-test.js index 8eb9e92ce..e8928ac86 100644 --- a/src/routes/test/cloudron-test.js +++ b/src/routes/test/cloudron-test.js @@ -5,7 +5,7 @@ /* global before:false */ /* global after:false */ -let async = require('async'), +const async = require('async'), constants = require('../../constants.js'), database = require('../../database.js'), expect = require('expect.js'), diff --git a/src/routes/volumes.js b/src/routes/volumes.js index bfc88487d..344904b5f 100644 --- a/src/routes/volumes.js +++ b/src/routes/volumes.js @@ -13,54 +13,46 @@ const assert = require('assert'), BoxError = require('../boxerror.js'), volumes = require('../volumes.js'), HttpError = require('connect-lastmile').HttpError, - HttpSuccess = require('connect-lastmile').HttpSuccess; + HttpSuccess = require('connect-lastmile').HttpSuccess, + safe = require('safetydance'); -function load(req, res, next) { +async function load(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); - volumes.get(req.params.id, function (error, result) { - if (error) return next(BoxError.toHttpError(error)); - - req.resource = result; - - next(); - }); + const [error, result] = safe(await volumes.get(req.params.id)); + if (error) return next(BoxError.toHttpError(error)); + if (!result) return next(new HttpError(404, 'Volume not found')); + req.resource = result; + next(); } -function add(req, res, next) { +async function add(req, res, next) { assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.name !== 'string') return next(new HttpError(400, 'name must be a string')); if (typeof req.body.hostPath !== 'string') return next(new HttpError(400, 'hostPath must be a string')); - volumes.add(req.body.name, req.body.hostPath, auditSource.fromRequest(req), function (error, id) { - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(201, { id })); - }); - + const [error, id] = await safe(volumes.add(req.body.name, req.body.hostPath, auditSource.fromRequest(req))); + if (error) return next(BoxError.toHttpError(error)); + next(new HttpSuccess(201, { id })); } -function get(req, res, next) { +async function get(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); next(new HttpSuccess(200, req.resource)); } -function del(req, res, next) { +async function del(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); - volumes.del(req.resource, auditSource.fromRequest(req), function (error) { - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(204)); - }); + const [error] = await safe(volumes.del(req.resource, auditSource.fromRequest(req))); + if (error) return next(BoxError.toHttpError(error)); + next(new HttpSuccess(204)); } -function list(req, res, next) { - volumes.list(function (error, result) { - if (error) return next(new HttpError(500, error)); - - next(new HttpSuccess(200, { volumes: result })); - }); +async function list(req, res, next) { + const [error, result] = await safe(volumes.list()); + if (error) return next(BoxError.toHttpError(error)); + next(new HttpSuccess(200, { volumes: result })); } diff --git a/src/sftp.js b/src/sftp.js index 6ca7a4680..0c7bf0b42 100644 --- a/src/sftp.js +++ b/src/sftp.js @@ -7,7 +7,7 @@ exports = module.exports = { DEFAULT_MEMORY_LIMIT: 256 * 1024 * 1024 }; -var apps = require('./apps.js'), +const apps = require('./apps.js'), assert = require('assert'), async = require('async'), debug = require('debug')('box:sftp'), @@ -34,7 +34,7 @@ function rebuild(serviceConfig, options, callback) { const memory = system.getMemoryAllocation(memoryLimit); const cloudronToken = hat(8 * 128); - apps.getAll(function (error, result) { + apps.getAll(async function (error, result) { if (error) return callback(error); let dataDirs = []; @@ -51,63 +51,62 @@ function rebuild(serviceConfig, options, callback) { dataDirs.push({ hostDir, mountDir }); }); + let allVolumes; + [error, allVolumes] = await safe(volumes.list()); + if (error) return callback(error); - volumes.list(function (error, allVolumes) { - if (error) return callback(error); + allVolumes.forEach(function (volume) { + if (!safe.fs.existsSync(volume.hostPath)) { + debug(`Ignoring volume host path ${volume.hostPath} since it does not exist`); + return; + } - allVolumes.forEach(function (volume) { - if (!safe.fs.existsSync(volume.hostPath)) { - debug(`Ignoring volume host path ${volume.hostPath} since it does not exist`); - return; - } + dataDirs.push({ hostDir: volume.hostPath, mountDir: `/app/data/${volume.id}` }); + }); - dataDirs.push({ hostDir: volume.hostPath, mountDir: `/app/data/${volume.id}` }); - }); + docker.inspect('sftp', function (error, data) { + if (!error && data && data.Mounts) { + let currentDataDirs = data.Mounts; + if (currentDataDirs) { + currentDataDirs = currentDataDirs.filter(function (d) { return d.Destination.indexOf('/app/data/') === 0; }).map(function (d) { return { hostDir: d.Source, mountDir: d.Destination }; }); - docker.inspect('sftp', function (error, data) { - if (!error && data && data.Mounts) { - let currentDataDirs = data.Mounts; - if (currentDataDirs) { - currentDataDirs = currentDataDirs.filter(function (d) { return d.Destination.indexOf('/app/data/') === 0; }).map(function (d) { return { hostDir: d.Source, mountDir: d.Destination }; }); + // sort for comparison + currentDataDirs.sort(function (a, b) { return a.hostDir < b.hostDir ? -1 : 1; }); + dataDirs.sort(function (a, b) { return a.hostDir < b.hostDir ? -1 : 1; }); - // sort for comparison - currentDataDirs.sort(function (a, b) { return a.hostDir < b.hostDir ? -1 : 1; }); - dataDirs.sort(function (a, b) { return a.hostDir < b.hostDir ? -1 : 1; }); - - if (!force && _.isEqual(currentDataDirs, dataDirs)) { - debug('Skipping rebuild, no changes'); - return callback(); - } + if (!force && _.isEqual(currentDataDirs, dataDirs)) { + debug('Skipping rebuild, no changes'); + return callback(); } } + } - const mounts = dataDirs.map(function (v) { return `-v "${v.hostDir}:${v.mountDir}"`; }).join(' '); - const cmd = `docker run --restart=always -d --name="sftp" \ - --hostname sftp \ - --net cloudron \ - --net-alias sftp \ - --log-driver syslog \ - --log-opt syslog-address=udp://127.0.0.1:2514 \ - --log-opt syslog-format=rfc5424 \ - --log-opt tag=sftp \ - -m ${memory} \ - --memory-swap ${memoryLimit} \ - --dns 172.18.0.1 \ - --dns-search=. \ - -p 222:22 \ - ${mounts} \ - -e CLOUDRON_SFTP_TOKEN="${cloudronToken}" \ - -v "${paths.SFTP_KEYS_DIR}:/etc/ssh:ro" \ - --label isCloudronManaged=true \ - --read-only -v /tmp -v /run "${tag}"`; + const mounts = dataDirs.map(function (v) { return `-v "${v.hostDir}:${v.mountDir}"`; }).join(' '); + const cmd = `docker run --restart=always -d --name="sftp" \ + --hostname sftp \ + --net cloudron \ + --net-alias sftp \ + --log-driver syslog \ + --log-opt syslog-address=udp://127.0.0.1:2514 \ + --log-opt syslog-format=rfc5424 \ + --log-opt tag=sftp \ + -m ${memory} \ + --memory-swap ${memoryLimit} \ + --dns 172.18.0.1 \ + --dns-search=. \ + -p 222:22 \ + ${mounts} \ + -e CLOUDRON_SFTP_TOKEN="${cloudronToken}" \ + -v "${paths.SFTP_KEYS_DIR}:/etc/ssh:ro" \ + --label isCloudronManaged=true \ + --read-only -v /tmp -v /run "${tag}"`; - // ignore error if container not found (and fail later) so that this code works across restarts - async.series([ - shell.exec.bind(null, 'stopSftp', 'docker stop sftp || true'), - shell.exec.bind(null, 'removeSftp', 'docker rm -f sftp || true'), - shell.exec.bind(null, 'startSftp', cmd) - ], callback); - }); + // ignore error if container not found (and fail later) so that this code works across restarts + async.series([ + shell.exec.bind(null, 'stopSftp', 'docker stop sftp || true'), + shell.exec.bind(null, 'removeSftp', 'docker rm -f sftp || true'), + shell.exec.bind(null, 'startSftp', cmd) + ], callback); }); }); } diff --git a/src/system.js b/src/system.js index 56b548e16..b89eb5df6 100644 --- a/src/system.js +++ b/src/system.js @@ -23,48 +23,39 @@ const apps = require('./apps.js'), const dfAsync = async.asyncify(df), dfFileAsync = async.asyncify(df.file); -function getVolumeDisks(appsDataDisk, callback) { +async function getVolumeDisks(appsDataDisk) { assert.strictEqual(typeof appsDataDisk, 'string'); - assert.strictEqual(typeof callback, 'function'); let volumeDisks = {}; + const allVolumes = await volumes.list(); - volumes.list(function (error, allVolumes) { - if (error) return callback(error); + for (const volume of allVolumes) { + const [error, result] = await safe(df(volume.hostPath)); + volumeDisks[volume.id] = error ? appsDataDisk : result.filesystem; // ignore any errors + } - async.eachSeries(allVolumes, function (volume, iteratorDone) { - dfFileAsync(volume.hostPath, function (error, result) { - volumeDisks[volume.id] = error ? appsDataDisk : result.filesystem; // ignore any errors - - iteratorDone(); - }); - }, function (error) { - callback(error, volumeDisks); - }); - }); + return volumeDisks; } -function getAppDisks(appsDataDisk, callback) { +async function getAppDisks(appsDataDisk) { assert.strictEqual(typeof appsDataDisk, 'string'); - assert.strictEqual(typeof callback, 'function'); let appDisks = {}; - apps.getAll(function (error, allApps) { - if (error) return callback(error); + return new Promise((resolve, reject) => { + apps.getAll(async function (error, allApps) { + if (error) return reject(error); - async.eachSeries(allApps, function (app, iteratorDone) { - if (!app.dataDir) { - appDisks[app.id] = appsDataDisk; - return iteratorDone(); + for (const app of allApps) { + if (!app.dataDir) { + appDisks[app.id] = appsDataDisk; + } else { + const [error, result] = await safe(df.file(app.dataDir)); + appDisks[app.id] = error ? appsDataDisk : result.filesystem; // ignore any errors + } } - dfFileAsync(app.dataDir, function (error, result) { - appDisks[app.id] = error ? appsDataDisk : result.filesystem; // ignore any errors - iteratorDone(); - }); - }, function (error) { - callback(error, appDisks); + resolve(appDisks); }); }); } @@ -98,7 +89,7 @@ function getDisks(callback) { dfFileAsync.bind(null, paths.APPS_DATA_DIR), dfFileAsync.bind(null, info.DockerRootDir), getBackupDisk, - ], function (error, values) { + ], async function (error, values) { if (error) return callback(new BoxError(BoxError.FS_ERROR, error)); // filter by ext4 and then sort to make sure root disk is first @@ -116,17 +107,13 @@ function getDisks(callback) { volumes: {} // filled below }; - async.series([ - getAppDisks.bind(null, disks.appsDataDisk), - getVolumeDisks.bind(null, disks.appsDataDisk) - ], function (error, values) { - if (error) return callback(new BoxError(BoxError.FS_ERROR, error)); + [error, disks.apps] = await safe(getAppDisks(disks.appsDataDisk)); + if (error) return callback(error); - disks.apps = values[0], - disks.volumes = values[1]; + [error, disks.volumes] = await safe(getVolumeDisks(disks.appsDataDisk)); + if (error) return callback(error); - callback(null, disks); - }); + callback(null, disks); }); }); } diff --git a/src/test/promise-retry-test.js b/src/test/promise-retry-test.js index c0560990f..8ebe1f54d 100644 --- a/src/test/promise-retry-test.js +++ b/src/test/promise-retry-test.js @@ -20,11 +20,11 @@ describe('promiseRetry', function () { }); it('throws error', async function () { - await safe(promiseRetry({ times: 5, interval: 1000 }, async () => { + const [error] = await safe(promiseRetry({ times: 5, interval: 1000 }, async () => { throw new Error('42'); })); - expect(safe.error.message).to.be('42'); + expect(error.message).to.be('42'); }); it('3 tries', async function () { diff --git a/src/test/system-test.js b/src/test/system-test.js index 289167751..d9b74e692 100644 --- a/src/test/system-test.js +++ b/src/test/system-test.js @@ -6,7 +6,7 @@ 'use strict'; -var async = require('async'), +const async = require('async'), database = require('../database.js'), expect = require('expect.js'), system = require('../system.js'); diff --git a/src/test/volumes-test.js b/src/test/volumes-test.js index 8f7f35fff..f8fd895ca 100644 --- a/src/test/volumes-test.js +++ b/src/test/volumes-test.js @@ -6,10 +6,11 @@ 'use strict'; -var async = require('async'), +const async = require('async'), BoxError = require('../boxerror.js'), database = require('../database.js'), expect = require('expect.js'), + safe = require('safetydance'), volumes = require('../volumes.js'); const AUDIT_SOURCE = { ip: '1.2.3.4', userId: 'someuserid' }; @@ -32,82 +33,60 @@ function cleanup(done) { describe('Volumes', function () { before(setup); after(cleanup); + + it('cannot add bad name', async function () { + const [error] = await safe(volumes.add('music/is', '/tmp/music', AUDIT_SOURCE)); + if (!error) throw new Error('Expecting bad field error'); + expect(error.reason).to.be(BoxError.BAD_FIELD); + }); + + it('cannot add bad path', async function () { + const [error] = await safe(volumes.add('music', '/tmp/music', AUDIT_SOURCE)); + if (!error) throw new Error('Expecting bad field error'); + expect(error.reason).to.be(BoxError.BAD_FIELD); + }); + let volume; - - it('cannot add bad name', function (done) { - volumes.add('music/is', '/tmp/music', AUDIT_SOURCE, function (error) { - expect(error.reason).to.be(BoxError.BAD_FIELD); - done(); - }); + it('can add volume', async function () { + const id = await volumes.add('music', '/mnt/cloudron-test-music', AUDIT_SOURCE); + expect(id).to.be.a('string'); + volume = { id, name: 'music', hostPath: '/mnt/cloudron-test-music' }; }); - it('cannot add bad path', function (done) { - volumes.add('music', '/tmp/music', AUDIT_SOURCE, function (error) { - expect(error.reason).to.be(BoxError.BAD_FIELD); - done(); - }); + it('cannot add duplicate path', async function () { + const [error] = await safe(volumes.add('music-dup', '/mnt/cloudron-test-music', AUDIT_SOURCE)); + expect(error.reason).to.be(BoxError.ALREADY_EXISTS); }); - it('can add volume', function (done) { - volumes.add('music', '/mnt/cloudron-test-music', AUDIT_SOURCE, function (error, id) { - expect(error).to.be(null); - expect(id).to.be.a('string'); - volume = { id, name: 'music', hostPath: '/mnt/cloudron-test-music' }; - done(); - }); + it('cannot add duplicate name', async function () { + const [error] = await safe(volumes.add('music', '/media/cloudron-test-music', AUDIT_SOURCE)); + expect(error.reason).to.be(BoxError.ALREADY_EXISTS); }); - it('cannot add duplicate path', function (done) { - volumes.add('music-dup', '/mnt/cloudron-test-music', AUDIT_SOURCE, function (error) { - expect(error.reason).to.be(BoxError.ALREADY_EXISTS); - done(); - }); + it('can get volume', async function () { + const result = await volumes.get(volume.id); + expect(result.hostPath).to.be('/mnt/cloudron-test-music'); }); - it('cannot add duplicate name', function (done) { - volumes.add('music', '/media/cloudron-test-music', AUDIT_SOURCE, function (error) { - expect(error.reason).to.be(BoxError.ALREADY_EXISTS); - done(); - }); + it('cannot get random volume', async function () { + const result = await volumes.get('randomvolume'); + expect(result).to.be(null); }); - it('can get volume', function (done) { - volumes.get(volume.id, function (error, result) { - expect(error).to.be(null); - expect(result.hostPath).to.be('/mnt/cloudron-test-music'); - done(); - }); + it('can list volumes', async function () { + const result = await volumes.list(); + expect(result).to.be.an(Array); + expect(result.length).to.be(1); + expect(result[0].id).to.be(volume.id); + expect(result[0].hostPath).to.be('/mnt/cloudron-test-music'); }); - it('cannot get random volume', function (done) { - volumes.get('randomvolume', function (error) { - expect(error.reason).to.be(BoxError.NOT_FOUND); - done(); - }); + it('cannot del random volume', async function () { + const [error] = await safe(volumes.del({ id: 'randomvolume' }, AUDIT_SOURCE)); + expect(error.reason).to.be(BoxError.NOT_FOUND); }); - it('can list volumes', function (done) { - volumes.list(function (error, result) { - expect(error).to.be(null); - expect(result).to.be.an(Array); - expect(result.length).to.be(1); - expect(result[0].id).to.be(volume.id); - expect(result[0].hostPath).to.be('/mnt/cloudron-test-music'); - done(); - }); - }); - - it('cannot del random volume', function (done) { - volumes.get('randomvolume', function (error) { - expect(error.reason).to.be(BoxError.NOT_FOUND); - done(); - }); - }); - - it('can del volume', function (done) { - volumes.del(volume, AUDIT_SOURCE, function (error) { - expect(error).to.be(null); - done(); - }); + it('can del volume', async function () { + await volumes.del(volume, AUDIT_SOURCE); }); }); diff --git a/src/volumedb.js b/src/volumedb.js deleted file mode 100644 index cd43eac44..000000000 --- a/src/volumedb.js +++ /dev/null @@ -1,95 +0,0 @@ -/* jslint node:true */ - -'use strict'; - -exports = module.exports = { - add, - get, - list, - update, - del, - clear -}; - -const assert = require('assert'), - BoxError = require('./boxerror.js'), - database = require('./database.js'); - -const VOLUMES_FIELDS = [ 'id', 'name', 'hostPath', 'creationTime' ].join(','); - -function get(id, callback) { - assert.strictEqual(typeof id, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query(`SELECT ${VOLUMES_FIELDS} FROM volumes WHERE id=?`, [ id ], function (error, result) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Volume not found')); - - callback(null, result[0]); - }); -} - -function list(callback) { - database.query(`SELECT ${VOLUMES_FIELDS} FROM volumes ORDER BY name`, function (error, results) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - callback(null, results); - }); -} - -function add(id, name, hostPath, callback) { - assert.strictEqual(typeof id, 'string'); - assert.strictEqual(typeof name, 'string'); - assert.strictEqual(typeof hostPath, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query('INSERT INTO volumes (id, name, hostPath) VALUES (?, ?, ?)', [ id, name, hostPath ], function (error) { - if (error && error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('name') !== -1) return callback(new BoxError(BoxError.ALREADY_EXISTS, 'name already exists')); - if (error && error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('hostPath') !== -1) return callback(new BoxError(BoxError.ALREADY_EXISTS, 'hostPath already exists')); - if (error && error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('PRIMARY') !== -1) return callback(new BoxError(BoxError.ALREADY_EXISTS, 'id already exists')); - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - callback(null); - }); -} - -function update(id, data, callback) { - assert.strictEqual(typeof id, 'string'); - assert.strictEqual(typeof data, 'object'); - assert.strictEqual(typeof callback, 'function'); - - var args = [ ], fields = [ ]; - for (var k in data) { - fields.push(k + ' = ?'); - args.push(data[k]); - } - args.push(id); - - database.query('UPDATE volumes SET ' + fields.join(', ') + ' WHERE id=?', args, function (error) { - if (error && error.reason === BoxError.NOT_FOUND) return callback(new BoxError(BoxError.NOT_FOUND, 'Volume not found')); - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - callback(null); - }); -} - -function del(id, callback) { - assert.strictEqual(typeof id, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query('DELETE FROM volumes WHERE id=?', [ id ], function (error, result) { - if (error && error.code === 'ER_ROW_IS_REFERENCED_2') return callback(new BoxError(BoxError.CONFLICT, 'Volume is in use')); - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Volume not found')); - - callback(null); - }); -} - -function clear(callback) { - database.query('DELETE FROM volumes', function (error) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - callback(error); - }); -} diff --git a/src/volumes.js b/src/volumes.js index 753568ec6..db8336072 100644 --- a/src/volumes.js +++ b/src/volumes.js @@ -10,6 +10,7 @@ exports = module.exports = { const assert = require('assert'), BoxError = require('./boxerror.js'), collectd = require('./collectd.js'), + database = require('./database.js'), debug = require('debug')('box:volumes'), ejs = require('ejs'), eventlog = require('./eventlog.js'), @@ -17,8 +18,9 @@ const assert = require('assert'), path = require('path'), safe = require('safetydance'), services = require('./services.js'), - uuid = require('uuid'), - volumedb = require('./volumedb.js'); + uuid = require('uuid'); + +const VOLUMES_FIELDS = [ 'id', 'name', 'hostPath', 'creationTime' ].join(','); const COLLECTD_CONFIG_EJS = fs.readFileSync(__dirname + '/collectd/volume.ejs', { encoding: 'utf8' }); const NOOP_CALLBACK = function (error) { if (error) debug(error); }; @@ -51,66 +53,64 @@ function validateHostPath(hostPath) { return null; } -function add(name, hostPath, auditSource, callback) { +async function add(name, hostPath, auditSource) { assert.strictEqual(typeof name, 'string'); assert.strictEqual(typeof hostPath, 'string'); assert.strictEqual(typeof auditSource, 'object'); - assert.strictEqual(typeof callback, 'function'); let error = validateName(name); - if (error) return callback(error); + if (error) throw error; error = validateHostPath(hostPath); - if (error) return callback(error); + if (error) throw error; const id = uuid.v4(); - volumedb.add(id, name, hostPath, function (error) { - if (error) return callback(error); + try { + await database.query('INSERT INTO volumes (id, name, hostPath) VALUES (?, ?, ?)', [ id, name, hostPath ]); + } catch (error) { + if (error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('name') !== -1) throw new BoxError(BoxError.ALREADY_EXISTS, 'name already exists'); + if (error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('hostPath') !== -1) throw new BoxError(BoxError.ALREADY_EXISTS, 'hostPath already exists'); + if (error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('PRIMARY') !== -1) throw new BoxError(BoxError.ALREADY_EXISTS, 'id already exists'); + throw error; + } - eventlog.add(eventlog.ACTION_VOLUME_ADD, auditSource, { id, name, hostPath }); - services.rebuildService('sftp', NOOP_CALLBACK); + eventlog.add(eventlog.ACTION_VOLUME_ADD, auditSource, { id, name, hostPath }); + services.rebuildService('sftp', NOOP_CALLBACK); - const collectdConf = ejs.render(COLLECTD_CONFIG_EJS, { volumeId: id, hostPath }); - collectd.addProfile(id, collectdConf, NOOP_CALLBACK); + const collectdConf = ejs.render(COLLECTD_CONFIG_EJS, { volumeId: id, hostPath }); + collectd.addProfile(id, collectdConf, NOOP_CALLBACK); - callback(null, id); - }); + return id; } -function get(id, callback) { +async function get(id) { assert.strictEqual(typeof id, 'string'); - assert.strictEqual(typeof callback, 'function'); - volumedb.get(id, function (error, result) { - if (error) return callback(error); + const result = await database.query(`SELECT ${VOLUMES_FIELDS} FROM volumes WHERE id=?`, [ id ]); + if (result.length === 0) return null; - callback(null, result); - }); + return result[0]; } -function list(callback) { - assert.strictEqual(typeof callback, 'function'); - - volumedb.list(function (error, result) { - if (error) return callback(error); - - return callback(null, result); - }); +async function list() { + const result = await database.query(`SELECT ${VOLUMES_FIELDS} FROM volumes ORDER BY name`); + return result; } -function del(volume, auditSource, callback) { +async function del(volume, auditSource) { assert.strictEqual(typeof volume, 'object'); assert.strictEqual(typeof auditSource, 'object'); - assert.strictEqual(typeof callback, 'function'); - volumedb.del(volume.id, function (error) { - if (error) return callback(error); + try { + const result = await database.query('DELETE FROM volumes WHERE id=?', [ volume.id ]); + if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Volume not found'); + } catch (error) { + if (error.code === 'ER_ROW_IS_REFERENCED_2') throw new BoxError(BoxError.CONFLICT, 'Volume is in use'); + throw error; + } - eventlog.add(eventlog.ACTION_VOLUME_REMOVE, auditSource, { volume }); - services.rebuildService('sftp', NOOP_CALLBACK); - collectd.removeProfile(volume.id, NOOP_CALLBACK); - - return callback(null); - }); + eventlog.add(eventlog.ACTION_VOLUME_REMOVE, auditSource, { volume }); + services.rebuildService('sftp', NOOP_CALLBACK); + collectd.removeProfile(volume.id, NOOP_CALLBACK); }