@atomist/sdm-pack-spring
Version:
Atomist software delivery machine extension pack for Spring and Spring Boot applications
191 lines • 10 kB
JavaScript
;
/*
* Copyright © 2019 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const automation_client_1 = require("@atomist/automation-client");
const sdm_1 = require("@atomist/sdm");
const spawn = require("cross-spawn");
const os = require("os");
const portfinder = require("portfinder");
const helpers_1 = require("../../maven/build/helpers");
const mavenLogInterpreter_1 = require("../../maven/build/mavenLogInterpreter");
const mavenCommand_1 = require("../../maven/mavenCommand");
const springLoggingPatterns_1 = require("../../spring/deploy/springLoggingPatterns");
exports.ListBranchDeploys = {
name: "listLocalDeploys",
intent: "list branch deploys",
description: "List local deployments of repository across all branches",
listener: (ci) => __awaiter(void 0, void 0, void 0, function* () { return handleListDeploys(ci.context); }),
};
function deploymentToString(deploymentKey) {
const deployment = deploymentEndpoints[deploymentKey];
const abbreviatedSha = deployment.sha.slice(0, 7);
const deploymentEndpoint = deployment.endpoint;
return `${deploymentKey} deployed ${abbreviatedSha} at ${deploymentEndpoint}`;
}
function handleListDeploys(ctx) {
return __awaiter(this, void 0, void 0, function* () {
const message = `${Object.keys(deploymentEndpoints).length} branches currently deployed on ${os.hostname()}:\n${Object.keys(deploymentEndpoints).map(deploymentToString).join("\n")}`;
yield ctx.messageClient.respond(message);
return automation_client_1.Success;
});
}
const deploymentEndpoints = {};
/**
* Use Maven per-branch deploy
* @param opts options
*/
function executeMavenPerBranchSpringBootDeploy(opts) {
const optsToUse = Object.assign({ lowerPort: 9090, successPatterns: springLoggingPatterns_1.SpringBootSuccessPatterns, commandLineArgumentsFor: springBootMavenArgs, baseUrl: "http://127.0.0.1", maxConcurrentDeployments: 5 }, opts);
const deployer = new MavenDeployer(optsToUse);
return (goalInvocation) => __awaiter(this, void 0, void 0, function* () {
const { credentials, id } = goalInvocation;
try {
const deployment = yield goalInvocation.configuration.sdm.projectLoader.doWithProject({
credentials,
id,
readOnly: true,
}, project => deployer.deployProject(goalInvocation, project));
const deploymentKey = `${id.owner}/${id.repo}/${goalInvocation.goalEvent.branch}`;
deploymentEndpoints[deploymentKey] = { sha: goalInvocation.goalEvent.sha, endpoint: deployment.endpoint };
return { code: 0, externalUrls: [{ label: "Endpoint", url: deployment.endpoint }] };
}
catch (err) {
return { code: 1, message: err.stack };
}
});
}
exports.executeMavenPerBranchSpringBootDeploy = executeMavenPerBranchSpringBootDeploy;
/**
* Holds state relating to existing deployments
*/
class MavenDeployer {
constructor(options) {
this.options = options;
// Already allocated ports
this.repoBranchToPort = {};
// Keys are ports: values are child processes
this.portToChildProcess = {};
}
deployProject(goalInvocation, project) {
return __awaiter(this, void 0, void 0, function* () {
const branch = goalInvocation.goalEvent.branch;
const contextRoot = `/${project.id.owner}/${project.id.repo}/${branch}`;
let port = this.repoBranchToPort[project.id.repo + ":" + branch];
if (!port) {
automation_client_1.logger.info("Looking for unused port for branch '%s' of %s:%s...", branch, project.id.owner, project.id.repo);
port = yield portfinder.getPortPromise({ /*host: this.options.baseUrl,*/ port: this.options.lowerPort });
this.repoBranchToPort[project.id.repo + ":" + branch] = port;
automation_client_1.logger.info("Reserving port %d for branch '%s' of %s:%s", port, branch, project.id.owner, project.id.repo);
}
const existingChildProcess = this.portToChildProcess[port];
if (!!existingChildProcess) {
automation_client_1.logger.info("Killing existing process for branch '%s' of %s:%s with pid %s", branch, project.id.owner, project.id.repo, existingChildProcess.pid);
yield sdm_1.killAndWait(existingChildProcess);
}
else {
automation_client_1.logger.info("No existing process for branch '%s' of %s:%s", branch, project.id.owner, project.id.repo);
// Check we won't end with a crazy number of child processes
const presentCount = Object.keys(this.portToChildProcess)
.filter(n => typeof n === "number")
.length;
if (presentCount >= this.options.maxConcurrentDeployments) {
throw new Error(`Unable to deploy project at ${project.id} as limit of ${this.options.maxConcurrentDeployments} has been reached`);
}
}
const mvn = yield mavenCommand_1.determineMavenCommand(project);
const childProcess = spawn(mvn, [
"spring-boot:run",
...helpers_1.MavenOptions,
].concat(this.options.commandLineArgumentsFor(port, contextRoot)), {
cwd: project.baseDir,
});
if (!childProcess.pid) {
throw new Error("Fatal error deploying using Maven--is `mvn` on your automation node path?\n" +
`Attempted to execute '${mvn} spring-boot:run' in ${project.baseDir}`);
}
const deployment = {
childProcess,
endpoint: `${this.options.baseUrl}:${port}${contextRoot}`,
};
this.portToChildProcess[port] = childProcess;
const newLineDelimitedLog = new sdm_1.DelimitedWriteProgressLogDecorator(goalInvocation.progressLog, "\n");
childProcess.stdout.on("data", what => newLineDelimitedLog.write(what.toString()));
childProcess.stderr.on("data", what => newLineDelimitedLog.write(what.toString()));
let stdout = "";
let stderr = "";
return new Promise((resolve, reject) => {
childProcess.stdout.addListener("data", what => {
if (!!what) {
stdout += what.toString();
}
if (this.options.successPatterns.some(successPattern => successPattern.test(stdout))) {
resolve(deployment);
}
});
childProcess.stderr.addListener("data", what => {
if (!!what) {
stderr += what.toString();
}
});
childProcess.addListener("exit", () => __awaiter(this, void 0, void 0, function* () {
if (this.options.successPatterns.some(successPattern => successPattern.test(stdout))) {
resolve(deployment);
}
else {
yield reportFailureToUser(goalInvocation, stdout);
automation_client_1.logger.error("Maven deployment failure vvvvvvvvvvvvvvvvvvvvvv");
automation_client_1.logger.error("stdout:\n%s\nstderr:\n%s\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^", stdout, stderr);
reject(new Error("Maven deployment failure"));
}
}));
childProcess.addListener("error", reject);
});
});
}
}
function reportFailureToUser(gi, log) {
return __awaiter(this, void 0, void 0, function* () {
const interpretation = mavenLogInterpreter_1.MavenLogInterpreter(log);
if (!!interpretation) {
yield gi.addressChannels(`✘ Maven deployment failure for ${gi.id.url}/${gi.goalEvent.branch}`);
if (!!interpretation.relevantPart) {
yield (gi.addressChannels(`\`\`\`\n${interpretation.relevantPart}\n\`\`\``));
}
else {
yield (gi.addressChannels("See SDM log for full Maven output"));
}
}
});
}
function springBootMavenArgs(port, contextRoot) {
return [
"-Dspring-boot.run.arguments=--server.port=" + port + ",--server.contextPath=" + contextRoot +
",--server.servlet.contextPath=" + contextRoot,
"-Drun.arguments=--server.port=" + port + ",--server.contextPath=" + contextRoot +
",--server.servlet.contextPath=" + contextRoot,
];
}
//# sourceMappingURL=MavenPerBranchSpringBootDeploymentGoal.js.map