2015-04-10 19:23:52 +00:00
|
|
|
'use strict';
|
|
|
|
|
2015-07-26 16:59:57 +00:00
|
|
|
var env = process.env.NODE_ENV || 'development',
|
|
|
|
db = require('./db'),
|
2016-01-09 11:31:36 +00:00
|
|
|
httpServer = require('./lib/httpServer'),
|
2015-05-12 21:07:03 +00:00
|
|
|
path = require('path'),
|
2015-05-18 18:25:08 +00:00
|
|
|
fs = require('fs'),
|
|
|
|
Steppy = require('twostep').Steppy,
|
|
|
|
_ = require('underscore'),
|
2016-01-10 13:55:57 +00:00
|
|
|
Reader = require('./lib/reader').Reader,
|
2016-01-10 16:37:19 +00:00
|
|
|
Notifier = require('./lib/notifier').Notifier,
|
2016-01-05 11:18:20 +00:00
|
|
|
ProjectsCollection = require('./lib/project').ProjectsCollection,
|
2016-01-06 20:24:41 +00:00
|
|
|
BuildsCollection = require('./lib/build').BuildsCollection,
|
2015-07-05 18:03:58 +00:00
|
|
|
libLogger = require('./lib/logger'),
|
2016-03-16 19:20:08 +00:00
|
|
|
libReader = require('./lib/reader'),
|
2016-03-16 20:09:36 +00:00
|
|
|
libNotifier = require('./lib/notifier'),
|
2016-02-29 20:53:42 +00:00
|
|
|
libNode = require('./lib/node'),
|
|
|
|
libCommand = require('./lib/command'),
|
|
|
|
libExecutor = require('./lib/executor'),
|
|
|
|
libScm = require('./lib/scm'),
|
2015-12-29 20:32:05 +00:00
|
|
|
EventEmitter = require('events').EventEmitter,
|
2016-01-09 13:35:33 +00:00
|
|
|
validateConfig = require('./lib/validateConfig'),
|
|
|
|
utils = require('./lib/utils');
|
2015-04-10 19:23:52 +00:00
|
|
|
|
2015-07-12 12:05:33 +00:00
|
|
|
var app = new EventEmitter(),
|
2016-01-09 19:23:39 +00:00
|
|
|
logger = libLogger('app');
|
2015-07-05 18:03:58 +00:00
|
|
|
|
2016-01-10 13:55:57 +00:00
|
|
|
app.reader = new Reader();
|
|
|
|
|
2016-01-09 11:31:36 +00:00
|
|
|
var httpServerLogger = libLogger('http server');
|
|
|
|
|
|
|
|
app.httpServer = httpServer.create();
|
|
|
|
|
|
|
|
app.httpServer.on('error', function(err, req, res) {
|
|
|
|
httpServerLogger.error(
|
|
|
|
'Error processing request ' + req.method + ' ' + req.url + ':',
|
|
|
|
err.stack || err
|
|
|
|
);
|
|
|
|
if (!res.headersSent) {
|
|
|
|
res.statusCode = 500;
|
|
|
|
res.end();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
app.httpServer.addRequestListener(function(req, res, next) {
|
|
|
|
var start = Date.now();
|
|
|
|
|
|
|
|
res.on('finish', function() {
|
|
|
|
var end = Date.now();
|
|
|
|
|
|
|
|
httpServerLogger.log(
|
|
|
|
'[%s] %s %s %s - %s ms',
|
|
|
|
new Date(end).toUTCString(),
|
|
|
|
req.method,
|
|
|
|
req.url,
|
|
|
|
res.statusCode,
|
|
|
|
end - start
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
2015-05-18 20:27:02 +00:00
|
|
|
app.lib = {};
|
2015-07-05 18:03:58 +00:00
|
|
|
app.lib.logger = libLogger;
|
2016-03-16 19:20:08 +00:00
|
|
|
app.lib.reader = libReader;
|
2016-03-16 20:09:36 +00:00
|
|
|
app.lib.notifier = libNotifier;
|
2016-02-29 20:53:42 +00:00
|
|
|
app.lib.command = libCommand;
|
|
|
|
app.lib.executor = libExecutor;
|
|
|
|
app.lib.scm = libScm;
|
|
|
|
app.lib.node = libNode;
|
2015-05-12 21:07:03 +00:00
|
|
|
|
2015-07-20 20:57:41 +00:00
|
|
|
var configDefaults = {
|
2015-07-20 21:01:55 +00:00
|
|
|
notify: {},
|
2015-08-18 21:29:30 +00:00
|
|
|
http: {host: '127.0.0.1', port: 3000, url: 'http://127.0.0.1:3000'}
|
2015-07-20 20:57:41 +00:00
|
|
|
};
|
|
|
|
|
2015-10-14 18:45:54 +00:00
|
|
|
var completeUncompletedBuilds = function(callback) {
|
|
|
|
Steppy(
|
|
|
|
function() {
|
|
|
|
db.builds.find({
|
|
|
|
start: {descCreateDate: ''},
|
|
|
|
limit: 100
|
|
|
|
}, this.slot());
|
|
|
|
},
|
2015-10-14 18:59:36 +00:00
|
|
|
function(err, lastBuilds) {
|
|
|
|
var uncompletedBuilds = _(lastBuilds).filter(function(lastBuild) {
|
|
|
|
return !lastBuild.completed;
|
|
|
|
});
|
|
|
|
|
2015-10-14 18:45:54 +00:00
|
|
|
var completeGroup = this.makeGroup();
|
|
|
|
|
|
|
|
if (uncompletedBuilds.length) {
|
|
|
|
var queuedAndOtherUncompletedBuilds = _(uncompletedBuilds).partition(
|
|
|
|
function(uncompletedBuild) {
|
|
|
|
return uncompletedBuild.status === 'queued';
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
var queuedBuilds = queuedAndOtherUncompletedBuilds[0];
|
|
|
|
uncompletedBuilds = queuedAndOtherUncompletedBuilds[1];
|
|
|
|
|
|
|
|
if (queuedBuilds.length) {
|
|
|
|
logger.log(
|
|
|
|
'remove queued builds: %s',
|
|
|
|
_(queuedBuilds).pluck('id').join(', ')
|
|
|
|
);
|
|
|
|
|
|
|
|
db.builds.del(queuedBuilds, completeGroup.slot());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uncompletedBuilds.length) {
|
|
|
|
logger.log(
|
|
|
|
'complete with interrupt error uncompleted builds: %s',
|
|
|
|
_(uncompletedBuilds).pluck('id').join(', ')
|
|
|
|
);
|
|
|
|
|
|
|
|
_(uncompletedBuilds).each(function(uncompletedBuild) {
|
|
|
|
var endDate = (
|
|
|
|
uncompletedBuild.startDate ||
|
|
|
|
uncompletedBuild.createDate
|
|
|
|
);
|
|
|
|
|
|
|
|
var sumDuration = _(uncompletedBuild.stepTimings).reduce(
|
|
|
|
function(sum, timing) {
|
|
|
|
return sum + timing.duration;
|
|
|
|
},
|
|
|
|
0
|
|
|
|
) || 0;
|
|
|
|
|
|
|
|
endDate += sumDuration;
|
|
|
|
|
|
|
|
db.builds.update(
|
|
|
|
{id: uncompletedBuild.id},
|
|
|
|
{
|
|
|
|
endDate: endDate,
|
|
|
|
status: 'error',
|
|
|
|
completed: true,
|
|
|
|
error: {message: 'interrupted by server restart'}
|
|
|
|
},
|
|
|
|
completeGroup.slot()
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2015-05-18 18:25:08 +00:00
|
|
|
Steppy(
|
|
|
|
function() {
|
|
|
|
app.config = {};
|
|
|
|
app.config.paths = {};
|
|
|
|
|
|
|
|
// path to root dir (with projects, builds etc)
|
|
|
|
app.config.paths.data = path.join(process.cwd(), 'data');
|
2015-12-08 19:09:46 +00:00
|
|
|
|
2015-07-05 22:02:37 +00:00
|
|
|
app.config.paths.preload = path.join(app.config.paths.data, 'preload.json');
|
|
|
|
|
|
|
|
var preloadExistsCallback = this.slot();
|
|
|
|
fs.exists(app.config.paths.preload, function(isExists) {
|
|
|
|
preloadExistsCallback(null, isExists);
|
2015-05-18 18:25:08 +00:00
|
|
|
});
|
|
|
|
},
|
2016-01-18 20:34:49 +00:00
|
|
|
function(err, isPreloadExists) {
|
2015-07-05 22:02:37 +00:00
|
|
|
if (isPreloadExists) {
|
|
|
|
var preload = require(app.config.paths.preload);
|
|
|
|
// register rc plugins
|
|
|
|
_(preload.plugins).each(function(plugin) {
|
2015-07-06 17:19:13 +00:00
|
|
|
logger.log('Preload plugin "%s"', plugin);
|
2015-07-05 22:02:37 +00:00
|
|
|
require(plugin).register(app);
|
|
|
|
});
|
|
|
|
}
|
2015-05-18 20:27:02 +00:00
|
|
|
|
2016-01-10 13:55:57 +00:00
|
|
|
app.reader.load(app.config.paths.data, 'config', this.slot());
|
2015-05-18 20:27:02 +00:00
|
|
|
},
|
2016-01-18 20:34:49 +00:00
|
|
|
function(err, config) {
|
2015-12-29 20:32:05 +00:00
|
|
|
validateConfig(config, this.slot());
|
|
|
|
},
|
2016-01-18 20:34:49 +00:00
|
|
|
function(err, config) {
|
2015-05-18 18:25:08 +00:00
|
|
|
_(app.config).defaults(config);
|
2015-07-20 20:57:41 +00:00
|
|
|
_(app.config).defaults(configDefaults);
|
2015-05-18 18:25:08 +00:00
|
|
|
|
2016-01-18 20:34:49 +00:00
|
|
|
// try to read db and projects paths from config or set default values
|
|
|
|
_(app.config.paths).defaults(config.paths, {
|
|
|
|
db: path.join(app.config.paths.data, 'db'),
|
|
|
|
projects: path.join(app.config.paths.data, 'projects')
|
|
|
|
});
|
|
|
|
|
2016-01-09 13:35:33 +00:00
|
|
|
logger.log('Server config:', utils.toPrettyJson(app.config));
|
2015-05-18 18:25:08 +00:00
|
|
|
|
2016-01-18 20:34:49 +00:00
|
|
|
var dbDirExistsCallback = this.slot();
|
|
|
|
fs.exists(app.config.paths.db, function(isExists) {
|
|
|
|
dbDirExistsCallback(null, isExists);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function(err, isDbDirExists) {
|
|
|
|
if (isDbDirExists) {
|
|
|
|
this.pass(null);
|
|
|
|
} else {
|
|
|
|
fs.mkdir(app.config.paths.db, this.slot());
|
|
|
|
}
|
|
|
|
},
|
|
|
|
function() {
|
2015-12-03 19:08:46 +00:00
|
|
|
var dbBackend = require(app.config.storage.backend);
|
|
|
|
|
|
|
|
// monkey patch memdown to allow save empty strings which is correct
|
|
|
|
// at general but occasionally not allowed at _checkKey
|
|
|
|
// https://github.com/Level/abstract-leveldown/issues/74
|
|
|
|
if (app.config.storage.backend === 'memdown') {
|
|
|
|
dbBackend.prototype._checkKey = _.noop;
|
|
|
|
}
|
|
|
|
|
|
|
|
db.init(app.config.paths.db, {db: dbBackend}, this.slot());
|
2015-06-13 18:59:32 +00:00
|
|
|
},
|
|
|
|
function() {
|
2016-01-05 11:18:20 +00:00
|
|
|
app.projects = new ProjectsCollection({
|
|
|
|
db: db,
|
2016-01-10 13:55:57 +00:00
|
|
|
reader: app.reader,
|
2016-01-05 11:18:20 +00:00
|
|
|
baseDir: app.config.paths.projects
|
|
|
|
});
|
2015-10-14 18:45:54 +00:00
|
|
|
|
2016-01-10 16:37:19 +00:00
|
|
|
app.notifier = new Notifier({db: db});
|
|
|
|
|
2015-10-14 18:45:54 +00:00
|
|
|
completeUncompletedBuilds(this.slot());
|
2015-06-13 18:59:32 +00:00
|
|
|
},
|
2016-01-18 20:34:49 +00:00
|
|
|
function() {
|
2016-02-29 21:11:58 +00:00
|
|
|
require('./distributor').create(app, this.slot());
|
2015-07-05 20:35:57 +00:00
|
|
|
},
|
|
|
|
function(err, distributor) {
|
2016-01-06 20:24:41 +00:00
|
|
|
app.builds = new BuildsCollection({
|
|
|
|
db: db,
|
|
|
|
distributor: distributor
|
|
|
|
});
|
2015-07-05 20:35:57 +00:00
|
|
|
|
|
|
|
// register other plugins
|
2015-07-06 17:19:13 +00:00
|
|
|
_(app.config.plugins).each(function(plugin) {
|
|
|
|
logger.log('Load plugin "%s"', plugin);
|
|
|
|
require(plugin).register(app);
|
|
|
|
});
|
2015-07-05 20:35:57 +00:00
|
|
|
|
2016-02-29 21:11:58 +00:00
|
|
|
distributor.init();
|
|
|
|
|
2016-01-10 16:37:19 +00:00
|
|
|
app.notifier.init(app.config.notify, this.slot());
|
2015-05-18 18:25:08 +00:00
|
|
|
},
|
2016-01-05 11:18:20 +00:00
|
|
|
function() {
|
2016-02-25 20:00:14 +00:00
|
|
|
// only at development and only when there is no other request listeners
|
|
|
|
// (e.g. socketio) add not found route to the very end (after all
|
|
|
|
// plugins register)
|
|
|
|
if (
|
|
|
|
env === 'development' &&
|
|
|
|
app.httpServer._events &&
|
|
|
|
_(app.httpServer._events.request).isFunction()
|
|
|
|
) {
|
|
|
|
app.httpServer.addRequestListener(function(req, res, next) {
|
|
|
|
if (!res.headersSent) {
|
|
|
|
res.statusCode = 404;
|
|
|
|
res.end(req.method.toUpperCase() + ' ' + req.url + ' Not Found');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-01-05 11:18:20 +00:00
|
|
|
// load projects after all plugins to provide ability for plugins to
|
|
|
|
// handle `projectLoaded` event
|
|
|
|
app.projects.loadAll(this.slot());
|
|
|
|
},
|
2015-07-12 12:05:33 +00:00
|
|
|
function(err) {
|
2016-01-06 18:14:25 +00:00
|
|
|
logger.log('Loaded projects: ', _(app.projects.getAll()).pluck('name'));
|
2016-01-05 11:18:20 +00:00
|
|
|
|
2015-07-20 21:01:55 +00:00
|
|
|
var host = app.config.http.host,
|
|
|
|
port = app.config.http.port;
|
|
|
|
logger.log('Start http server on %s:%s', host, port);
|
2016-01-09 11:31:36 +00:00
|
|
|
app.httpServer.listen(port, host);
|
2015-07-12 12:05:33 +00:00
|
|
|
},
|
2015-05-18 18:25:08 +00:00
|
|
|
function(err) {
|
|
|
|
if (err) throw err;
|
|
|
|
}
|
|
|
|
);
|