diff --git a/CHANGES b/CHANGES index 788d8814c..c353eb738 100644 --- a/CHANGES +++ b/CHANGES @@ -1581,4 +1581,5 @@ * (backup) Do not abort archive if file(s) disappear * Show app upstream version in the info dialog * Add Scaleway ObjectStorage backup backend +* Preserve update backups for 3 weeks diff --git a/migrations/20190413140945-backups-add-preserveSecs.js b/migrations/20190413140945-backups-add-preserveSecs.js new file mode 100644 index 000000000..73a78230a --- /dev/null +++ b/migrations/20190413140945-backups-add-preserveSecs.js @@ -0,0 +1,15 @@ +'use strict'; + +exports.up = function(db, callback) { + db.runSql('ALTER TABLE backups ADD COLUMN preserveSecs INTEGER DEFAULT 0', function (error) { + if (error) console.error(error); + callback(error); + }); +}; + +exports.down = function(db, callback) { + db.runSql('ALTER TABLE backups DROP COLUMN preserveSecs', function (error) { + if (error) console.error(error); + callback(error); + }); +}; diff --git a/migrations/schema.sql b/migrations/schema.sql index c14c4ba91..83a7b3e5c 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -141,6 +141,7 @@ CREATE TABLE IF NOT EXISTS backups( state VARCHAR(16) NOT NULL, manifestJson TEXT, /* to validate if the app can be installed in this version of box */ format VARCHAR(16) DEFAULT "tgz", + preserveSecs INTEGER DEFAULT 0, PRIMARY KEY (id)); diff --git a/src/apptask.js b/src/apptask.js index 8c92bec86..52332e475 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -586,7 +586,7 @@ function backup(app, callback) { async.series([ updateApp.bind(null, app, { installationProgress: '10, Backing up' }), - backups.backupApp.bind(null, app, (progress) => updateApp(app, { installationProgress: `30, ${progress.message}` }, NOOP_CALLBACK)), + backups.backupApp.bind(null, app, { /* options */ }, (progress) => updateApp(app, { installationProgress: `30, ${progress.message}` }, NOOP_CALLBACK)), // done! function (callback) { @@ -706,7 +706,8 @@ function update(app, callback) { async.series([ updateApp.bind(null, app, { installationProgress: '15, Backing up app' }), - backups.backupApp.bind(null, app, (progress) => updateApp(app, { installationProgress: `15, Backup - ${progress.message}` }, NOOP_CALLBACK)) + // preserve update backups for 3 weeks + backups.backupApp.bind(null, app, { preserveSecs: 3*7*24*60*60 }, (progress) => updateApp(app, { installationProgress: `15, Backup - ${progress.message}` }, NOOP_CALLBACK)) ], function (error) { if (error) error.backupError = true; next(error); diff --git a/src/backupdb.js b/src/backupdb.js index 0048d8f7d..146659b48 100644 --- a/src/backupdb.js +++ b/src/backupdb.js @@ -6,7 +6,7 @@ var assert = require('assert'), safe = require('safetydance'), util = require('util'); -var BACKUPS_FIELDS = [ 'id', 'creationTime', 'version', 'type', 'dependsOn', 'state', 'manifestJson', 'format' ]; +var BACKUPS_FIELDS = [ 'id', 'creationTime', 'version', 'type', 'dependsOn', 'state', 'manifestJson', 'format', 'preserveSecs' ]; exports = module.exports = { add: add, diff --git a/src/backups.js b/src/backups.js index 8a4d3ebdd..85dabd31b 100644 --- a/src/backups.js +++ b/src/backups.js @@ -867,9 +867,10 @@ function snapshotApp(app, progressCallback, callback) { }); } -function rotateAppBackup(backupConfig, app, timestamp, progressCallback, callback) { +function rotateAppBackup(backupConfig, app, options, timestamp, progressCallback, callback) { assert.strictEqual(typeof backupConfig, 'object'); assert.strictEqual(typeof app, 'object'); + assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof timestamp, 'string'); assert.strictEqual(typeof progressCallback, 'function'); assert.strictEqual(typeof callback, 'function'); @@ -892,7 +893,7 @@ function rotateAppBackup(backupConfig, app, timestamp, progressCallback, callbac copy.on('done', function (copyBackupError) { const state = copyBackupError ? backupdb.BACKUP_STATE_ERROR : backupdb.BACKUP_STATE_NORMAL; - backupdb.update(backupId, { state: state }, function (error) { + backupdb.update(backupId, { preserveSecs: options.preserveSecs || 0, state: state }, function (error) { if (copyBackupError) return callback(copyBackupError); if (error) return callback(new BackupsError(BackupsError.INTERNAL_ERROR, error)); @@ -933,8 +934,9 @@ function uploadAppSnapshot(backupConfig, app, progressCallback, callback) { }); } -function backupAppWithTimestamp(app, timestamp, progressCallback, callback) { +function backupAppWithTimestamp(app, options, timestamp, progressCallback, callback) { assert.strictEqual(typeof app, 'object'); + assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof timestamp, 'string'); assert.strictEqual(typeof progressCallback, 'function'); assert.strictEqual(typeof callback, 'function'); @@ -947,13 +949,14 @@ function backupAppWithTimestamp(app, timestamp, progressCallback, callback) { uploadAppSnapshot(backupConfig, app, progressCallback, function (error) { if (error) return callback(error); - rotateAppBackup(backupConfig, app, timestamp, progressCallback, callback); + rotateAppBackup(backupConfig, app, options, timestamp, progressCallback, callback); }); }); } -function backupApp(app, progressCallback, callback) { +function backupApp(app, options, progressCallback, callback) { assert.strictEqual(typeof app, 'object'); + assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof progressCallback, 'function'); assert.strictEqual(typeof callback, 'function'); @@ -961,7 +964,7 @@ function backupApp(app, progressCallback, callback) { debug(`backupApp - Backing up ${app.fqdn} with timestamp ${timestamp}`); - backupAppWithTimestamp(app, timestamp, progressCallback, callback); + backupAppWithTimestamp(app, options, timestamp, progressCallback, callback); } // this function expects you to have a lock. Unlike other progressCallback this also has a progress field @@ -986,7 +989,7 @@ function backupBoxAndApps(progressCallback, callback) { return iteratorCallback(null, null); // nothing to backup } - backupAppWithTimestamp(app, timestamp, (progress) => progressCallback({ percent: percent, message: progress.message }), function (error, backupId) { + backupAppWithTimestamp(app, { /* options */ }, timestamp, (progress) => progressCallback({ percent: percent, message: progress.message }), function (error, backupId) { if (error && error.reason !== BackupsError.BAD_STATE) { debugApp(app, 'Unable to backup', error); return iteratorCallback(error); @@ -1101,6 +1104,7 @@ function cleanupAppBackups(backupConfig, referencedAppBackups, callback) { async.eachSeries(appBackups, function iterator(appBackup, iteratorDone) { if (referencedAppBackups.indexOf(appBackup.id) !== -1) return iteratorDone(); + if ((now - appBackup.creationTime) < (appBackup.preserveSecs * 1000)) return iteratorDone(); if ((now - appBackup.creationTime) < (backupConfig.retentionSecs * 1000)) return iteratorDone(); debug('cleanupAppBackups: removing %s', appBackup.id);