Build server prototype (integration with GitHub / NuGet / etc)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
micro-build-server/BuildServer/lib/builder.js

220 lines
7.3 KiB

"use strict";
const path = require("path");
const fs = require("fs");
const fse = require("fs-extra");
const async = require("async");
const gitLoader = require("./git/loader");
const processor = require("./task-processor");
const reportProcessor = require("./report-processor");
const mailSender = require("./mail-sender");
const settings = require("../settings");
const codePostfix = "";
const mailLazinessLevel = 1000;
const maxDescriptionLength = 140;
const maxTmpcodepathLength = 15;
const twoDigits = 100;
const createFinalState = (isSuccess) => {
if (isSuccess) {
return "success";
}
return "error";
};
const createBuildDoneMessage = (isSuccess, name) => {
if (isSuccess) {
return `Successfully built ${name}`;
}
return `Build failed for ${name}`;
};
const notifyStatus = (options, notifyStatusCallback) => {
const status = {
"description": String(options.description || "").substr(0, maxDescriptionLength),
"owner": options.owner,
"repo": options.reponame,
"sha": options.hash,
"state": options.state,
"target_url": `${settings.siteRoot}status/${options.owner}/${options.reponame}/${options.hash}`
};
settings.createGithub(options.owner).repos.createStatus(status, (createStatusErr) => {
if (createStatusErr) {
console.log(`Error while creating status: ${createStatusErr}`);
console.log(status);
return notifyStatusCallback(createStatusErr);
}
return notifyStatusCallback();
});
};
const wrapGitLoader = (skipGitLoader) => {
if (!skipGitLoader) {
return gitLoader;
}
return (gitLoaderOptions, gitLoaderCallback) => process.nextTick(gitLoaderCallback);
};
const safeParseJson = (data) => {
try {
return { "parsed": JSON.parse(data) };
} catch (err) {
return { err };
}
};
const build = (options, buildCallback) => {
const url = options.url;
const owner = options.owner;
const reponame = options.reponame;
const rev = options.rev;
const branch = options.branch;
const skipGitLoader = options.skipGitLoader;
const local = path.join(options.app.get("gitpath"), "r");
const tmp = path.join(options.app.get("tmpcodepath"), rev.substr(0, maxTmpcodepathLength));
const exported = tmp + codePostfix;
const release = path.join(options.app.get("releasepath"), owner, reponame, branch, rev);
const statusQueue = async.queue((task, queueCallback) => task(queueCallback), 1);
const actualGitLoader = wrapGitLoader(skipGitLoader);
const date = new Date();
const versionMajor = date.getFullYear();
const versionMinor = date.getMonth() + 1;
const versionBuild = date.getDate();
const versionRev = (date.getHours() * twoDigits) + date.getMinutes();
const version = `${versionMajor}.${versionMinor}.${versionBuild}.${versionRev}`;
const versionInfo = `${version}; built from ${rev}; repository: ${owner}/${reponame}; branch: ${branch}`;
statusQueue.push((queueCallback) => notifyStatus({
"description": "Preparing to build...",
"hash": rev,
owner,
reponame,
"state": "pending"
}, queueCallback));
fse.mkdirsSync(release);
fs.writeFileSync(path.join(options.app.get("releasepath"), owner, reponame, branch, "latest.id"), rev);
fse.mkdirsSync(path.join(options.app.get("releasepath"), owner, reponame, "$revs"));
fs.writeFileSync(path.join(options.app.get("releasepath"), owner, reponame, "$revs", `${rev}.branch`), branch);
const createErrorMessageForMail = (doneErr) => {
if (!doneErr) {
return "";
}
return `Error message: ${doneErr}\r\n\r\n`;
};
const createResultMessageForMail = (result) => {
if (!result || !result.messages || !result.messages.$allMessages) {
return JSON.stringify(result, null, " ");
}
return result.messages.$allMessages.map((msg) => `${msg.prefix}\t${msg.message}`).join("\r\n");
};
const done = (doneErr, result) => {
const allErrors = ((result || {}).errors || {}).$allMessages || [];
const allWarns = ((result || {}).warns || {}).$allMessages || [];
const allInfos = ((result || {}).infos || {}).$allMessages || [];
const errorMessage = (allErrors[0] || {}).message || doneErr;
const warnMessage = (allWarns[0] || {}).message;
const infoMessage = (allInfos[allInfos.length - 1] || {}).message;
reportProcessor.writeReport(release, doneErr, result, (writeErr) => {
statusQueue.push((queueCallback) => async.parallel([
(parallelCallback) => notifyStatus({
"description": errorMessage || warnMessage || infoMessage || "Success",
"hash": rev,
owner,
reponame,
"state": createFinalState(!doneErr)
}, parallelCallback),
(parallelCallback) => mailSender.send({
"from": settings.smtp.sender,
"headers": { "X-Laziness-level": mailLazinessLevel },
"subject": createBuildDoneMessage(doneErr, `${owner}/${reponame}/${branch}`),
"text": `Build status URL: ${settings.siteRoot}status/${owner}/${reponame}/${rev}\r\n\r\n${createErrorMessageForMail(doneErr)}${createResultMessageForMail(result)}`,
"to": settings.smtp.receiver
}, parallelCallback),
(parallelCallback) => {
if (doneErr) {
return process.nextTick(parallelCallback);
}
return fse.remove(tmp, parallelCallback);
}
], queueCallback));
if (writeErr) {
return buildCallback(writeErr);
}
return buildCallback(doneErr, result);
});
};
actualGitLoader({
branch,
exported,
"hash": rev,
local,
"remote": `${url}.git`
}, (gitLoaderErr) => {
if (gitLoaderErr) {
console.log(gitLoaderErr);
return done(`Git fetch error: ${gitLoaderErr}`);
}
console.log("Done loading from git");
return fs.exists(path.join(exported, "mbs.json"), (exists) => {
if (!exists) {
return done(null, "MBSNotFound");
}
return fs.readFile(path.join(exported, "mbs.json"), (readErr, data) => {
if (readErr) {
return done(readErr, "MBSUnableToRead");
}
const { parsed, err } = safeParseJson(data);
if (err) {
console.log(`Malformed data: ${data}`);
return done(err, "MBSMalformed");
}
return processor.processTask(parsed, {
branch,
exported,
owner,
release,
reponame,
rev,
tmp,
versionInfo
}, (processErr, result) => {
if (processErr) {
return done(processErr, result);
}
return done(processErr, result);
});
});
});
});
};
exports.build = build;