Skip to content

Commit 5c67d77

Browse files
committed
feature: maintenance mode
closes #6976 - add maintenance mode when running migrations - refactor update/populate migrations
1 parent 4dbb877 commit 5c67d77

23 files changed

+448
-518
lines changed

core/server/config/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ ConfigManager.prototype.set = function (config) {
191191
availableApps: this._config.paths.availableApps || {},
192192
clientAssets: path.join(corePath, '/built/assets/')
193193
},
194+
maintenance: {},
194195
scheduling: {
195196
active: activeSchedulingAdapter,
196197
path: schedulingPath

core/server/data/migration/index.js

+4-82
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,4 @@
1-
var versioning = require('../schema').versioning,
2-
errors = require('../../errors'),
3-
4-
// private
5-
logger,
6-
populate = require('./populate'),
7-
update = require('./update'),
8-
9-
// public
10-
init,
11-
reset = require('./reset'),
12-
backup = require('./backup');
13-
14-
/**
15-
*
16-
* @type {{info: logger.info, warn: logger.warn}}
17-
*/
18-
logger = {
19-
info: function info(message) {
20-
errors.logComponentInfo('Migrations', message);
21-
},
22-
warn: function warn(message) {
23-
errors.logComponentWarn('Skipping Migrations', message);
24-
}
25-
};
26-
27-
// Check for whether data is needed to be bootstrapped or not
28-
init = function init(tablesOnly) {
29-
tablesOnly = tablesOnly || false;
30-
31-
var modelOptions = {
32-
context: {
33-
internal: true
34-
}
35-
};
36-
37-
// There are 4 possible cases:
38-
// CASE 1: The database exists and is up-to-date
39-
// CASE 2: The database exists but is out of date
40-
// CASE 3: The database exists but the currentVersion setting does not or cannot be understood
41-
// CASE 4: The database has not yet been created
42-
return versioning.getDatabaseVersion().then(function (databaseVersion) {
43-
var defaultVersion = versioning.getDefaultDatabaseVersion();
44-
45-
// Update goes first, to allow for FORCE_MIGRATION
46-
// CASE 2: The database exists but is out of date
47-
if (databaseVersion < defaultVersion || process.env.FORCE_MIGRATION) {
48-
// Migrate to latest version
49-
logger.info('Database upgrade required from version ' + databaseVersion + ' to ' + defaultVersion);
50-
return update(databaseVersion, defaultVersion, logger, modelOptions);
51-
52-
// CASE 1: The database exists and is up-to-date
53-
} else if (databaseVersion === defaultVersion) {
54-
logger.info('Up-to-date at version ' + databaseVersion);
55-
return;
56-
57-
// CASE 3: The database exists but the currentVersion setting does not or cannot be understood
58-
} else {
59-
// In this case we don't understand the version because it is too high
60-
errors.logErrorAndExit(
61-
'Your database is not compatible with this version of Ghost',
62-
'You will need to create a new database'
63-
);
64-
}
65-
}, function (err) {
66-
if (err && err.message === 'Settings table does not exist') {
67-
// CASE 4: The database has not yet been created
68-
// Bring everything up from initial version.
69-
logger.info('Database initialisation required for version ' + versioning.getDefaultDatabaseVersion());
70-
return populate(logger, tablesOnly, modelOptions);
71-
}
72-
// CASE 3: the database exists but the currentVersion setting does not or cannot be understood
73-
// In this case the setting was missing or there was some other problem
74-
errors.logErrorAndExit(err, 'Problem occurred during migration initialisation!');
75-
});
76-
};
77-
78-
module.exports = {
79-
init: init,
80-
reset: reset,
81-
backupDatabase: backup
82-
};
1+
exports.update = require('./update');
2+
exports.populate = require('./populate');
3+
exports.reset = require('./reset');
4+
exports.backupDatabase = require('./backup');

core/server/data/migration/populate.js

+28-18
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,49 @@
11
// # Populate
22
// Create a brand new database for a new install of ghost
3-
var Promise = require('bluebird'),
3+
var Promise = require('bluebird'),
44
commands = require('../schema').commands,
55
fixtures = require('./fixtures'),
6-
models = require('../../models'),
7-
schema = require('../schema').tables,
6+
errors = require('../../errors'),
7+
schema = require('../schema').tables,
88
schemaTables = Object.keys(schema),
9-
populate;
9+
populate, logger;
10+
11+
// @TODO: remove me asap!
12+
logger = {
13+
info: function info(message) {
14+
errors.logComponentInfo('Migrations', message);
15+
},
16+
warn: function warn(message) {
17+
errors.logComponentWarn('Skipping Migrations', message);
18+
}
19+
};
1020

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

23-
var tableSequence = Promise.mapSeries(schemaTables, function createTable(table) {
24-
logger.info('Creating table: ' + table);
25-
return commands.createTable(table);
26-
});
28+
var tablesOnly = options.tablesOnly,
29+
modelOptions = {
30+
context: {
31+
internal: true
32+
}
33+
},
34+
tableSequence = Promise.mapSeries(schemaTables, function createTable(table) {
35+
logger.info('Creating table: ' + table);
36+
return commands.createTable(table);
37+
});
38+
39+
logger.info('Creating tables...');
2740

2841
if (tablesOnly) {
2942
return tableSequence;
3043
}
3144

3245
return tableSequence.then(function () {
33-
// Load the fixtures
3446
return fixtures.populate(logger, modelOptions);
35-
}).then(function () {
36-
return models.Settings.populateDefaults(modelOptions);
3747
});
3848
};
3949

