Compare commits

...

16 Commits

Author SHA1 Message Date
Jonatan Nilsson 6c720f4c2d Fix git integration test
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2024-02-16 06:52:50 +00:00
Jonatan Nilsson 14fead9c17 core: Update tests after moving version info
continuous-integration/appveyor/branch AppVeyor build failed Details
2024-02-16 06:50:34 +00:00
Jonatan Nilsson 6c115aa8b2 core: In application, move version into context
continuous-integration/appveyor/branch AppVeyor build failed Details
2024-02-16 06:48:29 +00:00
Jonatan Nilsson 5d39f776e1 git: Auto replace spaces with underscores in version output. Allows versions to have spaces in their name while keeping it unix friendly folder and file name.
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2022-08-18 10:54:38 +00:00
Jonatan Nilsson 79f3203f70 Fix integration test based on latest changes in git config loading
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2022-08-13 02:10:40 +00:00
Jonatan Nilsson e8a2ec52a6 git: Add support to filter based on specific prefix 2022-08-13 02:10:24 +00:00
Jonatan Nilsson 1995b38510 git: checkConfig no longer checks repo url. Git being down or latest version being unfetchable should not stop services from starting up.
continuous-integration/appveyor/branch AppVeyor build failed Details
Package: Remove beta flag, is stable enough for now
2022-08-13 01:48:05 +00:00
Jonatan Nilsson 63a06a2a34 git: checkConfig no longer checks repo url. Git being down or latest version being unfetchable should not stop services from starting up.
Package: Remove beta flag, is stable enough for now
2022-08-13 01:47:05 +00:00
Jonatan Nilsson cb5de72e13 db: Don't warn about clearing db when using in-memory db
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2022-04-19 16:37:45 +00:00
Jonatan Nilsson 33b3d98a37 application: Expose app config in ctx.config
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2022-04-02 20:10:09 +00:00
Jonatan Nilsson 618cfd0451 log: Workers in cluster will now properly notify master of newlog entries. Useful for sc-manager
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2022-04-01 09:59:47 +00:00
Jonatan Nilsson b5359515b5 lib: Fix so it displays the restart message during restart
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2022-03-30 08:19:49 +00:00
Jonatan Nilsson 23f9173720 test: Fix regex match error after some recent changes
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2022-03-29 20:38:59 +00:00
Jonatan Nilsson d519996959 Application: Add flag in application to keep track if its running and which version.
continuous-integration/appveyor/branch AppVeyor build failed Details
Lib: Add restart support to shut down server.
Runner: Add better logging when shut down request is sent.
Many: Add bunch of event emitters for interested parties
2022-03-29 17:14:50 +00:00
Jonatan Nilsson 90d4e7ab81 application: Emit event when updating changes
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2022-03-28 15:15:30 +00:00
Jonatan Nilsson a5239d0c27 db: Removed unused latestVersion
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2022-03-28 07:48:30 +00:00
17 changed files with 517 additions and 103 deletions

View File

