Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/maintenance mode #7019

Merged
merged 1 commit into from
Jul 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/server/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ ConfigManager.prototype.set = function (config) {
availableApps: this._config.paths.availableApps || {},
clientAssets: path.join(corePath, '/built/assets/')
},
maintenance: {},
scheduling: {
active: activeSchedulingAdapter,
path: schedulingPath
Expand Down
86 changes: 4 additions & 82 deletions core/server/data/migration/index.js
Original file line number Diff line number Diff line change
@@ -1,82 +1,4 @@
var versioning = require('../schema').versioning,
errors = require('../../errors'),

// private
logger,
populate = require('./populate'),
update = require('./update'),

// public
init,
reset = require('./reset'),
backup = require('./backup');

/**
*
* @type {{info: logger.info, warn: logger.warn}}
*/
logger = {
info: function info(message) {
errors.logComponentInfo('Migrations', message);
},
warn: function warn(message) {
errors.logComponentWarn('Skipping Migrations', message);
}
};

// Check for whether data is needed to be bootstrapped or not
init = function init(tablesOnly) {
tablesOnly = tablesOnly || false;

var modelOptions = {
context: {
internal: true
}
};

// There are 4 possible cases:
// CASE 1: The database exists and is up-to-date
// CASE 2: The database exists but is out of date
// CASE 3: The database exists but the currentVersion setting does not or cannot be understood
// CASE 4: The database has not yet been created
return versioning.getDatabaseVersion().then(function (databaseVersion) {
var defaultVersion = versioning.getDefaultDatabaseVersion();

// Update goes first, to allow for FORCE_MIGRATION
// CASE 2: The database exists but is out of date
if (databaseVersion < defaultVersion || process.env.FORCE_MIGRATION) {
// Migrate to latest version
logger.info('Database upgrade required from version ' + databaseVersion + ' to ' + defaultVersion);
return update(databaseVersion, defaultVersion, logger, modelOptions);

// CASE 1: The database exists and is up-to-date
} else if (databaseVersion === defaultVersion) {
logger.info('Up-to-date at version ' + databaseVersion);
return;

// CASE 3: The database exists but the currentVersion setting does not or cannot be understood
} else {
// In this case we don't understand the version because it is too high
errors.logErrorAndExit(
'Your database is not compatible with this version of Ghost',
'You will need to create a new database'
);
}
}, function (err) {
if (err && err.message === 'Settings table does not exist') {
// CASE 4: The database has not yet been created
// Bring everything up from initial version.
logger.info('Database initialisation required for version ' + versioning.getDefaultDatabaseVersion());
return populate(logger, tablesOnly, modelOptions);
}
// CASE 3: the database exists but the currentVersion setting does not or cannot be understood
// In this case the setting was missing or there was some other problem
errors.logErrorAndExit(err, 'Problem occurred during migration initialisation!');
});
};

module.exports = {
init: init,
reset: reset,
backupDatabase: backup
};
exports.update = require('./update');
exports.populate = require('./populate');
exports.reset = require('./reset');
exports.backupDatabase = require('./backup');
46 changes: 28 additions & 18 deletions core/server/data/migration/populate.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,49 @@
// # Populate
// Create a brand new database for a new install of ghost
var Promise = require('bluebird'),
var Promise = require('bluebird'),
commands = require('../schema').commands,
fixtures = require('./fixtures'),
models = require('../../models'),
schema = require('../schema').tables,
errors = require('../../errors'),
schema = require('../schema').tables,
schemaTables = Object.keys(schema),
populate;
populate, logger;

// @TODO: remove me asap!
logger = {
info: function info(message) {
errors.logComponentInfo('Migrations', message);
},
warn: function warn(message) {
errors.logComponentWarn('Skipping Migrations', message);
}
};

/**
* ## Populate
* Uses the schema to determine table structures, and automatically creates each table in order
* TODO: use this directly in tests, so migration.init() can forget about tablesOnly as an option
*
* @param {{info: logger.info, warn: logger.warn}} logger
* @param {Boolean} [tablesOnly] - used by tests
* @returns {Promise<*>}
*/
populate = function populate(logger, tablesOnly, modelOptions) {
logger.info('Creating tables...');
populate = function populate(options) {
options = options || {};

var tableSequence = Promise.mapSeries(schemaTables, function createTable(table) {
logger.info('Creating table: ' + table);
return commands.createTable(table);
});
var tablesOnly = options.tablesOnly,
modelOptions = {
context: {
internal: true
}
},
tableSequence = Promise.mapSeries(schemaTables, function createTable(table) {
logger.info('Creating table: ' + table);
return commands.createTable(table);
});

logger.info('Creating tables...');

if (tablesOnly) {
return tableSequence;
}

return tableSequence.then(function () {
// Load the fixtures
return fixtures.populate(logger, modelOptions);
}).then(function () {
return models.Settings.populateDefaults(modelOptions);
});
};

Expand Down
84 changes: 55 additions & 29 deletions core/server/data/migration/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,25 @@
var Promise = require('bluebird'),
backup = require('./backup'),
fixtures = require('./fixtures'),
models = require('../../models'),
errors = require('../../errors'),
i18n = require('../../i18n'),
db = require('../../data/db'),
sequence = require('../../utils/sequence'),
versioning = require('../schema').versioning,

updateDatabaseSchema,
migrateToDatabaseVersion,
update;
update, logger;

// @TODO: remove me asap!
logger = {
info: function info(message) {
errors.logComponentInfo('Migrations', message);
},
warn: function warn(message) {
errors.logComponentWarn('Skipping Migrations', message);
}
};

/**
* update database schema for one single version
Expand Down Expand Up @@ -47,9 +58,6 @@ migrateToDatabaseVersion = function migrateToDatabaseVersion(version, logger, mo
.then(function () {
return versioning.setDatabaseVersion(transaction, version);
})
.then(function () {
return models.Settings.populateDefaults(modelOptions);
})
.then(function () {
transaction.commit();
resolve();
Expand All @@ -68,36 +76,54 @@ migrateToDatabaseVersion = function migrateToDatabaseVersion(version, logger, mo
/**
* ## Update
* Does a backup, then updates the database and fixtures
*
* @param {String} fromVersion
* @param {String} toVersion
* @param {{info: logger.info, warn: logger.warn}} logger
* @returns {Promise<*>}
*/
update = function update(fromVersion, toVersion, logger, modelOptions) {
modelOptions = modelOptions || {};
// Is the current version lower than the version we can migrate from?
// E.g. is this blog's DB older than 003?
update = function update(options) {
options = options || {};

var fromVersion = options.fromVersion,
toVersion = options.toVersion,
forceMigration = options.forceMigration,
versionsToUpdate,
modelOptions = {
context: {
internal: true
}
};

// CASE: current database version is lower then we support
if (fromVersion < versioning.canMigrateFromVersion) {
return versioning.showCannotMigrateError();
return Promise.reject(new errors.DatabaseVersion(
i18n.t('errors.data.versioning.index.cannotMigrate.error'),
i18n.t('errors.data.versioning.index.cannotMigrate.context'),
i18n.t('common.seeLinkForInstructions', {link: 'http://support.ghost.org/how-to-upgrade/'})
));
}
// CASE: the database exists but is out of date
else if (fromVersion < toVersion || forceMigration) {
fromVersion = forceMigration ? versioning.canMigrateFromVersion : fromVersion;

fromVersion = process.env.FORCE_MIGRATION ? versioning.canMigrateFromVersion : fromVersion;
// Figure out which versions we're updating through.
// This shouldn't include the from/current version (which we're already on)
versionsToUpdate = versioning.getMigrationVersions(fromVersion, toVersion).slice(1);

// Figure out which versions we're updating through.
// This shouldn't include the from/current version (which we're already on)
var versionsToUpdate = versioning.getMigrationVersions(fromVersion, toVersion).slice(1);

return backup(logger)
.then(function () {
return Promise.mapSeries(versionsToUpdate, function (versionToUpdate) {
return migrateToDatabaseVersion(versionToUpdate, logger, modelOptions);
return backup(logger)
.then(function () {
return Promise.mapSeries(versionsToUpdate, function (versionToUpdate) {
return migrateToDatabaseVersion(versionToUpdate, logger, modelOptions);
});
})
.then(function () {
logger.info('Finished!');
});
})
.catch(function () {
// we don't want that your blog can't start
Promise.resolve();
});
}
// CASE: database is up-to-date
else if (fromVersion === toVersion) {
return Promise.resolve();
}
// CASE: we don't understand the version
else {
return Promise.reject(new errors.DatabaseVersion(i18n.t('errors.data.versioning.index.dbVersionNotRecognized')));
}
};

module.exports = update;
25 changes: 7 additions & 18 deletions core/server/data/schema/versioning.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
var path = require('path'),
Promise = require('bluebird'),
db = require('../db'),
errors = require('../../errors'),
i18n = require('../../i18n'),
defaultSettings = require('./default-settings'),

defaultDatabaseVersion;

// Default Database Version
// Newest Database Version
// The migration version number according to the hardcoded default settings
// This is the version the database should be at or migrated to
function getDefaultDatabaseVersion() {
function getNewestDatabaseVersion() {
if (!defaultDatabaseVersion) {
// This be the current version according to the software
defaultDatabaseVersion = defaultSettings.core.databaseVersion.defaultValue;
Expand All @@ -31,17 +32,14 @@ function getDatabaseVersion() {
.first('value')
.then(function (version) {
if (!version || isNaN(version.value)) {
return errors.rejectError(new Error(
i18n.t('errors.data.versioning.index.dbVersionNotRecognized')
));
return Promise.reject(new errors.DatabaseVersion(i18n.t('errors.data.versioning.index.dbVersionNotRecognized')));
}

return version.value;
});
}
return errors.rejectError(new Error(
i18n.t('errors.data.versioning.index.settingsTableDoesNotExist')
));

return Promise.reject(new errors.DatabaseNotPopulated(i18n.t('errors.data.versioning.index.databaseNotPopulated')));
});
}

Expand All @@ -66,14 +64,6 @@ function getMigrationVersions(fromVersion, toVersion) {
return versions;
}

function showCannotMigrateError() {
return errors.logAndRejectError(
i18n.t('errors.data.versioning.index.cannotMigrate.error'),
i18n.t('errors.data.versioning.index.cannotMigrate.context'),
i18n.t('common.seeLinkForInstructions', {link: 'http://support.ghost.org/how-to-upgrade/'})
);
}

/**
* ### Get Version Tasks
* Tries to require a directory matching the version number
Expand Down Expand Up @@ -107,8 +97,7 @@ function getUpdateFixturesTasks(version, logger) {

module.exports = {
canMigrateFromVersion: '003',
showCannotMigrateError: showCannotMigrateError,
getDefaultDatabaseVersion: getDefaultDatabaseVersion,
getNewestDatabaseVersion: getNewestDatabaseVersion,
getDatabaseVersion: getDatabaseVersion,
setDatabaseVersion: setDatabaseVersion,
getMigrationVersions: getMigrationVersions,
Expand Down
11 changes: 11 additions & 0 deletions core/server/errors/database-not-populated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function DatabaseNotPopulated(message) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 500;
this.errorType = this.name;
}

DatabaseNotPopulated.prototype = Object.create(Error.prototype);
DatabaseNotPopulated.prototype.name = 'DatabaseNotPopulated';

module.exports = DatabaseNotPopulated;
13 changes: 13 additions & 0 deletions core/server/errors/database-version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function DatabaseVersion(message, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 500;
this.errorType = this.name;
this.context = context;
this.help = help;
}

DatabaseVersion.prototype = Object.create(Error.prototype);
DatabaseVersion.prototype.name = 'DatabaseVersion';

module.exports = DatabaseVersion;
6 changes: 6 additions & 0 deletions core/server/errors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ var _ = require('lodash'),
TokenRevocationError = require('./token-revocation-error'),
VersionMismatchError = require('./version-mismatch-error'),
IncorrectUsage = require('./incorrect-usage'),
Maintenance = require('./maintenance'),
DatabaseNotPopulated = require('./database-not-populated'),
DatabaseVersion = require('./database-version'),
i18n = require('../i18n'),
config,
errors,
Expand Down Expand Up @@ -452,3 +455,6 @@ module.exports.TooManyRequestsError = TooManyRequestsError;
module.exports.TokenRevocationError = TokenRevocationError;
module.exports.VersionMismatchError = VersionMismatchError;
module.exports.IncorrectUsage = IncorrectUsage;
module.exports.Maintenance = Maintenance;
module.exports.DatabaseNotPopulated = DatabaseNotPopulated;
module.exports.DatabaseVersion = DatabaseVersion;
11 changes: 11 additions & 0 deletions core/server/errors/maintenance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function Maintenance(message) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 503;
this.errorType = this.name;
}

Maintenance.prototype = Object.create(Error.prototype);
Maintenance.prototype.name = 'Maintenance';

module.exports = Maintenance;
Loading