core/server/data/migration/update.js

+55-29
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,25 @@
33
var Promise = require('bluebird'),
44
backup = require('./backup'),
55
fixtures = require('./fixtures'),
6-
models = require('../../models'),
6+
errors = require('../../errors'),
7+
i18n = require('../../i18n'),
78
db = require('../../data/db'),
89
sequence = require('../../utils/sequence'),
910
versioning = require('../schema').versioning,
1011

1112
updateDatabaseSchema,
1213
migrateToDatabaseVersion,
13-
update;
14+
update, logger;
15+
16+
// @TODO: remove me asap!
17+
logger = {
18+
info: function info(message) {
19+
errors.logComponentInfo('Migrations', message);
20+
},
21+
warn: function warn(message) {
22+
errors.logComponentWarn('Skipping Migrations', message);
23+
}
24+
};
1425

1526
/**
1627
* update database schema for one single version
@@ -47,9 +58,6 @@ migrateToDatabaseVersion = function migrateToDatabaseVersion(version, logger, mo
4758
.then(function () {
4859
return versioning.setDatabaseVersion(transaction, version);
4960
})
50-
.then(function () {
51-
return models.Settings.populateDefaults(modelOptions);
52-
})
5361
.then(function () {
5462
transaction.commit();
5563
resolve();
@@ -68,36 +76,54 @@ migrateToDatabaseVersion = function migrateToDatabaseVersion(version, logger, mo
6876
/**
6977
* ## Update
7078
* Does a backup, then updates the database and fixtures
71-
*
72-
* @param {String} fromVersion
73-
* @param {String} toVersion
74-
* @param {{info: logger.info, warn: logger.warn}} logger
75-
* @returns {Promise<*>}
7679
*/
77-
update = function update(fromVersion, toVersion, logger, modelOptions) {
78-
modelOptions = modelOptions || {};
79-
// Is the current version lower than the version we can migrate from?
80-
// E.g. is this blog's DB older than 003?
80+
update = function update(options) {
81+
options = options || {};
82+
83+
var fromVersion = options.fromVersion,
84+
toVersion = options.toVersion,
85+
forceMigration = options.forceMigration,
86+
versionsToUpdate,
87+
modelOptions = {
88+
context: {
89+
internal: true
90+
}
91+
};
92+
93+
// CASE: current database version is lower then we support
8194
if (fromVersion < versioning.canMigrateFromVersion) {
82-
return versioning.showCannotMigrateError();
95+
return Promise.reject(new errors.DatabaseVersion(
96+
i18n.t('errors.data.versioning.index.cannotMigrate.error'),
97+
i18n.t('errors.data.versioning.index.cannotMigrate.context'),
98+
i18n.t('common.seeLinkForInstructions', {link: 'http://support.ghost.org/how-to-upgrade/'})
99+
));
83100
}
101+
// CASE: the database exists but is out of date
102+
else if (fromVersion < toVersion || forceMigration) {
103+
fromVersion = forceMigration ? versioning.canMigrateFromVersion : fromVersion;
84104

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

87-
// Figure out which versions we're updating through.
88-
// This shouldn't include the from/current version (which we're already on)
89-
var versionsToUpdate = versioning.getMigrationVersions(fromVersion, toVersion).slice(1);
90-
91-
return backup(logger)
92-
.then(function () {
93-
return Promise.mapSeries(versionsToUpdate, function (versionToUpdate) {
94-
return migrateToDatabaseVersion(versionToUpdate, logger, modelOptions);
109+
return backup(logger)
110+
.then(function () {
111+
return Promise.mapSeries(versionsToUpdate, function (versionToUpdate) {
112+
return migrateToDatabaseVersion(versionToUpdate, logger, modelOptions);
113+
});
114+
})
115+
.then(function () {
116+
logger.info('Finished!');
95117
});
96-
})
97-
.catch(function () {
98-
// we don't want that your blog can't start
99-
Promise.resolve();
100-
});
118+
}
119+
// CASE: database is up-to-date
120+
else if (fromVersion === toVersion) {
121+
return Promise.resolve();
122+
}
123+
// CASE: we don't understand the version
124+
else {
125+
return Promise.reject(new errors.DatabaseVersion(i18n.t('errors.data.versioning.index.dbVersionNotRecognized')));
126+
}
101127
};
102128

103129
module.exports = update;

core/server/data/schema/versioning.js

+7-18
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
var path = require('path'),
2+
Promise = require('bluebird'),
23
db = require('../db'),
34
errors = require('../../errors'),
45
i18n = require('../../i18n'),
56
defaultSettings = require('./default-settings'),
67

78
defaultDatabaseVersion;
89

9-
// Default Database Version
10+
// Newest Database Version
1011
// The migration version number according to the hardcoded default settings
1112
// This is the version the database should be at or migrated to
12-
function getDefaultDatabaseVersion() {
13+
function getNewestDatabaseVersion() {
1314
if (!defaultDatabaseVersion) {
1415
// This be the current version according to the software
1516
defaultDatabaseVersion = defaultSettings.core.databaseVersion.defaultValue;
@@ -31,17 +32,14 @@ function getDatabaseVersion() {
3132
.first('value')
3233
.then(function (version) {
3334
if (!version || isNaN(version.value)) {
34-
return errors.rejectError(new Error(
35-
i18n.t('errors.data.versioning.index.dbVersionNotRecognized')
36-
));
35+
return Promise.reject(new errors.DatabaseVersion(i18n.t('errors.data.versioning.index.dbVersionNotRecognized')));
3736
}
3837

3938
return version.value;
4039
});
4140
}
42-
return errors.rejectError(new Error(
43-
i18n.t('errors.data.versioning.index.settingsTableDoesNotExist')
44-
));
41+
42+
return Promise.reject(new errors.DatabaseNotPopulated(i18n.t('errors.data.versioning.index.databaseNotPopulated')));
4543
});
4644
}
4745

@@ -66,14 +64,6 @@ function getMigrationVersions(fromVersion, toVersion) {
6664
return versions;
6765
}
6866

69-
function showCannotMigrateError() {
70-
return errors.logAndRejectError(
71-
i18n.t('errors.data.versioning.index.cannotMigrate.error'),
72-
i18n.t('errors.data.versioning.index.cannotMigrate.context'),
73-
i18n.t('common.seeLinkForInstructions', {link: 'http://support.ghost.org/how-to-upgrade/'})
74-
);
75-
}
76-
7767
/**
7868
* ### Get Version Tasks
7969
* Tries to require a directory matching the version number
@@ -107,8 +97,7 @@ function getUpdateFixturesTasks(version, logger) {
10797

10898
module.exports = {
10999
canMigrateFromVersion: '003',
110-
showCannotMigrateError: showCannotMigrateError,
111-
getDefaultDatabaseVersion: getDefaultDatabaseVersion,
100+
getNewestDatabaseVersion: getNewestDatabaseVersion,
112101
getDatabaseVersion: getDatabaseVersion,
113102
setDatabaseVersion: setDatabaseVersion,
114103
getMigrationVersions: getMigrationVersions,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
function DatabaseNotPopulated(message) {
2+
this.message = message;
3+
this.stack = new Error().stack;
4+
this.statusCode = 500;
5+
this.errorType = this.name;
6+
}
7+
8+
DatabaseNotPopulated.prototype = Object.create(Error.prototype);
9+
DatabaseNotPopulated.prototype.name = 'DatabaseNotPopulated';
10+
11+
module.exports = DatabaseNotPopulated;
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function DatabaseVersion(message, context, help) {
2+
this.message = message;
3+
this.stack = new Error().stack;
4+
this.statusCode = 500;
5+
this.errorType = this.name;
6+
this.context = context;
7+
this.help = help;
8+
}
9+
10+
DatabaseVersion.prototype = Object.create(Error.prototype);
11+
DatabaseVersion.prototype.name = 'DatabaseVersion';
12+
13+
module.exports = DatabaseVersion;

core/server/errors/index.js

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ var _ = require('lodash'),
2020
TokenRevocationError = require('./token-revocation-error'),
2121
VersionMismatchError = require('./version-mismatch-error'),
2222
IncorrectUsage = require('./incorrect-usage'),
23+
Maintenance = require('./maintenance'),
24+
DatabaseNotPopulated = require('./database-not-populated'),
25+
DatabaseVersion = require('./database-version'),
2326
i18n = require('../i18n'),
2427
config,
2528
errors,
@@ -452,3 +455,6 @@ module.exports.TooManyRequestsError = TooManyRequestsError;
452455
module.exports.TokenRevocationError = TokenRevocationError;
453456
module.exports.VersionMismatchError = VersionMismatchError;
454457
module.exports.IncorrectUsage = IncorrectUsage;
458+
module.exports.Maintenance = Maintenance;
459+
module.exports.DatabaseNotPopulated = DatabaseNotPopulated;
460+
module.exports.DatabaseVersion = DatabaseVersion;

core/server/errors/maintenance.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
function Maintenance(message) {
2+
this.message = message;
3+
this.stack = new Error().stack;
4+
this.statusCode = 503;
5+
this.errorType = this.name;
6+
}
7+
8+
Maintenance.prototype = Object.create(Error.prototype);
9+
Maintenance.prototype.name = 'Maintenance';
10+
11+
module.exports = Maintenance;

0 commit comments

Comments
 (0)