2014-12-02 21:21:32 +00:00
|
|
|
'use strict';
|
|
|
|
|
2015-04-12 22:19:42 +00:00
|
|
|
var Steppy = require('twostep').Steppy,
|
2014-12-02 21:21:32 +00:00
|
|
|
fs = require('fs'),
|
2015-04-12 22:19:42 +00:00
|
|
|
path = require('path'),
|
2015-05-18 17:52:01 +00:00
|
|
|
_ = require('underscore'),
|
2015-12-02 20:24:45 +00:00
|
|
|
utils = require('./utils'),
|
2015-12-28 20:34:08 +00:00
|
|
|
SpawnCommand = require('./command/spawn').Command,
|
2016-01-05 11:18:20 +00:00
|
|
|
validateParams = require('./validateParams'),
|
|
|
|
EventEmitter = require('events').EventEmitter,
|
|
|
|
inherits = require('util').inherits;
|
2014-12-02 21:21:32 +00:00
|
|
|
|
2016-01-05 11:18:20 +00:00
|
|
|
/*
|
|
|
|
* Projects collection it's something similar to backbone collection.
|
|
|
|
* But contrasting to backbone there is no model of a single project, when you
|
|
|
|
* receive project from collection you just get a json.
|
|
|
|
* General id for the particular project is a `name` of that project.
|
|
|
|
*/
|
|
|
|
function ProjectsCollection(params) {
|
|
|
|
this.db = params.db;
|
|
|
|
this.reader = params.reader;
|
|
|
|
this.baseDir = params.baseDir;
|
|
|
|
this.configs = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.ProjectsCollection = ProjectsCollection;
|
|
|
|
|
|
|
|
inherits(ProjectsCollection, EventEmitter);
|
2014-12-02 21:21:32 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Validates and returns given `config` to the `callback`(err, config)
|
|
|
|
*/
|
2016-01-05 11:18:20 +00:00
|
|
|
ProjectsCollection.prototype.validateConfig = function(config, callback) {
|
2015-12-28 20:34:08 +00:00
|
|
|
Steppy(
|
|
|
|
function() {
|
|
|
|
validateParams(config, {
|
|
|
|
type: 'object',
|
|
|
|
properties: {
|
|
|
|
scm: {
|
|
|
|
type: 'object',
|
|
|
|
required: true,
|
|
|
|
properties: {
|
|
|
|
type: {enum: ['git', 'mercurial'], required: true},
|
|
|
|
repository: {type: 'string', required: true},
|
|
|
|
rev: {type: 'string', required: true}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
steps: {
|
|
|
|
type: 'array',
|
|
|
|
required: true,
|
|
|
|
items: {
|
|
|
|
type: 'object',
|
|
|
|
properties: {
|
|
|
|
cmd: {type: 'string', required: true},
|
2015-12-31 20:58:39 +00:00
|
|
|
name: {type: 'string'},
|
|
|
|
type: {enum: ['shell']},
|
2015-12-28 20:34:08 +00:00
|
|
|
shell: {type: 'string'}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
additionalProperties: true
|
|
|
|
});
|
|
|
|
|
|
|
|
this.pass(null);
|
|
|
|
},
|
|
|
|
function(err) {
|
|
|
|
if (err) {
|
|
|
|
err.message = (
|
|
|
|
'Error during validation of project "' + config.name +
|
|
|
|
'": ' + err.message
|
|
|
|
);
|
|
|
|
}
|
|
|
|
callback(err, config);
|
|
|
|
}
|
|
|
|
);
|
2014-12-02 21:21:32 +00:00
|
|
|
};
|
|
|
|
|
2016-01-05 11:18:20 +00:00
|
|
|
ProjectsCollection.prototype._getProjectPath = function(name) {
|
|
|
|
return path.join(this.baseDir, name);
|
|
|
|
}
|
2015-04-12 22:19:42 +00:00
|
|
|
|
2016-01-05 11:18:20 +00:00
|
|
|
ProjectsCollection.prototype._loadConfig = function(dir, callback) {
|
|
|
|
var self = this;
|
2014-12-02 21:21:32 +00:00
|
|
|
|
|
|
|
Steppy(
|
|
|
|
function() {
|
2016-01-05 11:18:20 +00:00
|
|
|
self.reader.load(dir, 'config', this.slot());
|
2014-12-02 21:21:32 +00:00
|
|
|
},
|
2015-05-19 20:48:20 +00:00
|
|
|
function(err, config) {
|
2015-08-21 20:38:20 +00:00
|
|
|
// convert steps object to array
|
|
|
|
if (!_(config.steps).isArray() && _(config.steps).isObject()) {
|
|
|
|
config.steps = _(config.steps).map(function(val, name) {
|
|
|
|
var step;
|
|
|
|
if (_(val).isObject()) {
|
|
|
|
step = val;
|
|
|
|
} else {
|
|
|
|
step = {cmd: val};
|
|
|
|
}
|
|
|
|
step.name = name;
|
|
|
|
return step;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-12-28 20:34:08 +00:00
|
|
|
// apply defaults to not yet validated config
|
2015-05-19 20:48:20 +00:00
|
|
|
_(config.steps).each(function(step) {
|
2015-05-20 20:20:51 +00:00
|
|
|
if (!step.type) step.type = 'shell';
|
2015-12-28 20:34:08 +00:00
|
|
|
if (!step.name && step.cmd) step.name = utils.prune(step.cmd, 40);
|
2015-05-19 20:48:20 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
this.pass(config);
|
|
|
|
},
|
2014-12-02 21:21:32 +00:00
|
|
|
callback
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2016-01-05 11:18:20 +00:00
|
|
|
/**
|
|
|
|
* Loads project to collection
|
|
|
|
*/
|
|
|
|
ProjectsCollection.prototype.load = function(name, callback) {
|
|
|
|
var self = this,
|
|
|
|
dir = self._getProjectPath(name);
|
|
|
|
|
|
|
|
Steppy(
|
|
|
|
function() {
|
|
|
|
self._loadConfig(dir, this.slot());
|
|
|
|
},
|
|
|
|
function(err, config) {
|
|
|
|
config.name = name;
|
|
|
|
config.dir = dir;
|
|
|
|
|
|
|
|
self.validateConfig(config, this.slot());
|
|
|
|
},
|
|
|
|
function(err, config) {
|
|
|
|
self.configs.push(config);
|
|
|
|
self.emit('projectLoaded', config);
|
|
|
|
this.pass(null);
|
|
|
|
},
|
2014-12-02 21:21:32 +00:00
|
|
|
callback
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2016-01-05 11:18:20 +00:00
|
|
|
ProjectsCollection.prototype.unload = function(name, callback) {
|
|
|
|
callback = callback || _.noop;
|
|
|
|
var self = this;
|
|
|
|
|
2014-12-02 21:21:32 +00:00
|
|
|
Steppy(
|
|
|
|
function() {
|
2016-01-05 11:18:20 +00:00
|
|
|
var index = _(self.configs).findIndex(function(config) {
|
|
|
|
return config.name === name;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (index === -1) {
|
|
|
|
throw new Error('Can`t unload not loaded project: "' + name + '"');
|
|
|
|
}
|
|
|
|
|
|
|
|
var unloadedConfig = self.configs.splice(index, 1)[0];
|
|
|
|
self.emit('projectUnloaded', unloadedConfig);
|
|
|
|
|
|
|
|
this.pass(null);
|
2014-12-02 21:21:32 +00:00
|
|
|
},
|
2016-01-05 11:18:20 +00:00
|
|
|
callback
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
ProjectsCollection.prototype.get = function(name) {
|
|
|
|
return _(this.configs).findWhere({name: name});
|
|
|
|
};
|
|
|
|
|
|
|
|
ProjectsCollection.prototype.getAll = function(name) {
|
|
|
|
return this.configs;
|
|
|
|
};
|
|
|
|
|
|
|
|
ProjectsCollection.prototype.findWhere = function(params) {
|
|
|
|
return _(this.configs).findWhere(params);
|
|
|
|
};
|
|
|
|
|
|
|
|
ProjectsCollection.prototype.where = function(params) {
|
|
|
|
return _(this.configs).where(params);
|
|
|
|
};
|
|
|
|
|
|
|
|
ProjectsCollection.prototype.filter = function(iterator) {
|
|
|
|
return _(this.configs).filter(iterator);
|
|
|
|
};
|
|
|
|
|
|
|
|
ProjectsCollection.prototype.pluck = function(attribute) {
|
|
|
|
return _(this.configs).pluck(attribute);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads all projects (from `this.baseDir`)
|
|
|
|
*/
|
|
|
|
ProjectsCollection.prototype.loadAll = function(callback) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
Steppy(
|
|
|
|
function() {
|
|
|
|
fs.readdir(self.baseDir, this.slot());
|
2014-12-02 21:21:32 +00:00
|
|
|
},
|
2016-01-05 11:18:20 +00:00
|
|
|
function(err, dirs) {
|
|
|
|
var loadGroup = this.makeGroup();
|
|
|
|
_(dirs).each(function(dir) {
|
|
|
|
self.load(dir, loadGroup.slot());
|
|
|
|
});
|
2014-12-02 21:21:32 +00:00
|
|
|
},
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
};
|
2015-07-26 13:05:54 +00:00
|
|
|
|
2016-01-05 11:18:20 +00:00
|
|
|
/*
|
|
|
|
* Calculates average build duration for the given project
|
|
|
|
*/
|
|
|
|
ProjectsCollection.prototype.getAvgBuildDuration = function(name, callback) {
|
|
|
|
var self = this;
|
|
|
|
|
2015-07-26 13:05:54 +00:00
|
|
|
Steppy(
|
|
|
|
function() {
|
|
|
|
// get last done builds to calc avg build time
|
2016-01-05 11:18:20 +00:00
|
|
|
self.db.builds.find({
|
2015-07-26 13:05:54 +00:00
|
|
|
start: {
|
2016-01-05 11:18:20 +00:00
|
|
|
projectName: name,
|
2015-07-26 13:05:54 +00:00
|
|
|
status: 'done',
|
|
|
|
descCreateDate: ''
|
|
|
|
},
|
|
|
|
limit: 10
|
|
|
|
}, this.slot());
|
|
|
|
},
|
|
|
|
function(err, doneBuilds) {
|
|
|
|
var durationsSum = _(doneBuilds).reduce(function(memo, build) {
|
|
|
|
return memo + (build.endDate - build.startDate);
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
this.pass(Math.round(durationsSum / doneBuilds.length));
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
};
|
2015-12-02 20:24:45 +00:00
|
|
|
|
2016-01-05 11:18:20 +00:00
|
|
|
ProjectsCollection.prototype.remove = function(name, callback) {
|
|
|
|
var self = this;
|
|
|
|
|
2015-12-02 20:24:45 +00:00
|
|
|
Steppy(
|
|
|
|
function() {
|
2016-01-05 11:18:20 +00:00
|
|
|
self.db.builds.find({
|
|
|
|
start: {projectName: name, descCreateDate: ''}
|
2015-12-02 20:24:45 +00:00
|
|
|
}, this.slot());
|
|
|
|
|
|
|
|
new SpawnCommand().run({cmd: 'rm', args: [
|
2016-01-05 11:18:20 +00:00
|
|
|
'-Rf', self._getProjectPath(name)
|
2015-12-02 20:24:45 +00:00
|
|
|
]}, this.slot());
|
2016-01-05 11:18:20 +00:00
|
|
|
|
|
|
|
self.unload(name, this.slot());
|
2015-12-02 20:24:45 +00:00
|
|
|
},
|
|
|
|
function(err, builds) {
|
|
|
|
if (builds.length) {
|
2016-01-05 11:18:20 +00:00
|
|
|
self.db.builds.del(builds, this.slot());
|
2015-12-02 20:24:45 +00:00
|
|
|
|
|
|
|
var logLinesRemoveGroup = this.makeGroup();
|
|
|
|
_(builds).each(function(build) {
|
2016-01-05 11:18:20 +00:00
|
|
|
self.db.logLines.remove({
|
2015-12-02 20:24:45 +00:00
|
|
|
start: {buildId: build.id}
|
|
|
|
}, logLinesRemoveGroup.slot());
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.pass(null, null);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
};
|
2015-12-04 19:54:21 +00:00
|
|
|
|
2016-01-05 11:18:20 +00:00
|
|
|
ProjectsCollection.prototype.rename = function(name, newName, callback) {
|
|
|
|
var self = this;
|
|
|
|
|
2015-12-04 19:54:21 +00:00
|
|
|
Steppy(
|
|
|
|
function() {
|
|
|
|
fs.rename(
|
2016-01-05 11:18:20 +00:00
|
|
|
self._getProjectPath(name),
|
|
|
|
self._getProjectPath(newName),
|
2015-12-04 19:54:21 +00:00
|
|
|
this.slot()
|
|
|
|
);
|
|
|
|
|
2016-01-05 11:18:20 +00:00
|
|
|
self.db.builds.multiUpdate(
|
|
|
|
{start: {projectName: name, descCreateDate: ''}},
|
2015-12-04 19:54:21 +00:00
|
|
|
function(build) {
|
2016-01-05 11:18:20 +00:00
|
|
|
build.project.name = newName;
|
2015-12-04 19:54:21 +00:00
|
|
|
return build;
|
|
|
|
},
|
|
|
|
this.slot()
|
|
|
|
);
|
|
|
|
},
|
2016-01-05 11:18:20 +00:00
|
|
|
function() {
|
|
|
|
// just update currently loaded project name by link
|
|
|
|
self.get(name).name = newName;
|
|
|
|
|
|
|
|
this.pass(null);
|
|
|
|
},
|
2015-12-04 19:54:21 +00:00
|
|
|
callback
|
|
|
|
);
|
|
|
|
};
|