run update as a task, so it is cancelable

This commit is contained in:
Girish Ramakrishnan
2018-11-30 16:00:47 -08:00
parent b409fd775d
commit 840d78b2f4
6 changed files with 77 additions and 52 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ mkdir -p "${PLATFORM_DATA_DIR}/collectd/collectd.conf.d"
mkdir -p "${PLATFORM_DATA_DIR}/logrotate.d"
mkdir -p "${PLATFORM_DATA_DIR}/acme"
mkdir -p "${PLATFORM_DATA_DIR}/backup"
mkdir -p "${PLATFORM_DATA_DIR}/logs/backup"
mkdir -p "${PLATFORM_DATA_DIR}/logs/backup" "${PLATFORM_DATA_DIR}/logs/updater"
mkdir -p "${PLATFORM_DATA_DIR}/update"
mkdir -p "${BOX_DATA_DIR}/appicons"
+1
View File
@@ -35,4 +35,5 @@ exports = module.exports = {
LOG_DIR: path.join(config.baseDir(), 'platformdata/logs'),
// this pattern is for the cloudron logs API route to work
BACKUP_LOG_FILE: path.join(config.baseDir(), 'platformdata/logs/backup/app.log'),
UPDATER_LOG_FILE: path.join(config.baseDir(), 'platformdata/logs/updater/app.log')
};
+1 -1
View File
@@ -9,7 +9,7 @@ fi
readonly UPDATER_SERVICE="cloudron-updater"
readonly DATETIME=`date '+%Y-%m-%d_%H-%M-%S'`
readonly LOG_FILE="/var/log/cloudron-updater-${DATETIME}.log"
readonly LOG_FILE="/home/yellowtent/platformdata/logs/updater/cloudron-updater-${DATETIME}.log"
if [[ $# == 1 && "$1" == "--check" ]]; then
echo "OK"
+23 -9
View File
@@ -3,7 +3,6 @@
exports = module.exports = {
update: update,
get: get,
clear: clear,
startTask: startTask,
stopTask: stopTask,
@@ -25,18 +24,27 @@ let assert = require('assert'),
paths = require('./paths.js'),
safe = require('safetydance'),
taskdb = require('./taskdb.js'),
util = require('util');
util = require('util'),
_ = require('underscore');
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
const TASKS = {
'backup': {
backup: {
lock: locker.OP_FULL_BACKUP,
logFile: paths.BACKUP_LOG_FILE,
program: __dirname + '/tasks/backuptask.js',
onFailure: mailer.backupFailed,
startEventId: eventlog.ACTION_BACKUP_START,
finishEventId: eventlog.ACTION_BACKUP_FINISH
},
update: {
lock: locker.OP_BOX_UPDATE,
logFile: paths.UPDATER_LOG_FILE,
program: __dirname + '/tasks/updatertask.js',
onFailure: NOOP_CALLBACK,
startEventId: eventlog.ACTION_UPDATE,
finishEventId: eventlog.ACTION_UPDATE
}
};
@@ -93,11 +101,12 @@ function get(id, callback) {
});
}
function clear(id, callback) {
function clear(id, args, callback) {
assert.strictEqual(typeof id, 'string');
assert(args && typeof args === 'object');
assert.strictEqual(typeof callback, 'function');
update(id, { percent: 0, message: 'Starting', result: '', errorMessage: '', args: {} }, callback);
update(id, { percent: 0, message: 'Starting', result: '', errorMessage: '', args: args }, callback);
}
function startTask(id, args, auditSource, callback) {
@@ -124,17 +133,22 @@ function startTask(id, args, auditSource, callback) {
// when parent process dies, this process is killed because KillMode=control-group in systemd unit file
assert(!gTasks[id], 'Task is already running');
clear(id, NOOP_CALLBACK);
clear(id, args, NOOP_CALLBACK);
eventlog.add(taskInfo.startEventId, auditSource, args);
gTasks[id] = child_process.fork(taskInfo.program, [], { stdio: [ 'pipe', fd, fd, 'ipc' ]});
gTasks[id] = child_process.fork(taskInfo.program, [], { stdio: [ 'pipe', fd, fd, 'ipc' ]}); // fork requires ipc
gTasks[id].once('exit', function (code, signal) {
debug(`startTask: ${id} completed with code ${code} and signal ${signal}`);
get(id, function (error, progress) {
if (!error && progress.errorMessage) error = new Error(progress.errorMessage);
if (!error && progress.percent !== 100) { // task crashed or was killed by us (code 50)
error = code === 0 ? new Error(`${id} task stopped`) : new Error(`${id} task crashed with code ${code} and signal ${signal}`);
update(id, { percent: 100, errorMessage: error.message }, NOOP_CALLBACK);
} else if (!error && progress.errorMessage) {
error = new Error(progress.errorMessage);
}
eventlog.add(taskInfo.finishEventId, auditSource, { errorMessage: error ? error.message : null, backupId: progress ? progress.result : null });
eventlog.add(taskInfo.finishEventId, auditSource, _.extend({ errorMessage: error ? error.message : null }, progress ? progress.result : {}));
locker.unlock(taskInfo.lock);
+38
View File
@@ -0,0 +1,38 @@
'use strict';
require('supererror')({ splatchError: true });
let database = require('../database.js'),
debug = require('debug')('box:updatertask'),
tasks = require('../tasks.js'),
updater = require('../updater.js');
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
process.on('SIGTERM', function () {
process.exit(0);
});
function exit(error) {
if (!error) process.exit(0);
debug(error);
process.exit(50);
}
// Main process starts here
debug('Staring update');
database.initialize(function (error) {
if (error) return exit(error);
tasks.get(tasks.TASK_UPDATE, function (error, result) {
if (error) return exit(error);
if (!result.args.boxUpdateInfo) return exit(new Error('Invalid args:' + JSON.stringify(result)));
updater.update(result.args.boxUpdateInfo, (progress) => tasks.update(tasks.TASK_UPDATE, progress, NOOP_CALLBACK), function (updateError) {
const progress = { percent: 100, errorMessage: updateError ? updateError.message : '' };
tasks.update(tasks.TASK_UPDATE, progress, () => exit(updateError));
});
});
});
+13 -41
View File
@@ -2,6 +2,7 @@
exports = module.exports = {
updateToLatest: updateToLatest,
update: update,
UpdaterError: UpdaterError
};
@@ -13,8 +14,6 @@ var assert = require('assert'),
config = require('./config.js'),
crypto = require('crypto'),
debug = require('debug')('box:updater'),
eventlog = require('./eventlog.js'),
locker = require('./locker.js'),
mkdirp = require('mkdirp'),
os = require('os'),
path = require('path'),
@@ -27,7 +26,6 @@ var assert = require('assert'),
const RELEASES_PUBLIC_KEY = path.join(__dirname, 'releases.gpg');
const UPDATE_CMD = path.join(__dirname, 'scripts/update.sh');
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
function UpdaterError(reason, errorOrMessage) {
assert.strictEqual(typeof reason, 'string');
@@ -152,30 +150,28 @@ function downloadAndVerifyRelease(updateInfo, callback) {
});
}
function doUpdate(boxUpdateInfo, callback) {
function update(boxUpdateInfo, progressCallback, callback) {
assert(boxUpdateInfo && typeof boxUpdateInfo === 'object');
assert.strictEqual(typeof progressCallback, 'function');
assert.strictEqual(typeof callback, 'function');
function updateError(e) {
tasks.update(tasks.TASK_UPDATE, { percent: -1, errorMessage: e.message }, NOOP_CALLBACK);
callback(e);
}
tasks.update(tasks.TASK_UPDATE, { percent: 5, message: 'Downloading and verifying release' }, NOOP_CALLBACK);
progressCallback({ percent: 5, message: 'Downloading and verifying release' });
downloadAndVerifyRelease(boxUpdateInfo, function (error, packageInfo) {
if (error) return updateError(error);
if (error) return callback(error);
tasks.update(tasks.TASK_UPDATE, { percent: 10, message: 'Backing up' }, NOOP_CALLBACK);
progressCallback({ percent: 10, message: 'Backing up' });
backups.backupBoxAndApps((progress) => tasks.update(tasks.TASK_MIGRATE, { percent: 10+progress.percent*70/100, message: progress.message }, NOOP_CALLBACK), function (error) {
if (error) return updateError(error);
backups.backupBoxAndApps((progress) => progressCallback({ percent: 10+progress.percent*70/100, message: progress.message }), function (error) {
if (error) return callback(error);
debug('updating box %s', boxUpdateInfo.sourceTarballUrl);
tasks.update(tasks.TASK_UPDATE, { percent: 70, message: 'Installing update' }, NOOP_CALLBACK);
progressCallback({ percent: 70, message: 'Installing update' });
// run installer.sh from new box code as a separate service
shell.sudo('update', [ UPDATE_CMD, packageInfo.file ], {}, function (error) {
if (error) return updateError(error);
if (error) return callback(error);
// Do not add any code here. The installer script will stop the box code any instant
});
@@ -183,30 +179,6 @@ function doUpdate(boxUpdateInfo, callback) {
});
}
function update(boxUpdateInfo, auditSource, callback) {
assert(boxUpdateInfo && typeof boxUpdateInfo === 'object');
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
var error = locker.lock(locker.OP_BOX_UPDATE);
if (error) return callback(new UpdaterError(UpdaterError.BAD_STATE, error.message));
eventlog.add(eventlog.ACTION_UPDATE, auditSource, { boxUpdateInfo: boxUpdateInfo });
// ensure tools can 'wait' on progress
tasks.update(tasks.TASK_UPDATE, { percent: 0, message: 'Starting' }, NOOP_CALLBACK);
debug('Starting update');
doUpdate(boxUpdateInfo, function (error) {
if (error) {
debug('Update failed with error:', error);
locker.unlock(locker.OP_BOX_UPDATE);
}
});
callback(null);
}
function updateToLatest(auditSource, callback) {
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
@@ -215,5 +187,5 @@ function updateToLatest(auditSource, callback) {
if (!boxUpdateInfo) return callback(new UpdaterError(UpdaterError.ALREADY_UPTODATE, 'No update available'));
if (!boxUpdateInfo.sourceTarballUrl) return callback(new UpdaterError(UpdaterError.BAD_STATE, 'No automatic update available'));
update(boxUpdateInfo, auditSource, callback);
tasks.startTask(tasks.TASK_UPDATE, { boxUpdateInfo }, auditSource, callback);
}