213 lines
8.0 KiB
JavaScript
213 lines
8.0 KiB
JavaScript
'use strict';
|
|
|
|
exports = module.exports = {
|
|
BackupsError: BackupsError,
|
|
|
|
getPaged: getPaged,
|
|
getByAppIdPaged: getByAppIdPaged,
|
|
|
|
getBackupUrl: getBackupUrl,
|
|
getAppBackupUrl: getAppBackupUrl,
|
|
getRestoreUrl: getRestoreUrl,
|
|
|
|
copyLastBackup: copyLastBackup
|
|
};
|
|
|
|
var assert = require('assert'),
|
|
backupdb = require('./backupdb.js'),
|
|
caas = require('./storage/caas.js'),
|
|
config = require('./config.js'),
|
|
debug = require('debug')('box:backups'),
|
|
s3 = require('./storage/s3.js'),
|
|
settings = require('./settings.js'),
|
|
util = require('util'),
|
|
_ = require('underscore');
|
|
|
|
function BackupsError(reason, errorOrMessage) {
|
|
assert.strictEqual(typeof reason, 'string');
|
|
assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined');
|
|
|
|
Error.call(this);
|
|
Error.captureStackTrace(this, this.constructor);
|
|
|
|
this.name = this.constructor.name;
|
|
this.reason = reason;
|
|
if (typeof errorOrMessage === 'undefined') {
|
|
this.message = reason;
|
|
} else if (typeof errorOrMessage === 'string') {
|
|
this.message = errorOrMessage;
|
|
} else {
|
|
this.message = 'Internal error';
|
|
this.nestedError = errorOrMessage;
|
|
}
|
|
}
|
|
util.inherits(BackupsError, Error);
|
|
BackupsError.EXTERNAL_ERROR = 'external error';
|
|
BackupsError.INTERNAL_ERROR = 'internal error';
|
|
BackupsError.MISSING_CREDENTIALS = 'missing credentials';
|
|
|
|
// choose which storage backend we use for test purpose we use s3
|
|
function api(provider) {
|
|
switch (provider) {
|
|
case 'caas': return caas;
|
|
case 's3': return s3;
|
|
default: return null;
|
|
}
|
|
}
|
|
|
|
function getPaged(page, perPage, callback) {
|
|
assert(typeof page === 'number' && page > 0);
|
|
assert(typeof perPage === 'number' && perPage > 0);
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
backupdb.getPaged(page, perPage, function (error, results) {
|
|
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
|
|
|
|
callback(null, results);
|
|
});
|
|
}
|
|
|
|
// this should probably be provider specific
|
|
function cleanBackupConfig(backupConfig) {
|
|
return _.pick(backupConfig, 'provider', 'key', 'bucket', 'prefix');
|
|
}
|
|
|
|
function getByAppIdPaged(page, perPage, appId, callback) {
|
|
assert(typeof page === 'number' && page > 0);
|
|
assert(typeof perPage === 'number' && perPage > 0);
|
|
assert.strictEqual(typeof appId, 'string');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
backupdb.getByAppIdPaged(page, perPage, appId, function (error, results) {
|
|
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
|
|
|
|
callback(null, results);
|
|
});
|
|
}
|
|
|
|
function getBackupUrl(appBackupIds, callback) {
|
|
assert(util.isArray(appBackupIds));
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
var now = new Date();
|
|
var filebase = util.format('backup_%s-v%s', now.toISOString(), config.version());
|
|
var filename = filebase + '.tar.gz';
|
|
|
|
settings.getBackupConfig(function (error, backupConfig) {
|
|
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
|
|
|
|
api(backupConfig.provider).getSignedUploadUrl(backupConfig, filename, function (error, result) {
|
|
if (error) return callback(error);
|
|
|
|
var obj = {
|
|
id: filename,
|
|
url: result.url,
|
|
sessionToken: result.sessionToken,
|
|
backupKey: backupConfig.key
|
|
};
|
|
|
|
debug('getBackupUrl: id:%s url:%s sessionToken:%s backupKey:%s', obj.id, obj.url, obj.sessionToken, obj.backupKey);
|
|
|
|
backupdb.add({ filename: filename, creationTime: now, version: config.version(), type: backupdb.BACKUP_TYPE_BOX,
|
|
dependsOn: appBackupIds, config: cleanBackupConfig(backupConfig) }, function (error) {
|
|
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
|
|
|
|
callback(null, obj);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function getAppBackupUrl(app, callback) {
|
|
assert.strictEqual(typeof app, 'object');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
var now = new Date();
|
|
var filebase = util.format('appbackup_%s_%s-v%s', app.id, now.toISOString(), app.manifest.version);
|
|
var configFilename = filebase + '.json', dataFilename = filebase + '.tar.gz';
|
|
|
|
settings.getBackupConfig(function (error, backupConfig) {
|
|
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
|
|
|
|
api(backupConfig.provider).getSignedUploadUrl(backupConfig, configFilename, function (error, configResult) {
|
|
if (error) return callback(error);
|
|
|
|
api(backupConfig.provider).getSignedUploadUrl(backupConfig, dataFilename, function (error, dataResult) {
|
|
if (error) return callback(error);
|
|
|
|
var obj = {
|
|
id: dataFilename,
|
|
url: dataResult.url,
|
|
configUrl: configResult.url,
|
|
sessionToken: dataResult.sessionToken, // this token can be used for both config and data upload
|
|
backupKey: backupConfig.key // only data is encrypted
|
|
};
|
|
|
|
debug('getAppBackupUrl: %j', obj);
|
|
|
|
backupdb.add({ filename: dataFilename, creationTime: now, version: app.manifest.version, type: backupdb.BACKUP_TYPE_APP,
|
|
dependsOn: [ ], config: cleanBackupConfig(backupConfig) }, function (error) {
|
|
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
|
|
|
|
callback(null, obj);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
// backupId is the s3 filename. appbackup_%s_%s-v%s.tar.gz
|
|
function getRestoreUrl(backupId, callback) {
|
|
assert.strictEqual(typeof backupId, 'string');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
settings.getBackupConfig(function (error, backupConfig) {
|
|
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
|
|
|
|
backupdb.get(backupId, function (error, backupInfo) {
|
|
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
|
|
|
|
api(backupInfo.provider).getSignedDownloadUrl(backupConfig, backupInfo, function (error, result) {
|
|
if (error) return callback(error);
|
|
|
|
var obj = {
|
|
id: backupId,
|
|
url: result.url,
|
|
sessionToken: result.sessionToken,
|
|
backupKey: backupInfo.key
|
|
};
|
|
|
|
debug('getRestoreUrl: id:%s url:%s sessionToken:%s backupKey:%s', obj.id, obj.url, obj.sessionToken, obj.backupKey);
|
|
|
|
callback(null, obj);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function copyLastBackup(app, callback) {
|
|
assert.strictEqual(typeof app, 'object');
|
|
assert.strictEqual(typeof app.lastBackupId, 'string');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
var toFilenameArchive = util.format('appbackup_%s_%s-v%s.tar.gz', app.id, (new Date()).toISOString(), app.manifest.version);
|
|
var toFilenameConfig = util.format('appbackup_%s_%s-v%s.json', app.id, (new Date()).toISOString(), app.manifest.version);
|
|
|
|
settings.getBackupConfig(function (error, backupConfig) {
|
|
if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error));
|
|
|
|
api(backupConfig.provider).copyObject(backupConfig, app.lastBackupId, toFilenameArchive, function (error) {
|
|
if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error));
|
|
|
|
// TODO change that logic by adjusting app.lastBackupId to not contain the file type
|
|
var configFileId = app.lastBackupId.slice(0, -'.tar.gz'.length) + '.json';
|
|
|
|
api(backupConfig.provider).copyObject(backupConfig, configFileId, toFilenameConfig, function (error) {
|
|
if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error));
|
|
|
|
return callback(null, toFilenameArchive);
|
|
});
|
|
});
|
|
});
|
|
}
|