UNPKG

@atomist/sdm-pack-spring

Version:

Atomist software delivery machine extension pack for Spring and Spring Boot applications

191 lines 10 kB
"use strict"; /* * 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