Ghost/core/server/data/db/state-manager.js
Hannah Wolfe 0b79abf5b2 Added new, simpler, linear boot process
Background:
- Ghosts existing boot process is split across multiple files, has affordances for outdated ways of running Ghost and is generally non-linear making it nigh-impossible to follow
- The web of dependencies that are loaded on boot are also impossible to unpick, which makes it really hard to decouple Ghost
- With 4.0 we want to introduce a new, linear, simpler, clearer way to boot up Ghost to unlock decoupling Ghost into much smaller pieces

This commit:
- adds a new ghost.js file which switches between boot mode with `node index` or `node index old` so that if we find bugs we can work around them this week
   - Note: the old boot process will go away very soon, but ghost.js will remain as the interface between the command to start Ghost and the application code
- reworks the database migration process into a standalone utility, so that the DB is handled as one simple step of the boot process, decoupled from everything else
- is missing tests for this new db utility
- leaves a lot of work to do around loading core code, services, express apps in a sensible order, as work to fix this would start to break the old boot process
- doesn't use the new maintenance app because we aren't restarting the server here, instead we have the concept of a "core app" that starts in maintenance mode - need to think about how apps will be decoupled in the near future
2021-02-08 11:56:44 +00:00

107 lines
2.9 KiB
JavaScript

const KnexMigrator = require('knex-migrator');
const errors = require('@tryghost/errors');
const states = {
READY: 0,
NEEDS_INITIALISATION: 1,
NEEDS_MIGRATION: 2,
ERROR: 3
};
const printState = ({state, logging}) => {
if (state === states.READY) {
logging.info('Database is in a ready state.');
}
if (state === states.NEEDS_INITIALISATION) {
logging.warn('Database state requires initialisation.');
}
if (state === states.NEEDS_MIGRATION) {
logging.warn('Database state requires migration.');
}
if (state === states.ERROR) {
logging.error('Database is in an error state.');
}
};
class DatabaseStateManager {
constructor({knexMigratorFilePath}) {
this.knexMigrator = new KnexMigrator({
knexMigratorFilePath
});
}
async getState() {
let state = states.READY;
try {
await this.knexMigrator.isDatabaseOK();
return state;
} catch (error) {
// CASE: database has not yet been initialised
if (error.code === 'DB_NOT_INITIALISED') {
state = states.NEEDS_INITIALISATION;
return state;
}
// CASE: there's no migration table so we can't understand
if (error.code === 'MIGRATION_TABLE_IS_MISSING') {
state = states.NEEDS_INITIALISATION;
return state;
}
// CASE: database needs migrations
if (error.code === 'DB_NEEDS_MIGRATION') {
state = states.NEEDS_MIGRATION;
return state;
}
// CASE: database connection errors, unknown cases
let errorToThrow = error;
if (!errors.utils.isIgnitionError(errorToThrow)) {
errorToThrow = new errors.GhostError({message: errorToThrow.message, err: errorToThrow});
}
throw errorToThrow;
}
}
async makeReady({logging}) {
try {
let state = await this.getState();
if (logging) {
printState({state, logging});
}
if (state === states.READY) {
return;
}
if (state === states.NEEDS_INITIALISATION) {
await this.knexMigrator.init();
}
if (state === states.NEEDS_MIGRATION) {
await this.knexMigrator.migrate();
}
state = await this.getState();
if (logging) {
printState({state, logging});
}
} catch (error) {
let errorToThrow = error;
if (!errors.utils.isIgnitionError(error)) {
errorToThrow = new errors.GhostError({message: errorToThrow.message, err: errorToThrow});
}
throw errorToThrow;
}
}
}
module.exports = DatabaseStateManager;