service-core/core/core.mjs
Jonatan Nilsson 47344c5e7a
Some checks failed
continuous-integration/appveyor/branch AppVeyor build failed
Updated core logic and how stable is calculated.
Fixed some minor bugs.
Will now no longer travel through history but instead stop at last stable version.
2022-02-18 13:32:44 +00:00

183 lines
6.1 KiB
JavaScript

import Application from './application.mjs'
import Util from './util.mjs'
import getLog from './log.mjs'
import { Low } from 'lowdb'
import StaticProvider from './providers/static.mjs'
import GitProvider from './providers/git.mjs'
export default class Core {
static providers = new Map()
static addProvider(name, provider) {
if (!name || typeof(name) !== 'string')
throw new Error('addProvider name must be a string')
if (typeof(provider) !== 'function')
throw new Error(`addProvider ${name} provider must be a class`)
let test = new provider({})
if (typeof(test.checkConfig) !== 'function')
throw new Error(`addProvider ${name} provider class missing checkConfig`)
if (typeof(test.getLatestVersion) !== 'function')
throw new Error(`addProvider ${name} provider class missing getLatestVersion`)
Core.providers.set(name, provider)
}
constructor(db, util, log, restart = function() {}) {
// some sanity checks
if (!log || typeof(log) !== 'object') throw new Error('log parameter was invalid')
if (typeof(log.event) !== 'object') throw new Error('log parameter was invalid')
if (typeof(log.info) !== 'function'
|| typeof(log.warn) !== 'function'
|| typeof(log.error) !== 'function'
|| typeof(log.event.info) !== 'function'
|| typeof(log.event.warn) !== 'function'
|| typeof(log.event.error) !== 'function') throw new Error('log parameter was invalid')
if (!util || !(util instanceof Util)) throw new Error('util not instance of Util')
if (!db || !(db instanceof Low)) throw new Error('db not instance of Low')
if (typeof(restart) !== 'function') throw new Error('restart was not a function')
this.running = false
this.db = db
this.util = util
this.log = log
this.restart = restart
this.applications = []
this.applicationMap = new Map()
this._applicationFatalCrash = null
}
getApplication(name) {
return this.applicationMap.get(name)
}
async init() {
this.log.info(`Verifying config`)
this.util.verifyConfig(this.db.config)
let names = this.util.getAppNames(this.db.config)
this.log.info(`Found applications: ${names.join(', ')}.`)
for (let name of names) {
try {
let provConstructor = Core.providers.get(this.db.config[name].provider)
let provider = new provConstructor(this.db.config[name])
await provider.checkConfig(this.db.config[name])
let application = new Application({
db: this.db,
util: this.util,
log: getLog(name, this.db.config[name].log || null),
core: this,
}, provider, name)
this.applications.push(application)
this.applicationMap.set(name, application)
} catch (err) {
this.log.error(err, `Error creating application ${name} with provider ${this.db.config[name].provider}: ${err.message}`)
}
}
if (names.length && !this.applications.length) {
return Promise.reject(new Error('None of the application were successful in running'))
}
}
async run() {
if (this.running) return
this.running = true
this.log.info(`Running updater on ${this.applications.length} apps`)
await Promise.all(this.applications.map((app) => {
return app.update().catch(err => {
app.ctx.log.error(err, `Error updating: ${err.message}`)
})
}))
let found = false
for (let app of this.applications) {
app.startAutoupdater()
await this.runApplication(app).then(
() => {
found = true
},
err => {
app.ctx.log.error(err, `Error running: ${err.message}`)
}
)
app.on('updated', this.runApplication.bind(this, app))
}
if (!found) {
throw new Error('No stable application was found')
}
}
criticalError(application, version, errorCode) {
application.ctx.log.fatal(`Critical error ${errorCode} running ${version.version}`)
version.stable = -2
this.db.writeSync()
}
async runApplication(application) {
let name = application.name
let found = false
if (!this.db.data.core[name].versions.length) {
return Promise.reject(new Error(`No versions were found`))
}
for (let i = 0; i < this.db.data.core[name].versions.length; i++) {
let version = this.db.data.core[name].versions[i]
if (!version.installed || version.stable < -1) continue
if (version.stable < 0 && !application.fresh) {
application.ctx.log.warn(`Restarting for ${version.version} due to last run failing while not being in fresh state`)
return this.restart(`Application ${name} has fresh false while attempting to run ${version.version} with stable -1`)
}
await application.closeServer()
this._applicationFatalCrash = this.criticalError.bind(this, application, version)
process.once('exit', this._applicationFatalCrash)
let wasFresh = application.fresh
try {
application.ctx.log.info(`Attempting to run version ${version.version}`)
await application.runVersion(version.version)
found = true
version.stable = 1
await this.db.write()
break
} catch(err) {
application.ctx.log.error(err, `Error starting ${version.version}: ${err.message}`)
process.off('exit', this._applicationFatalCrash)
if (version.stable < 1) {
if (wasFresh) {
version.stable = -2
} else {
version.stable = -1
}
await this.db.write()
if (version.stable === -1) {
return this.restart(`Application ${name} version ${version.version} failed to start but application was dirty, check if restarting fixes it`)
}
} else {
await this.db.write()
return this.restart(`Application ${name} version ${version.version} previously stable but now failing`)
}
} finally {
process.off('exit', this._applicationFatalCrash)
}
}
if (!found) {
return Promise.reject(Error(`No stable versions were found`))
}
}
}
Core.addProvider('static', StaticProvider)
Core.addProvider('git', GitProvider)