backups: split listing and targets
This commit is contained in:
+5
-165
@@ -1,15 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
get,
|
||||
getByIdentifierAndStatePaged,
|
||||
getByTypePaged,
|
||||
add,
|
||||
update,
|
||||
setState,
|
||||
list,
|
||||
del,
|
||||
|
||||
startBackupTask,
|
||||
|
||||
startCleanupTask,
|
||||
@@ -58,6 +49,7 @@ exports = module.exports = {
|
||||
};
|
||||
|
||||
const assert = require('assert'),
|
||||
backupListing = require('./backuplisting.js'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
constants = require('./constants.js'),
|
||||
cron = require('./cron.js'),
|
||||
@@ -66,7 +58,6 @@ const assert = require('assert'),
|
||||
database = require('./database.js'),
|
||||
debug = require('debug')('box:backups'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
hat = require('./hat.js'),
|
||||
locks = require('./locks.js'),
|
||||
mounts = require('./mounts.js'),
|
||||
path = require('path'),
|
||||
@@ -78,28 +69,11 @@ const assert = require('assert'),
|
||||
uuid = require('uuid'),
|
||||
_ = require('./underscore.js');
|
||||
|
||||
const BACKUPS_FIELDS = [ 'id', 'remotePath', 'label', 'identifier', 'creationTime', 'packageVersion', 'type', 'dependsOnJson', 'state', 'manifestJson', 'preserveSecs', 'encryptionVersion', 'appConfigJson', 'targetId' ].join(',');
|
||||
|
||||
const BACKUP_TARGET_FIELDS = [ 'id', 'label', 'configJson', 'limitsJson', 'retentionJson', 'schedule', 'encryptionJson', 'format', 'priority', 'creationTime', 'ts' ].join(',');
|
||||
|
||||
function postProcess(result) {
|
||||
assert.strictEqual(typeof result, 'object');
|
||||
|
||||
result.dependsOn = result.dependsOnJson ? safe.JSON.parse(result.dependsOnJson) : [];
|
||||
delete result.dependsOnJson;
|
||||
|
||||
result.manifest = result.manifestJson ? safe.JSON.parse(result.manifestJson) : null;
|
||||
delete result.manifestJson;
|
||||
|
||||
result.appConfig = result.appConfigJson ? safe.JSON.parse(result.appConfigJson) : null;
|
||||
delete result.appConfigJson;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function postProcessTarget(result) {
|
||||
assert.strictEqual(typeof result, 'object');
|
||||
|
||||
result.config = result.configJson ? safe.JSON.parse(result.configJson) : {};
|
||||
delete result.configJson;
|
||||
|
||||
@@ -140,80 +114,6 @@ function generateEncryptionKeysSync(password) {
|
||||
};
|
||||
}
|
||||
|
||||
async function add(data) {
|
||||
assert(data && typeof data === 'object');
|
||||
assert.strictEqual(typeof data.remotePath, 'string');
|
||||
assert(data.encryptionVersion === null || typeof data.encryptionVersion === 'number');
|
||||
assert.strictEqual(typeof data.packageVersion, 'string');
|
||||
assert.strictEqual(typeof data.type, 'string');
|
||||
assert.strictEqual(typeof data.identifier, 'string');
|
||||
assert.strictEqual(typeof data.state, 'string');
|
||||
assert(Array.isArray(data.dependsOn));
|
||||
assert.strictEqual(typeof data.manifest, 'object');
|
||||
assert.strictEqual(typeof data.preserveSecs, 'number');
|
||||
assert.strictEqual(typeof data.appConfig, 'object');
|
||||
|
||||
const creationTime = data.creationTime || new Date(); // allow tests to set the time
|
||||
const manifestJson = JSON.stringify(data.manifest);
|
||||
const prefixId = data.type === exports.BACKUP_TYPE_APP ? `${data.type}_${data.identifier}` : data.type; // type and identifier are same for other types
|
||||
const id = `${prefixId}_v${data.packageVersion}_${hat(32)}`; // id is used by the UI to derive dependent packages. making this a UUID will require a lot of db querying
|
||||
const appConfigJson = data.appConfig ? JSON.stringify(data.appConfig) : null;
|
||||
|
||||
const targets = await database.query(`SELECT id FROM backupTargets WHERE priority=?`, [ true ]);
|
||||
const targetId = targets[0].id;
|
||||
|
||||
const [error] = await safe(database.query('INSERT INTO backups (id, remotePath, identifier, encryptionVersion, packageVersion, type, creationTime, state, dependsOnJson, manifestJson, preserveSecs, appConfigJson, targetId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[ id, data.remotePath, data.identifier, data.encryptionVersion, data.packageVersion, data.type, creationTime, data.state, JSON.stringify(data.dependsOn), manifestJson, data.preserveSecs, appConfigJson, targetId ]));
|
||||
|
||||
if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'Backup already exists');
|
||||
if (error) throw error;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
async function getByIdentifierAndStatePaged(identifier, state, page, perPage) {
|
||||
assert.strictEqual(typeof identifier, 'string');
|
||||
assert.strictEqual(typeof state, 'string');
|
||||
assert(typeof page === 'number' && page > 0);
|
||||
assert(typeof perPage === 'number' && perPage > 0);
|
||||
|
||||
const results = await database.query(`SELECT ${BACKUPS_FIELDS} FROM backups WHERE identifier = ? AND state = ? ORDER BY creationTime DESC LIMIT ?,?`, [ identifier, state, (page-1)*perPage, perPage ]);
|
||||
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async function get(id) {
|
||||
assert.strictEqual(typeof id, 'string');
|
||||
|
||||
const result = await database.query('SELECT ' + BACKUPS_FIELDS + ' FROM backups WHERE id = ? ORDER BY creationTime DESC', [ id ]);
|
||||
if (result.length === 0) return null;
|
||||
|
||||
return postProcess(result[0]);
|
||||
}
|
||||
|
||||
async function getByTypePaged(type, page, perPage) {
|
||||
assert.strictEqual(typeof type, 'string');
|
||||
assert(typeof page === 'number' && page > 0);
|
||||
assert(typeof perPage === 'number' && perPage > 0);
|
||||
|
||||
const results = await database.query(`SELECT ${BACKUPS_FIELDS} FROM backups WHERE type = ? ORDER BY creationTime DESC LIMIT ?,?`, [ type, (page-1)*perPage, perPage ]);
|
||||
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function validateLabel(label) {
|
||||
assert.strictEqual(typeof label, 'string');
|
||||
|
||||
if (label.length >= 200) return new BoxError(BoxError.BAD_FIELD, 'label too long');
|
||||
if (/[^a-zA-Z0-9._() -]/.test(label)) return new BoxError(BoxError.BAD_FIELD, 'label can only contain alphanumerals, space, dot, hyphen, brackets or underscore');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function validatePolicy(policy) {
|
||||
assert.strictEqual(typeof policy, 'object');
|
||||
|
||||
@@ -230,48 +130,6 @@ async function validatePolicy(policy) {
|
||||
if ('keepYearly' in retention && typeof retention.keepYearly !== 'number') return new BoxError(BoxError.BAD_FIELD, 'retention.keepYearly must be a number');
|
||||
}
|
||||
|
||||
// this is called by REST API
|
||||
async function update(id, data) {
|
||||
assert.strictEqual(typeof id, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
|
||||
let error;
|
||||
if ('label' in data) {
|
||||
error = validateLabel(data.label);
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
const fields = [], values = [];
|
||||
for (const p in data) {
|
||||
if (p === 'label' || p === 'preserveSecs') {
|
||||
fields.push(p + ' = ?');
|
||||
values.push(data[p]);
|
||||
}
|
||||
}
|
||||
values.push(id);
|
||||
|
||||
const backup = await get(id);
|
||||
if (backup === null) throw new BoxError(BoxError.NOT_FOUND, 'Backup not found');
|
||||
|
||||
const result = await database.query('UPDATE backups SET ' + fields.join(', ') + ' WHERE id = ?', values);
|
||||
if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Backup not found');
|
||||
|
||||
if ('preserveSecs' in data) {
|
||||
// update the dependancies
|
||||
for (const depId of backup.dependsOn) {
|
||||
await database.query('UPDATE backups SET preserveSecs=? WHERE id = ?', [ data.preserveSecs, depId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function setState(id, state) {
|
||||
assert.strictEqual(typeof id, 'string');
|
||||
assert.strictEqual(typeof state, 'string');
|
||||
|
||||
const result = await database.query('UPDATE backups SET state = ? WHERE id = ?', [state, id]);
|
||||
if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Backup not found');
|
||||
}
|
||||
|
||||
async function startBackupTask(auditSource) {
|
||||
const [error] = await safe(locks.acquire(locks.TYPE_FULL_BACKUP_TASK));
|
||||
if (error) throw new BoxError(BoxError.BAD_STATE, `Another backup task is in progress: ${error.message}`);
|
||||
@@ -287,7 +145,7 @@ async function startBackupTask(auditSource) {
|
||||
// background
|
||||
tasks.startTask(taskId, { timeout: 24 * 60 * 60 * 1000 /* 24 hours */, nice: 15, memoryLimit, oomScoreAdjust: -999 })
|
||||
.then(async (backupId) => {
|
||||
const backup = await get(backupId);
|
||||
const backup = await backupListing.get(backupId);
|
||||
await eventlog.add(eventlog.ACTION_BACKUP_FINISH, auditSource, { taskId, backupId, remotePath: backup.remotePath });
|
||||
})
|
||||
.catch(async (error) => {
|
||||
@@ -302,24 +160,6 @@ async function startBackupTask(auditSource) {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
async function list(page, perPage) {
|
||||
assert(typeof page === 'number' && page > 0);
|
||||
assert(typeof perPage === 'number' && perPage > 0);
|
||||
|
||||
const results = await database.query('SELECT ' + BACKUPS_FIELDS + ' FROM backups ORDER BY creationTime DESC LIMIT ?,?', [ (page-1)*perPage, perPage ]);
|
||||
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async function del(id) {
|
||||
assert.strictEqual(typeof id, 'string');
|
||||
|
||||
const result = await database.query('DELETE FROM backups WHERE id=?', [ id ]);
|
||||
if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Backup not found');
|
||||
}
|
||||
|
||||
// this function is used in migrations - 20200512172301-settings-backup-encryption.js
|
||||
function cleanupCacheFilesSync() {
|
||||
const files = safe.fs.readdirSync(path.join(paths.BACKUP_INFO_DIR));
|
||||
@@ -433,7 +273,7 @@ async function ensureMounted() {
|
||||
|
||||
async function getPolicy() {
|
||||
const results = await database.query(`SELECT ${BACKUP_TARGET_FIELDS} FROM backupTargets WHERE priority=?`, [ true ]);
|
||||
const result = postProcessTarget(results[0]);
|
||||
const result = postProcess(results[0]);
|
||||
return { retention: result.retention, schedule: result.schedule };
|
||||
}
|
||||
|
||||
@@ -501,12 +341,12 @@ async function addDefaultTarget() {
|
||||
async function getTarget(id) {
|
||||
const results = await database.query(`SELECT ${BACKUP_TARGET_FIELDS} FROM backupTargets WHERE id=?`, [ id ]);
|
||||
if (results.length === 0) return null;
|
||||
return postProcessTarget(results[0]);
|
||||
return postProcess(results[0]);
|
||||
}
|
||||
|
||||
async function getConfig() {
|
||||
const results = await database.query(`SELECT ${BACKUP_TARGET_FIELDS} FROM backupTargets WHERE priority=?`, [ true ]);
|
||||
const result = postProcessTarget(results[0]);
|
||||
const result = postProcess(results[0]);
|
||||
|
||||
const config = result.config;
|
||||
config.format = result.format;
|
||||
|
||||
Reference in New Issue
Block a user