diff --git a/src/routes/test/volumes-test.js b/src/routes/test/volumes-test.js index 901e2e6f2..d5ee37342 100644 --- a/src/routes/test/volumes-test.js +++ b/src/routes/test/volumes-test.js @@ -67,6 +67,19 @@ describe('Volumes API', function () { expect(response.body.hostPath).to.be('/media/cloudron-test-music'); }); + it('can update volume', async function () { + let response = await superagent.post(`${serverUrl}/api/v1/volumes/${volumeId}`) + .query({ access_token: owner.token }) + .send({ mountOptions: { hostPath: '/media/cloudron-test-music-2' }}); + expect(response.statusCode).to.equal(204); + + response = await superagent.get(`${serverUrl}/api/v1/volumes/${volumeId}`) + .query({ access_token: owner.token }); + expect(response.statusCode).to.equal(200); + expect(response.body.id).to.be(volumeId); + expect(response.body.hostPath).to.be('/media/cloudron-test-music-2'); + }); + it('can delete volume', async function () { const response = await superagent.del(`${serverUrl}/api/v1/volumes/${volumeId}`) .query({ access_token: owner.token }); diff --git a/src/routes/volumes.js b/src/routes/volumes.js index f271ee3fd..89ac5074d 100644 --- a/src/routes/volumes.js +++ b/src/routes/volumes.js @@ -3,6 +3,7 @@ exports = module.exports = { add, get, + update, del, list, load, @@ -48,6 +49,17 @@ async function get(req, res, next) { next(new HttpSuccess(200, volumes.removePrivateFields(req.volume))); } +async function update(req, res, next) { + assert.strictEqual(typeof req.params.id, 'string'); + + if (!req.body.mountOptions || typeof req.body.mountOptions !== 'object') return next(new HttpError(400, 'mountOptions must be a non-null object')); + + const [error] = await safe(volumes.update(req.volume.id, req.body.mountOptions, AuditSource.fromRequest(req))); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(204)); +} + async function del(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); diff --git a/src/server.js b/src/server.js index 0ca61ebbe..491f06b93 100644 --- a/src/server.js +++ b/src/server.js @@ -385,6 +385,7 @@ async function initializeExpressSync() { router.post('/api/v1/volumes', json, token, authorizeAdmin, routes.volumes.add); router.get ('/api/v1/volumes', token, authorizeAdmin, routes.volumes.list); router.get ('/api/v1/volumes/:id', token, authorizeAdmin, routes.volumes.load, routes.volumes.get); + router.post('/api/v1/volumes/:id', token, authorizeAdmin, routes.volumes.load, routes.volumes.update); router.del ('/api/v1/volumes/:id', token, authorizeAdmin, routes.volumes.load, routes.volumes.del); router.get ('/api/v1/volumes/:id/status', token, authorizeAdmin, routes.volumes.load, routes.volumes.getStatus); router.post('/api/v1/volumes/:id/remount', token, authorizeAdmin, routes.volumes.load, routes.volumes.remount); diff --git a/src/volumes.js b/src/volumes.js index 459737e5f..362e73e59 100644 --- a/src/volumes.js +++ b/src/volumes.js @@ -3,6 +3,7 @@ exports = module.exports = { add, get, + update, del, list, remount, @@ -134,6 +135,34 @@ function removePrivateFields(volume) { return newVolume; } +async function update(id, mountOptions, auditSource) { + assert.strictEqual(typeof id, 'string'); + assert.strictEqual(typeof mountOptions, 'object'); + assert.strictEqual(typeof auditSource, 'object'); + + const volume = await get(id); + + const error = mounts.validateMountOptions(volume.mountType, mountOptions); + if (error) throw error; + + let hostPath; + if (volume.mountType === 'mountpoint' || volume.mountType === 'filesystem') { + const error = validateHostPath(mountOptions.hostPath, volume.mountType); + if (error) throw error; + hostPath = mountOptions.hostPath; + } else { + hostPath = path.join(paths.VOLUMES_MOUNT_DIR, id); + const mount = { name: volume.name, hostPath, mountType: volume.mountType, mountOptions }; + await mounts.tryAddMount(mount, { timeout: 10 }); // 10 seconds + } + + await safe(database.query('UPDATE volumes SET hostPath=?,mountOptionsJson=? WHERE id=?)', [ hostPath, JSON.stringify(mountOptions), id ])); + + await eventlog.add(eventlog.ACTION_VOLUME_UPDATE, auditSource, { id, name, hostPath }); + // in theory, we only need to do this mountpoint volumes. but for some reason a restart is required to detect new "mounts" + safe(services.rebuildService('sftp', auditSource), { debug }); +} + async function get(id) { assert.strictEqual(typeof id, 'string');