UNPKG

@atomist/sdm-pack-spring

Version:

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

229 lines 12.1 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 springLoggingPatterns_1 = require("../../spring/deploy/springLoggingPatterns"); const gradleLogInterpreter_1 = require("../build/gradleLogInterpreter"); const gradleCommand_1 = require("../gradleCommand"); exports.ListGradleBranchDeploys = { 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; }); } /** * Goal to deploy to Gradle with one process per branch * @type {GenericGoal} */ exports.GradlePerBranchSpringBootDeploymentGoal = new sdm_1.GoalWithPrecondition({ uniqueName: "gradleDeploy", orderedName: "3-deploy", environment: sdm_1.IndependentOfEnvironment, displayName: "deploy branch locally", completedDescription: "Deployed branch locally", failedDescription: "Local branch deployment failure", }); const deploymentEndpoints = {}; /** * Use Maven per-branch deploy * @param projectLoader use to load projects * @param opts options */ function executeGradlePerBranchSpringBootDeploy(opts) { const optsToUse = Object.assign({ lowerPort: 9090, successPatterns: springLoggingPatterns_1.SpringBootSuccessPatterns, commandLineArgumentsFor: springBootGradleArgs, baseUrl: "http://127.0.0.1", maxConcurrentDeployments: 5 }, opts); const deployer = new GradleDeployer(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, externlaUrls: [{ label: "Endpoint", url: deployment.endpoint }] }; } catch (err) { return { code: 1, message: err.stack }; } }); } exports.executeGradlePerBranchSpringBootDeploy = executeGradlePerBranchSpringBootDeploy; /** * Holds state */ class GradleDeployer { 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 gradle = yield gradleCommand_1.determineGradleCommand(project); const childProcess = spawn(gradle, [ "bootRun", ].concat(this.options.commandLineArgumentsFor(port, contextRoot)), { cwd: project.baseDir, }); if (!childProcess.pid) { throw new Error("Fatal error deploying using Gradle--is `gradle` on your automation node path?\n" + `Attempted to execute '${gradle} bootRun' 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("Gradle deployment failure vvvvvvvvvvvvvvvvvvvvvv"); automation_client_1.logger.error("stdout:\n%s\nstderr:\n%s\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^", stdout, stderr); reject(new Error("Gradle deployment failure")); } })); childProcess.addListener("error", reject); }); }); } } function reportFailureToUser(gi, log) { return __awaiter(this, void 0, void 0, function* () { const interpretation = gradleLogInterpreter_1.GradleLogInterpreter(log); if (!!interpretation) { yield gi.addressChannels(`✘ Gradle 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")); } } }); } /** * Generate the command line arguments for running your application using Gradle * @param {number} port The port on which the application will be running * @param {string} contextRoot The context root to be used for deploying your application * @returns {string[]} The command line arguments */ function springBootGradleArgs(port, contextRoot) { return [ "-Pargs=--server.port=" + port + ",--server.contextPath=" + contextRoot + ",--server.servlet.contextPath=" + contextRoot, ]; } exports.springBootGradleArgs = springBootGradleArgs; /** * Goal to deploy your application using Gradle. This will perform a deployment per branch. */ class GradlePerBranchDeployment extends sdm_1.FulfillableGoalWithRegistrations { // tslint:disable-next-line constructor(goalDetailsOrUniqueName = sdm_1.DefaultGoalNameGenerator.generateName("gradle-branch-deployment")) { super(Object.assign(Object.assign(Object.assign({}, exports.GradlePerBranchSpringBootDeploymentGoal.definition), sdm_1.getGoalDefinitionFrom(goalDetailsOrUniqueName, sdm_1.DefaultGoalNameGenerator.generateName("gradle-branch-deployment"))), { displayName: "deploy" })); this.goalDetailsOrUniqueName = goalDetailsOrUniqueName; } with(registration) { const deploymentOptions = { lowerPort: registration.lowerPort, successPatterns: registration.successPatterns, commandLineArgumentsFor: !!registration.commandLineArgumentsFor ? registration.commandLineArgumentsFor : springBootGradleArgs, baseUrl: !!registration.baseUrl ? registration.baseUrl : "http://127.0.0.1", maxConcurrentDeployments: !!registration.maxConcurrentDeployments ? registration.maxConcurrentDeployments : 5, }; this.addFulfillment(Object.assign({ name: sdm_1.DefaultGoalNameGenerator.generateName("deployer"), goalExecutor: executeGradlePerBranchSpringBootDeploy(deploymentOptions) }, registration)); return this; } } exports.GradlePerBranchDeployment = GradlePerBranchDeployment; //# sourceMappingURL=GradlePerBranchSpringBootDeploymentGoal.js.map