@ -26,7 +26,8 @@ export default class Application extends EventEmitter {
getLog: getLog,
HttpServer: HttpServer,
request: request,
}
},
version: '',
}
this.config = defaults({}, this.ctx.db.config[name])
this.provider = provider
@ -34,7 +35,6 @@ export default class Application extends EventEmitter {
this.updating = false
this.http = new HttpServer(this.config)
this.module = null
this.running = false
this.workers = {}
// Fresh is used to indicate that when we run the application and it fails,
@ -55,6 +55,8 @@ export default class Application extends EventEmitter {
this.config.heartbeatPath = this.config.heartbeatPath || '/'
this.config.clusterWaitOnCrash = this.config.clusterWaitOnCrash || (1 * 1000)
this.ctx.config = this.config
Object.assign(this, {
setInterval: opts.setInterval || setInterval,
setTimeout: opts.setTimeout || setTimeout,
@ -98,7 +100,6 @@ export default class Application extends EventEmitter {
updateLog(message, level = 'info') {
this.ctx.db.data.core[this.name].updater += message
this.ctx.log[level](message)
this.emit('updatelog')
return message
}
@ -113,17 +114,22 @@ export default class Application extends EventEmitter {
if (this.ctx.db.data.core[this.name].updater !== this.msgStatic) {
this.ctx.db.data.core[this.name].updater = ''
this.updateLog(this.msgStatic)
this.emit('updatelog', this.msgStatic)
return this.ctx.db.write().then(function() { return null })
}
return Promise.resolve(null)
}
if (this.updating) return null
this.updating = true
this.emit('updating', this.updating)
return this._update()
.then((result) => {
this.updating = false
this.emit('updating', this.updating)
return this.ctx.db.write()
.then(function() { return result })
})
@ -159,6 +165,7 @@ export default class Application extends EventEmitter {
try {
log += this.updateLog(`Checking for latest version at ${new Date().toISOString().replace('T', ' ').split('.')[0]}. `, 'debug') + '\n'
this.emit('updatelog', log)
// Get the latest version from our provider
latest = await this.provider.getLatestVersion()
@ -166,7 +173,8 @@ export default class Application extends EventEmitter {
// If the versino matches the latest installed, then there's nothing to do
if (this.ctx.db.data.core[this.name].latestInstalled === latest.version) {
log += this.updateLog(`Found ${latest.version}. `, 'debug') + '\n'
this.updateLog('Already up to date, nothing to do. ', 'debug')
log += this.updateLog('Already up to date, nothing to do. ', 'debug')
this.emit('updatelog', log)
return null
}
@ -181,7 +189,8 @@ export default class Application extends EventEmitter {
if (found) {
// Check if the existing version found was already installed.
if (found.installed) {
this.updateLog('Version was already installed, nothing to do. ')
log += this.updateLog('Version was already installed, nothing to do. ')
this.emit('updatelog', log)
return null
}
@ -197,12 +206,14 @@ export default class Application extends EventEmitter {
// if so, we should skip them and consider those versions as black
// listed and avoid at all cost.
if (latest.failtodownload && latest.failtodownload > 3) {
this.updateLog('Version failed to download too many times, skipping this version. ')
log += this.updateLog('Version failed to download too many times, skipping this version. ')
this.emit('updatelog', log)
return null
}
if (latest.failtoinstall && latest.failtoinstall > 3) {
this.updateLog('Version failed to install too many times, skipping this version. ')
log += this.updateLog('Version failed to install too many times, skipping this version. ')
this.emit('updatelog', log)
return null
}
@ -213,7 +224,7 @@ export default class Application extends EventEmitter {
latest.stable = 0
this.ctx.db.upsertFirst(this.ctx.db.data.core[this.name].versions, latest)
}
this.emit('updatelog', log)
this.emit('update', latest)
// The target file for the archive and the target folder for new our version
@ -224,6 +235,7 @@ export default class Application extends EventEmitter {
await this.fs.mkdir(folder, { recursive: true })
log += this.updateLog(`Downloading ${latest.link} to ${target}. `) + '\n'
this.emit('updatelog', log)
await this.ctx.db.write()
// Download the latest version using the provider in question.
@ -234,11 +246,13 @@ export default class Application extends EventEmitter {
})
log += '\n' + this.updateLog(`Extracting ${target}. `) + '\n'
this.emit('updatelog', log)
await this.ctx.db.write()
// Download was successful, extract the archived file that we downloaded
await this.ctx.util.extractFile(target, function(msg) {
await this.ctx.util.extractFile(target, (msg) => {
log += msg
this.emit('updatelog', log)
}).catch(function(err) {
latest.failtodownload = (latest.failtodownload || 0) + 1
return Promise.reject(err)
@ -258,6 +272,7 @@ export default class Application extends EventEmitter {
.catch((err) => {
latest.failtodownload = (latest.failtodownload || 0) + 1
log += this.updateLog('Version did not include or was missing index.mjs. ') + '\n'
this.emit('updatelog', log)
return Promise.reject(err)
})
@ -275,6 +290,7 @@ export default class Application extends EventEmitter {
if (packageStat) {
log += this.updateLog(`running npm install --production. `) + '\n'
this.emit('updatelog', log)
await this.ctx.db.write()
// For some weird reason, --loglevel=notice is required otherwise
@ -283,15 +299,20 @@ export default class Application extends EventEmitter {
this.ctx.util.getNpmExecutable(),
['install', '--production', '--no-optional', '--no-package-lock', '--no-audit', '--loglevel=notice'],
folder,
function(msg) { log += msg }
(msg) => {
log += msg
this.emit('updatelog', log)
}
).catch(function(err) {
latest.failtoinstall = (latest.failtoinstall || 0) + 1
return Promise.reject(err)
})
log = this.logAddSeperator(log)
this.emit('updatelog', log)
} else {
log += this.updateLog('Release did not contain package.json, skipping npm install. ') + '\n'
this.emit('updatelog', log)
}
} catch (err) {
log += this.updateLog(`Error: ${err.message}. `) + '\n'
@ -307,12 +328,14 @@ export default class Application extends EventEmitter {
if (latest) {
latest.log = log
}
this.emit('updatelog', log)
return Promise.reject(err)
}
// If we reached here then everything went swimmingly. Mark the version
// as being installed and attach the install log to it.
log += this.updateLog(`Finished updating ${this.name} to version ${latest.version}.`) + '\n'
this.emit('updatelog', log)
this.ctx.db.data.core[this.name].latestInstalled = latest.version
latest.installed = true
latest.log = log
@ -365,7 +388,22 @@ export default class Application extends EventEmitter {
this.workers[i].started = new Date()
}
async runVersion(version) {
runVersion(version) {
this.ctx.db.data.core[this.name].active = version
this.ctx.version = version
this.emit('running', this.ctx.version)
return this.ctx.db.write().then(() => {
return this._runVersion(version)
.catch((err) => {
this.ctx.version = ''
this.emit('running', this.ctx.version)
return Promise.reject(err)
})
})
}
async _runVersion(version) {
this.ctx.db.data.core[this.name].active = version
await this.ctx.db.write()
@ -439,6 +477,8 @@ export default class Application extends EventEmitter {
}
closeServer() {
this.ctx.version = ''
this.emit('running', this.ctx.version)
if (this.config.cluster && !this.isSlave) {
if (this.__clusterWorkerDied) {
this.cluster.off('exit', this.__clusterWorkerDied)

View File

@ -61,6 +61,8 @@ export default class Core {
this.log.info(`Found applications: ${names.join(', ')}.`)
let hasCluster = false
for (let name of names) {
if (this.isSlave && process.env.CLUSTER_APP_NAME !== name) {
continue
@ -82,17 +84,51 @@ export default class Core {
let application = new Application({
db: this.db,
util: this.util,
log: getLog(logName, this.db.config[name].log || null),
log: getLog(logName, this.db.config[name].log || null, { name: name }),
core: this,
}, provider, name)
this.applications.push(application)
this.applicationMap.set(name, application)
if (this.db.config[name].cluster) {
hasCluster = true
}
} catch (err) {
this.log.error(err, `Error creating application ${name} with provider ${this.db.config[name].provider}: ${err.message}`)
this.log.error(err, `Error creating application ${name} with provider ${this.db.config[name].provider} with config ${JSON.stringify(this.db.config[name])}: ${err.message}`)
}
}
if (hasCluster && !this.isSlave) {
cluster.on('message', (worker, message) => {
// Some sanity checking
if (!message
|| typeof(message) !== 'object'
|| typeof(message.apptarget) !== 'string'
|| typeof(message.type) !== 'string'
|| typeof(message.payload) !== 'object'
|| !message.payload
) {
return
}
let app = this.getApplication(message.apptarget)
let targetLog = null
if (app) {
targetLog = app.ctx.log
} else if (message.apptarget === this.db.config.name) {
targetLog = this.log
}
if (!targetLog) return
if (message.type === 'newlog') {
targetLog.emit('newlog', message.payload)
}
})
}
if (names.length && !this.applications.length) {
this.log.error('None of the application were successful in running')
return Promise.reject(new Error('None of the application were successful in running'))
}
}

View File

@ -89,7 +89,6 @@ export default function GetDB(config, log, orgFilename = 'db.json') {
defaults(db.data.core[name], {
active: '',
latestInstalled: '',
latestVersion: '',
updater: '',
versions: [],
})
@ -123,7 +122,9 @@ export default function GetDB(config, log, orgFilename = 'db.json') {
return db.read()
.then(function() {
if (!isObject(db.data)) {
db.log.warn(`File ${fullpath} was empty or not a json object, clearing it.`)
if (fullpath !== 'in-memory') {
db.log.warn(`File ${fullpath} was empty or not a json object, clearing it.`)
}
db.data = {}
}
defaults(db.data, { core: { version: 1 } })

View File

@ -37,7 +37,11 @@ export default class ServiceCore {
async init(module = null) {
this.db = await GetDB(this.config, this.log, this.dbfilename)
this.core = new Core(this.db, this.util, this.log)
this.core = new Core(this.db, this.util, this.log, (msg) => {
let err = new Error('Got request to restart' + (msg ? ': ' + msg : ''))
this.log.fatal(err)
process.exit(0)
})
let provider = new StaticProvider()
this.app = new Application({

View File

@ -1,4 +1,5 @@
// import nodewindows from 'node-windows'
import cluster from 'cluster'
import bunyan from 'bunyan-lite'
import { setTimeout } from 'timers/promises'
@ -54,6 +55,14 @@ export default function getLog(name, streams = null, opts = {}) {
stream: {
write: function(record) {
logger.emit('newlog', record)
if (cluster.isWorker) {
process.send({
apptarget: opts.name || name,
type: 'newlog',
payload: record,
})
}
},
end: function() {},
destroy: function() {},

View File

@ -26,9 +26,10 @@ export default class GitProvider {
for (let asset of item.assets) {
if (!asset.name.endsWith('-sc.7z')) continue
if (this.config.git_required_prefix && !asset.name.startsWith(this.config.git_required_prefix)) continue
return {
version: item.name,
version: item.name.replace(/ /g, '_'),
link: asset.browser_download_url,
filename: asset.name,
description: item.body,
@ -55,10 +56,5 @@ export default class GitProvider {
if (typeof(this.config.url) !== 'string') return Promise.reject(new Error('url was not a valid url'))
try { new URL(this.config.url) }
catch (err) { return Promise.reject(new Error('url was not a valid url: ' + err.message)) }
return this.getLatestVersion()
.catch(function(err) {
return Promise.reject(new Error(`Error fetching latest release: ${err.message}`))
})
}
}

View File

@ -34,12 +34,9 @@ export async function runner(root_import_meta_url, configname = 'config.json', d
const db = await GetDB(config, log, util.getPathFromRoot('./' + dbname))
const core = new Core(db, util, log, function(msg) {
if (msg) {
runner.log.warn('Got a restart request: ' + msg)
} else {
runner.log.warn('Got a restart request with no message or reason')
}
process.exit(1)
let err = new Error('Got request to restart' + (msg ? ': ' + msg : ''))
runner.log.fatal(err)
process.exit(0)
})
await core.init()
await core.run()

View File

@ -1,6 +1,6 @@
{
"name": "service-core",
"version": "3.0.0-beta.12",
"version": "3.0.2",
"description": "Core boiler plate code to install node server as windows service",
"main": "index.mjs",
"scripts": {

View File

@ -48,9 +48,11 @@ t.describe('#runVersion("static")', function() {
assert.strictEqual(checkCtx.app, app)
})
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(app.fresh, true)
let err = await assert.isRejected(app.runVersion('static'))
assert.strictEqual(app.fresh, false)
assert.strictEqual(app.ctx.version, '')
assert.match(err.message, /http/i)
assert.match(err.message, /createServer/i)
@ -62,9 +64,11 @@ t.describe('#runVersion("static")', function() {
app.config.startWaitUntilFail = 50
app.registerModule(function() { return new Promise(function() {}) })
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(app.fresh, true)
let err = await assert.isRejected(app.runVersion('static'))
assert.strictEqual(app.fresh, false)
assert.strictEqual(app.ctx.version, '')
assert.match(err.message, /time/i)
assert.match(err.message, /out/i)
@ -83,9 +87,11 @@ t.describe('#runVersion("static")', function() {
})
})
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(app.fresh, true)
await app.runVersion('static')
assert.strictEqual(app.fresh, false)
assert.strictEqual(app.ctx.version, 'static')
assert.strictEqual(ctx.db.data.core.testapp.active, 'static')
})
@ -101,9 +107,11 @@ t.describe('#runVersion("static")', function() {
app.config.heartbeatAttemptsWait = 5
app.registerModule(defaultHandler(handler))
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(app.fresh, true)
let err = await assert.isRejected(app.runVersion('static'))
assert.strictEqual(app.fresh, false)
assert.strictEqual(app.ctx.version, '')
assert.match(err.message, /failed/i)
assert.match(err.message, /400/i)
@ -121,11 +129,13 @@ t.describe('#runVersion("static")', function() {
app.config.heartbeatAttemptsWait = 10
app.registerModule(defaultHandler(handler))
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(app.fresh, true)
let start = performance.now()
let err = await assert.isRejected(app.runVersion('static'))
let end = performance.now()
assert.strictEqual(app.fresh, false)
assert.strictEqual(app.ctx.version, '')
assert.match(err.message, /failed/i)
assert.match(err.message, /time/i)
@ -150,9 +160,11 @@ t.describe('#runVersion("static")', function() {
app.config.heartbeatAttemptsWait = 5
app.registerModule(defaultHandler(handler))
assert.strictEqual(app.ctx.version, '')
let err = await assert.isRejected(app.runVersion('static'))
assert.match(err.message, /failed/i)
assert.match(err.message, /400/i)
assert.strictEqual(app.ctx.version, '')
await app.closeServer()
app.registerModule(defaultHandler(handler))
@ -173,9 +185,11 @@ t.describe('#runVersion("static")', function() {
app.registerModule(defaultHandler(handler))
app.isSlave = true
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(app.fresh, true)
await app.runVersion('static')
assert.strictEqual(app.fresh, false)
assert.strictEqual(app.ctx.version, 'static')
assert.strictEqual(called, 0)
assert.strictEqual(ctx.db.data.core.testapp.active, 'static')
@ -226,9 +240,11 @@ t.describe('#runVersion("version")', function() {
app.config.port = assertPort
stubFsStat.rejects(assertNotError)
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(app.fresh, true)
let err = await assert.isRejected(app.runVersion('v100'))
assert.strictEqual(app.fresh, true)
assert.strictEqual(app.ctx.version, '')
assert.notStrictEqual(err, assertNotError)
assert.match(err.message, new RegExp(assertNotError.message))
@ -246,9 +262,11 @@ t.describe('#runVersion("version")', function() {
await fs.mkdir(util.getPathFromRoot('./testnoexisting/v99'), { recursive: true })
await fs.writeFile(util.getPathFromRoot('./testnoexisting/v99/index.mjs'), `throw new Error('${assertError.message}')`)
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(app.fresh, true)
let err = await assert.isRejected(app.runVersion('v99'))
assert.strictEqual(app.fresh, false)
assert.strictEqual(app.ctx.version, '')
assert.notStrictEqual(err, assertError)
assert.strictEqual(err.message, assertError.message)
@ -262,10 +280,12 @@ t.describe('#runVersion("version")', function() {
await fs.mkdir(util.getPathFromRoot('./testnoexisting/v98'), { recursive: true })
await fs.writeFile(util.getPathFromRoot('./testnoexisting/v98/index.mjs'), ``)
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(app.fresh, true)
let err = await assert.isRejected(app.runVersion('v98'))
assert.strictEqual(app.fresh, false)
assert.match(err.message, /start/i)
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(app.ctx.db.data.core.testnoexisting.active, 'v98')
let checkDb = await lowdb({}, ctx.log, assertConfig)
@ -282,9 +302,11 @@ t.describe('#runVersion("version")', function() {
app.ctx.log.info.reset()
app.ctx.log.event.info.reset()
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(app.fresh, true)
await app.runVersion('v97')
assert.strictEqual(app.fresh, false)
assert.strictEqual(app.ctx.version, 'v97')
assert.ok(app.ctx.log.info.called)
assert.ok(app.ctx.log.event.info.called)

View File

@ -42,7 +42,6 @@ t.describe('constructor()', function() {
assert.ok(ctx.db.data.core.test.versions)
assert.strictEqual(ctx.db.data.core.test.active, '')
assert.strictEqual(ctx.db.data.core.test.latestInstalled, '')
assert.strictEqual(ctx.db.data.core.test.latestVersion, '')
})
t.test('should keep config and other of itself', function() {
@ -66,6 +65,7 @@ t.describe('constructor()', function() {
assert.strictEqual(app.config.clusterWaitOnCrash, 1 * 1000)
assert.strictEqual(app.ctx.db, ctx.db)
assert.strictEqual(app.ctx.app, app)
assert.strictEqual(app.ctx.config, app.config)
assert.strictEqual(app.ctx.util, ctx.util)
assert.strictEqual(app.ctx.sc.Util, Util)
assert.strictEqual(app.ctx.sc.bunyan, bunyan)
@ -74,7 +74,7 @@ t.describe('constructor()', function() {
assert.strictEqual(app.ctx.sc.getLog, getLog)
assert.strictEqual(app.name, assertName)
assert.strictEqual(app.fresh, true)
assert.strictEqual(app.running, false)
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(app.monitoringCluster, false)
assert.deepStrictEqual(app.workers, {})
assert.strictEqual(app.isSlave, false)
@ -329,10 +329,13 @@ t.describe('#closeServer()', function() {
})
t.test('should call closeServer correctly', async function() {
const assertNotVersion = 'v1521'
const assertError = new Error('Moonlight Fiesta')
stubCloseServer.rejects(assertError)
app.ctx.version = assertNotVersion
let err = await assert.isRejected(app.closeServer())
assert.strictEqual(app.ctx.version, '')
assert.strictEqual(err, assertError)
})

View File

@ -166,7 +166,9 @@ runners.forEach(function([runnerName, appname]) {
function parseLine(line) {
if (line[0] === '{') {
return JSON.parse(line)
try {
return JSON.parse(line)
} catch {}
}
return {
msg: line
@ -187,6 +189,24 @@ runners.forEach(function([runnerName, appname]) {
return setTimeout(ms)
}
}
async function safeTry(func) {
let lastException = null
for (let i = 0; i < 3; i++) {
if (i > 0) {
allLogs.push('[safeTry] Failed with error ' + lastException.message + ', trying agian')
await setTimeout(500)
}
try {
await func()
return
}
catch (err) {
lastException = err
}
}
throw lastException
}
integrationLog.on('newlog', function(record) {
allLogs.push(JSON.stringify(record))
@ -275,6 +295,7 @@ runners.forEach(function([runnerName, appname]) {
}
t.test('should be fully operational', async function() {
let db;
console.log()
if (!turnDebuggingOn) { console.log('Running empty test') }
@ -293,12 +314,13 @@ runners.forEach(function([runnerName, appname]) {
let secondLast = parseLine(logs[logs.length - 2])
let last = parseLine(logs[logs.length - 1])
assert.match(secondLast.msg, /creating/i)
assert.match(secondLast.msg, /application/i)
assert.match(secondLast.msg, /testapp/i)
assert.match(secondLast.msg, /0 releases/i)
assert.match(last.err.message, /none/i)
assert.match(last.err.message, /successful/i)
assert.match(secondLast.msg, /No/i)
assert.match(secondLast.msg, /versions/i)
assert.match(secondLast.msg, /found/i)
assert.match(last.msg, /starting/i)
assert.match(last.msg, /runner/i)
assert.match(last.err.message, /stable/i)
assert.match(last.err.message, /application/i)
// Reset our log
logs.splice(0, logs.length); logIndex = 0; logWaitIndex = 0;
@ -329,11 +351,13 @@ runners.forEach(function([runnerName, appname]) {
catchupLog()
let db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion1)
assert.strictEqual(db.core[appname].versions.length, 1)
assert.strictEqual(db.core[appname].versions[0].stable, 1)
assert.strictEqual(db.core[appname].versions[0].installed, true)
await safeTry(async function() {
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion1)
assert.strictEqual(db.core[appname].versions.length, 1)
assert.strictEqual(db.core[appname].versions[0].stable, 1)
assert.strictEqual(db.core[appname].versions[0].installed, true)
})
// Create our second version
await fs.writeFile(index, version_2_nolisten)
@ -353,7 +377,7 @@ runners.forEach(function([runnerName, appname]) {
}
if (appname !== 'testappcluster') {
while (!hasLogLine(/restart request.*v2_nolisten.*dirty/)) {
while (!hasLogLine(/restart.*v2_nolisten.*dirty/)) {
await catchupLog(10)
}
@ -362,13 +386,15 @@ runners.forEach(function([runnerName, appname]) {
}
catchupLog()
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion2)
assert.strictEqual(db.core[appname].versions.length, 2)
assert.strictEqual(db.core[appname].versions[0].stable, -1)
assert.strictEqual(db.core[appname].versions[0].installed, true)
assert.strictEqual(db.core[appname].versions[1].stable, 1)
assert.strictEqual(db.core[appname].versions[1].installed, true)
await safeTry(async function() {
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion2)
assert.strictEqual(db.core[appname].versions.length, 2)
assert.strictEqual(db.core[appname].versions[0].stable, -1)
assert.strictEqual(db.core[appname].versions[0].installed, true)
assert.strictEqual(db.core[appname].versions[1].stable, 1)
assert.strictEqual(db.core[appname].versions[1].installed, true)
})
// Since application was in dirty state, on next attempt should attempt to
// run v2 again and then falling back to v1
@ -397,11 +423,13 @@ runners.forEach(function([runnerName, appname]) {
await setTimeout(10)
}
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion1)
assert.strictEqual(db.core[appname].versions.length, 2)
assert.strictEqual(db.core[appname].versions[0].stable, -2)
assert.strictEqual(db.core[appname].versions[1].stable, 1)
await safeTry(async function() {
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion1)
assert.strictEqual(db.core[appname].versions.length, 2)
assert.strictEqual(db.core[appname].versions[0].stable, -2)
assert.strictEqual(db.core[appname].versions[1].stable, 1)
})
assert.ok(findInLogs(/Attempting to run version v2_nolisten/))
assert.ok(findInLogs(/Error starting v2_nolisten/))
@ -424,11 +452,13 @@ runners.forEach(function([runnerName, appname]) {
await setTimeout(10)
}
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion1)
assert.strictEqual(db.core[appname].versions.length, 2)
assert.strictEqual(db.core[appname].versions[0].stable, -2)
assert.strictEqual(db.core[appname].versions[1].stable, 1)
await safeTry(async function() {
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion1)
assert.strictEqual(db.core[appname].versions.length, 2)
assert.strictEqual(db.core[appname].versions[0].stable, -2)
assert.strictEqual(db.core[appname].versions[1].stable, 1)
})
assert.notOk(findInLogs(/Attempting to run version v2_nolisten/))
assert.notOk(findInLogs(/Error starting v2_nolisten/))
@ -455,12 +485,14 @@ runners.forEach(function([runnerName, appname]) {
await catchupLog(10)
}
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion3)
assert.strictEqual(db.core[appname].versions.length, 3)
assert.strictEqual(db.core[appname].versions[0].stable, -2)
assert.strictEqual(db.core[appname].versions[1].stable, -2)
assert.strictEqual(db.core[appname].versions[2].stable, 1)
await safeTry(async function() {
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion3)
assert.strictEqual(db.core[appname].versions.length, 3)
assert.strictEqual(db.core[appname].versions[0].stable, -2)
assert.strictEqual(db.core[appname].versions[1].stable, -2)
assert.strictEqual(db.core[appname].versions[2].stable, 1)
})
catchupLog()
@ -486,12 +518,14 @@ runners.forEach(function([runnerName, appname]) {
await setTimeout(10)
}
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion1)
assert.strictEqual(db.core[appname].versions.length, 3)
assert.strictEqual(db.core[appname].versions[0].stable, -2)
assert.strictEqual(db.core[appname].versions[1].stable, -2)
assert.strictEqual(db.core[appname].versions[2].stable, 1)
await safeTry(async function() {
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion1)
assert.strictEqual(db.core[appname].versions.length, 3)
assert.strictEqual(db.core[appname].versions[0].stable, -2)
assert.strictEqual(db.core[appname].versions[1].stable, -2)
assert.strictEqual(db.core[appname].versions[2].stable, 1)
})
}
// Create our fourth version
@ -525,11 +559,32 @@ runners.forEach(function([runnerName, appname]) {
await setTimeout(10)
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion4)
assert.strictEqual(db.core[appname].versions.length, 4)
assert.strictEqual(db.core[appname].versions[0].stable, 1)
assert.strictEqual(db.core[appname].versions[1].stable, -2)
await safeTry(async function() {
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core[appname].active, assertNameVersion4)
assert.strictEqual(db.core[appname].versions.length, 4)
assert.strictEqual(db.core[appname].versions[0].stable, 1)
assert.strictEqual(db.core[appname].versions[1].stable, -2)
})
if (appname === 'testappcluster') {
let foundCore = false
let foundWorker = false
for (let line of allLogs) {
if (line.startsWith('[FROMWORKERCORE] test-runner-cluster')) {
foundCore = true
}
else if (line.startsWith('[FROMWORKERAPP] testappcluster-1')) {
foundWorker = true
}
if (foundCore && foundWorker) {
break
}
}
assert.ok(foundCore)
assert.ok(foundWorker)
}
wasSuccessful = true
})
})

View File

@ -1,3 +1,4 @@
import cluster from 'cluster'
import { Eltro as t, assert, stub } from 'eltro'
import fs from 'fs/promises'
import Core from '../core/core.mjs'
@ -439,6 +440,108 @@ t.describe('#init()', function() {
assert.strictEqual(application.ctx.log.streams[1].level, 40)
assert.strictEqual(application.ctx.log.streams[1].type, 'stream')
})
t.test('should listen on cluster messages if one or more are cluster on', async function() {
const assertAppName1 = 'Dai Sagara Yoshiharu'
const assertAppName2 = 'Kuryo'
const assertCoreName = 'Ichuu'
const assertPayload1 = { a: 1 }
const assertPayload2 = { b: 2 }
const assertConfig = {
name: assertCoreName,
[assertAppName1]: {
provider: assertProviderName,
cluster: 2,
},
[assertAppName2]: {
provider: assertProviderName,
cluster: 1,
},
}
db.config = assertConfig
fakeUtil.getAppNames.returns([assertAppName1, assertAppName2])
assert.strictEqual(core.applications.length, 0)
await core.init()
core.log.emit = stub()
assert.strictEqual(core.applications.length, 2)
let app1 = core.getApplication(assertAppName1)
let app2 = core.getApplication(assertAppName2)
app1.ctx.log.emit = stub()
app2.ctx.log.emit = stub()
cluster.emit('message', null, {
apptarget: app1.name,
type: 'newlog',
payload: assertPayload1
})
assert.notOk(core.log.emit.called)
assert.ok(app1.ctx.log.emit.called)
assert.ok(app1.ctx.log.emit.firstCall[0], 'newlog')
assert.ok(app1.ctx.log.emit.firstCall[1], assertPayload1)
assert.notOk(app2.ctx.log.emit.called)
app1.ctx.log.emit.reset()
cluster.emit('message', null, {
apptarget: app2.name,
type: 'newlog',
payload: assertPayload2
})
assert.notOk(core.log.emit.called)
assert.notOk(app1.ctx.log.emit.called)
assert.ok(app2.ctx.log.emit.called)
assert.ok(app2.ctx.log.emit.firstCall[0], 'newlog')
assert.ok(app2.ctx.log.emit.firstCall[1], assertPayload2)
app2.ctx.log.emit.reset()
let tests = [
null,
undefined,
12412,
'asdfag',
{},
{ apptarget: 12421, type: 'newlog', payload: {}},
{ apptarget: {}, type: 'newlog', payload: {}},
{ apptarget: null, type: 'newlog', payload: {}},
{ type: 'newlog', payload: {}},
{ apptarget: app1.name, type: 12421, payload: {}},
{ apptarget: app1.name, type: {}, payload: {}},
{ apptarget: app1.name, type: null, payload: {}},
{ apptarget: app1.name, payload: {}},
{ apptarget: app1.name, type: 'newlog', payload: 12421},
{ apptarget: app1.name, type: 'newlog', payload: null},
{ apptarget: app1.name, type: 'newlog', payload: 'test'},
]
tests.forEach(function(test) {
cluster.emit('message', null, test)
assert.notOk(core.log.emit.called)
assert.notOk(app1.ctx.log.emit.called)
assert.notOk(app2.ctx.log.emit.called)
})
cluster.emit('message', null, {
apptarget: assertCoreName,
type: 'newlog',
payload: assertPayload1
})
assert.notOk(app1.ctx.log.emit.called)
assert.notOk(app2.ctx.log.emit.called)
assert.ok(core.log.emit.called)
assert.ok(core.log.emit.called)
assert.ok(core.log.emit.firstCall[0], 'newlog')
assert.ok(core.log.emit.firstCall[1], assertPayload1)
})
})
t.describe('#run()', function() {

View File

@ -150,7 +150,6 @@ t.test('Should support adding an application with defaults', async function() {
assert.ok(db.data.core.app.versions)
assert.strictEqual(db.data.core.app.active, '')
assert.strictEqual(db.data.core.app.latestInstalled, '')
assert.strictEqual(db.data.core.app.latestVersion, '')
assert.notOk(db.data.core.herpderp)
@ -160,7 +159,6 @@ t.test('Should support adding an application with defaults', async function() {
assert.ok(db.data.core.herpderp.versions)
assert.strictEqual(db.data.core.herpderp.active, '')
assert.strictEqual(db.data.core.herpderp.latestInstalled, '')
assert.strictEqual(db.data.core.herpderp.latestVersion, '')
})
t.test('Should support reading from db', async function() {

View File

@ -86,7 +86,7 @@ export function prettyPrintMessage(line) {
console.log(err)
}
return
} catch (err){ console.log(err)}
} catch { }
}
console.log(line)
}

View File

@ -16,28 +16,26 @@ t.timeout(5000).describe('#getLatestVersion()', function() {
assert.ok(version.version)
assert.ok(version.description)
assert.ok(version.link)
assert.match(version.link, /\/attachments\//)
assert.match(version.link, /\/download\//)
})
})
t.timeout(5000).describe('#checkConfig()', function() {
t.test('should fail if link does not return json repository object', async function() {
let err = await assert.isRejected(new GitProvider({ url: 'http://git.nfp.is/api/v1/repos/thething/ProgramQueuer' }).checkConfig())
let err = await assert.isRejected(new GitProvider({ url: 'http://git.nfp.is/api/v1/repos/thething/ProgramQueuer' }).getLatestVersion())
assert.match(err.message, /valid/i)
assert.match(err.message, /repository/i)
err = await assert.isRejected(new GitProvider({ url: 'http://git.nfp.is/api/v1/orgs/nfp/repos' }).checkConfig())
err = await assert.isRejected(new GitProvider({ url: 'http://git.nfp.is/api/v1/orgs/nfp/repos' }).getLatestVersion())
assert.match(err.message, /service-core/i)
assert.match(err.message, /release/i)
})
t.test('should fail if no active release repository with assets', async function() {
let err = await assert.isRejected(new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/thething/eltro/releases' }).checkConfig())
let err = await assert.isRejected(new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/thething/eltro/releases' }).getLatestVersion())
assert.match(err.message, /service-core/i)
assert.match(err.message, /release/i)
})
t.test('should fail on private repositories', async function() {
let err = await assert.isRejected(new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/TheThing/privateexample/releases' }).checkConfig())
let err = await assert.isRejected(new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/TheThing/privateexample/releases' }).getLatestVersion())
assert.match(err.message, /fail/i)
assert.match(err.message, /404/i)
assert.match(err.message, /release/i)
@ -45,19 +43,19 @@ t.timeout(5000).describe('#checkConfig()', function() {
t.test('should otherwise succeed', function() {
return new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/TheThing/sc-helloworld/releases' })
.checkConfig()
.getLatestVersion()
})
let test = t
if (!process.env.gittesttoken) {
console.log('Skipping "git.test.integration: #checkConfig() should succeed on private repo with token"')
console.log('Skipping "git.test.integration: #getLatestVersion() should succeed on private repo with token"')
test = test.skip()
}
test.test('should succeed on private repo with token', function() {
return new GitProvider({
token: process.env.gittesttoken.trim(),
url: 'https://git.nfp.is/api/v1/repos/TheThing/privateexample/releases',
}).checkConfig()
}).getLatestVersion()
})
})

View File

@ -20,6 +20,25 @@ t.describe('#getLatestVersion()', function() {
assert.strictEqual(version.log, '')
})
t.test('should auto replace spaces with underscores result', async function() {
const assertOriginalName = 'The Smell Of Sea'
const assertCorrectName = 'The_Smell_Of_Sea'
const assertLink = 'Over The Future'
const assertFilename = 'test-sc.7z'
let stubber = stub()
let provider = new GitProvider({}, stubber)
stubber.resolves({ body: [
{ name: assertOriginalName, assets: [{ name: assertFilename, browser_download_url: assertLink }] },
]})
let version = await provider.getLatestVersion()
assert.strictEqual(version.version, assertCorrectName)
assert.strictEqual(version.link, assertLink)
assert.strictEqual(version.filename, assertFilename)
assert.strictEqual(version.log, '')
})
t.test('should skip zip files for now', async function() {
const assertName = 'Karen'
const assertLink = 'My Wings'
@ -108,6 +127,135 @@ t.describe('#getLatestVersion()', function() {
assert.match(err.message, /found/)
})
// --
t.test('should return correct name and link in result when git_required_prefix is specified', async function() {
const assertName = 'Karen'
const assertLink = 'Over The Future'
const assertFilename = 'gittest_test-sc.7z'
let stubber = stub()
let provider = new GitProvider({ git_required_prefix: 'gittest' }, stubber)
stubber.resolves({ body: [
{ name: assertName, assets: [{ name: assertFilename, browser_download_url: assertLink }] },
]})
let version = await provider.getLatestVersion()
assert.strictEqual(version.version, assertName)
assert.strictEqual(version.link, assertLink)
assert.strictEqual(version.filename, assertFilename)
assert.strictEqual(version.log, '')
})
t.test('should skip zip files for now even when git_required_prefix is specified', async function() {
const assertName = 'Karen'
const assertLink = 'My Wings'
const assertFilename = 'gittest_test-sc.zip'
let stubber = stub()
let provider = new GitProvider({ git_required_prefix: 'gittest' }, stubber)
stubber.resolves({ body: [
{ name: assertName, assets: [{ name: assertFilename, browser_download_url: assertLink }] },
]})
let err = await assert.isRejected(provider.getLatestVersion())
assert.match(err.message, /release/)
assert.match(err.message, /found/)
})
t.test('should skip versions with missing git_required_prefix prefix', async function() {
const assertName = 'name1'
const assertLink = 'somelink'
const assertFilename = 'gittest_something-sc.7z'
let stubber = stub()
let provider = new GitProvider({ git_required_prefix: 'gittest' }, stubber)
stubber.resolves({ body: [
{ name: 'test', assets: [] },
{ name: assertName, assets: [{ name: 'something-sc.7z', browser_download_url: 'nope' }] },
{ name: assertName, assets: [{ name: assertFilename, browser_download_url: assertLink }] },
]})
let version = await provider.getLatestVersion()
assert.strictEqual(version.version, assertName)
assert.strictEqual(version.link, assertLink)
assert.strictEqual(version.filename, assertFilename)
})
t.test('should skip versions with non-sc, non-git_required_prefix filename', async function() {
const assertName = 'name2'
const assertLink = 'somelink2'
const assertFilename = 'gittest_something-sc.7z'
let stubber = stub()
let provider = new GitProvider({ git_required_prefix: 'gittest' }, stubber)
stubber.resolves({ body: [
{ name: 'test', assets: [{ name: 'nope.7z', browser_download_url: 'nope' }] },
{ name: 'test', assets: [{ name: 'gittest_nope.7z', browser_download_url: 'nope' }] },
{ name: assertName, assets: [{ name: assertFilename, browser_download_url: assertLink }] },
]})
let version = await provider.getLatestVersion()
assert.strictEqual(version.version, assertName)
assert.strictEqual(version.link, assertLink)
assert.strictEqual(version.filename, assertFilename)
})
t.test('should skip assets with non-sc filename and non-git_required_prefix', async function() {
const assertName = 'name3'
const assertLink = 'somelink3'
const assertFilename = 'gittest_something-sc.7z'
let stubber = stub()
let provider = new GitProvider({ git_required_prefix: 'gittest' }, stubber)
stubber.resolves({ body: [
{ name: 'test', assets: [{ name: 'gittest_nope.7z', browser_download_url: 'nope' }] },
{ name: assertName, assets: [
{ name: 'nope.7z', browser_download_url: 'nope' },
{ name: 'gittest_nope.7z', browser_download_url: 'nope' },
{ name: 'something-sc.7z', browser_download_url: 'nope' },
{ name: assertFilename, browser_download_url: assertLink },
] },
]})
let version = await provider.getLatestVersion()
assert.strictEqual(version.version, assertName)
assert.strictEqual(version.link, assertLink)
assert.strictEqual(version.filename, assertFilename)
})
t.test('should otherwise reject non-git_required_prefix files', async function() {
let stubber = stub()
let provider = new GitProvider({ git_required_prefix: 'gittest' }, stubber)
stubber.resolves({ body: [
{ name: 'test', assets: [{ name: 'nope.7z', browser_download_url: 'nope' }] },
{ name: 'test2', assets: [{ name: 'nope2.7z', browser_download_url: 'nope' },] },
{ name: 'test3', assets: [{ name: 'nope2.7z', browser_download_url: 'nope' },] },
{ name: 'test3', assets: [{ name: 'something-sc.7z', browser_download_url: 'nope' },] },
]})
let err = await assert.isRejected(provider.getLatestVersion())
assert.match(err.message, /release/)
assert.match(err.message, /found/)
})
t.test('should otherwise reject', async function() {
let stubber = stub()
let provider = new GitProvider({}, stubber)
stubber.resolves({ body: [
{ name: 'test', assets: [{ name: 'nope.7z', browser_download_url: 'nope' }] },
{ name: 'test2', assets: [{ name: 'nope2.7z', browser_download_url: 'nope' },] },
{ name: 'test3', assets: [{ name: 'nope2.7z', browser_download_url: 'nope' },] },
]})
let err = await assert.isRejected(provider.getLatestVersion())
assert.match(err.message, /release/)
assert.match(err.message, /found/)
})
t.test('should reject if not valid body', async function() {
let provider = new GitProvider({}, stub())
@ -190,19 +338,10 @@ t.describe('#checkConfig()', function() {
}
})
t.test('should call requester with correct config and url', async function() {
const assertError = new Error('Toki wo Koeta Yoru')
const assertRequestConfig = { a: 1 }
t.test('should not call requester with correct config and url', async function() {
const assertUrl = 'http://test'
let provider = new GitProvider({ url: assertUrl }, stub())
provider.requestConfig = assertRequestConfig
provider.requester.rejects(assertError)
let err = await assert.isRejected(provider.checkConfig())
assert.notStrictEqual(err, assertError)
assert.match(err.message, new RegExp(assertError.message))
assert.strict(provider.requester.firstCall[0], assertRequestConfig)
assert.strict(provider.requester.firstCall[1], assertUrl)
let err = await provider.checkConfig()
assert.notOk(provider.requester.called)
})
})

View File

@ -27,6 +27,19 @@ runner(import.meta.url, {
}, 'db.json')
.then(
function(core) {
if (cluster.isPrimary) {
let app = core.applications[0]
app.ctx.log.on('newlog', function(record) {
if (record.name !== app.name) {
console.log(`[FROMWORKERAPP] ${record.name} (${record.pid}) ${record.msg}`)
}
})
core.log.on('newlog', function(record) {
if (record.pid !== process.pid) {
console.log(`[FROMWORKERCORE] ${record.name} (${record.pid}) ${record.msg}`)
}
})
}
core.log.info('core is running')
},
function(err) {