UNPKG

@topgroup/diginext

Version:

A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.

426 lines (425 loc) 20.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const makeDaySlug_1 = require("diginext-utils/dist/string/makeDaySlug"); const log_1 = require("diginext-utils/dist/xconsole/log"); const lodash_1 = require("lodash"); const dist_1 = require("tsoa/dist"); const package_json_1 = __importDefault(require("../../package.json")); const interfaces_1 = require("../interfaces"); const get_app_environment_1 = require("../modules/apps/get-app-environment"); const generate_build_tag_1 = require("../modules/build/generate-build-tag"); const start_build_1 = require("../modules/build/start-build"); const git_utils_1 = require("../modules/git/git-utils"); const mongodb_1 = require("../plugins/mongodb"); const DeployService_1 = __importDefault(require("../services/DeployService")); let DeployController = class DeployController { constructor() { this.service = new DeployService_1.default(); } /** * ### [DEPRECATED] * #### Use `buildAndDeploy()` instead. * Build container image first, then deploy that build to target deploy environment. */ deployFromSource(body, queryParams) { let { options: inputOptions } = body; // console.log("deployFromSource :>> ", body); // validation & conversion... if (!inputOptions) return { status: 0, messages: [`Deploy "options" is required.`] }; // if (!isJSON(inputOptions)) return { status: 0, messages: [`Deploy "options" is invalid (should be in JSON format).`] } as ResponseData; // const options = JSON.parse(inputOptions as string) as InputOptions; // log("[DEPLOY] options", options); // TODO: Save client CLI version to server database for tracking purpose! // check for version compatibility between CLI & SERVER: const cliVersion = inputOptions.version || "0.0.0"; const breakingChangeVersionCli = cliVersion.split(".")[0]; const serverVersion = package_json_1.default.version; const breakingChangeVersionServer = serverVersion.split(".")[0]; if (breakingChangeVersionCli != breakingChangeVersionServer) { return { status: 0, messages: [ `Your CLI version (${cliVersion}) is much lower than the BUILD SERVER version (${serverVersion}). Please upgrade: "dx update"`, ], }; } // return respondSuccess({ msg: `Building...` }); (0, log_1.log)(`deployFromSource > BUILD_TAG :>>`, inputOptions.buildTag); (0, start_build_1.startBuildV1)(inputOptions); // start build in background: return (0, interfaces_1.respondSuccess)({ msg: `Building...` }); } /** * Build container image first, then deploy that build to target deploy environment. * - `Alias of "/api/v1/deploy/from-source"` */ async buildAndDeploy(body, queryParams) { var _a; let { buildParams, deployParams } = body; // validation if (!buildParams) return (0, interfaces_1.respondFailure)(`Build "params" is required.`); if (!deployParams) return (0, interfaces_1.respondFailure)(`Deploy "params" is required.`); // start build in background: try { const data = await this.service.buildAndDeploy(buildParams, deployParams, this.ownership); if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.isDebugging) console.log(`[DEPLOY CONTROLLER] buildAndDeploy > data :>> `, data); return (0, interfaces_1.respondSuccess)({ data, msg: "Building..." }); } catch (e) { console.error(e); return (0, interfaces_1.respondFailure)(e.toString()); } } /** * Build container image first, then deploy that build to target deploy environment. * - `Alias of "/api/v1/deploy/build-first"` */ buildFromSourceAndDeploy(body, queryParams) { return this.buildAndDeploy(body); } /** * Build container image from app's git repo and deploy it to target deploy environment. */ async buildFromAppAndDeploy(body) { if (!body.appSlug) return (0, interfaces_1.respondFailure)(`Data of "appSlug" is required.`); if (!body.deployParams) return (0, interfaces_1.respondFailure)(`Data of "deployParams" is required.`); if (!body.deployParams.env) return (0, interfaces_1.respondFailure)(`Data of "deployParams.env" is required.`); const { AppService } = await Promise.resolve().then(() => __importStar(require("../services"))); const appSvc = new AppService(this.ownership); const app = await appSvc.findOne({ slug: body.appSlug }); if (!app) return (0, interfaces_1.respondFailure)(`App not found.`); const { env } = body.deployParams; const deployEnvironment = await (0, get_app_environment_1.getDeployEvironmentByApp)(app, env); if (!deployEnvironment) (0, interfaces_1.respondFailure)(`Unable to deploy: this app doesn't have any "${env}" deploy environment.`); // validate registry -> if this app has no registries but specified in deploy params -> move forward as it will use deploy params if (!deployEnvironment.registry && !body.deployParams.registry) return (0, interfaces_1.respondFailure)(`Container registry is required.`); // validate cluster -> if this app has no clusters but specified in deploy params -> move forward as it will use deploy params if (!deployEnvironment.cluster && !body.deployParams.cluster) return (0, interfaces_1.respondFailure)(`Cluster is required.`); const tagInfo = await (0, generate_build_tag_1.generateBuildTagByApp)(app, { branch: body.gitBranch }); const buildParams = { env, appSlug: app.slug, buildTag: tagInfo.tag, buildNumber: tagInfo.number, gitBranch: body.gitBranch, registrySlug: deployEnvironment.registry || body.deployParams.registry, }; const deployParams = body.deployParams; const buildAndDeployParams = { buildParams, deployParams }; return this.buildAndDeploy(buildAndDeployParams); } /** * Build container image from app's git repo and deploy it to target deploy environment. * - Flow: fork the git repo -> build from the new repo -> deploy to Diginext */ async buildFromGitRepoAndDeploy(body) { if (!body.sshUrl) return (0, interfaces_1.respondFailure)(`Data of "sshUrl" is required.`); if (!body.deployParams) return (0, interfaces_1.respondFailure)(`Data of "deployParams" is required.`); // deploy "dev" environment by default if (!body.deployParams.env) body.deployParams.env = "dev"; const { env } = body.deployParams; // inherit the ownership const { AppService } = await Promise.resolve().then(() => __importStar(require("../services"))); const appSvc = new AppService(this.ownership); let app = await appSvc.findOne({ "git.repoSSH": body.sshUrl }); // generate new app if (!app) { // try to get default git provider const gitData = (0, git_utils_1.parseGitRepoDataFromRepoSSH)(body.sshUrl); const { GitProviderService } = await Promise.resolve().then(() => __importStar(require("../services"))); const gitSvc = new GitProviderService(this.ownership); const gitProvider = await gitSvc.findOne({ type: gitData.providerType, public: true, workspace: this.workspace._id }); if (!gitProvider) throw new Error(`Unable to deploy: no git providers (${gitData.providerType.toUpperCase()}) in this workspace.`); // create a new app try { app = await appSvc.createWithGitURL(body.sshUrl, mongodb_1.MongoDB.toString(gitProvider._id), { workspace: this.workspace, owner: this.user }, { gitBranch: body.gitBranch, returnExisting: true }); } catch (e) { return (0, interfaces_1.respondFailure)(e.toString()); } } // get random registry in this workspace const { ContainerRegistryService } = await Promise.resolve().then(() => __importStar(require("../services"))); const regSvc = new ContainerRegistryService(this.ownership); const defaultRegistry = await regSvc.findOne({ workspace: this.workspace._id }); if (!defaultRegistry) throw new Error(`Unable to deploy: no container registries in this workspace.`); // find default cluster const { ClusterService } = await Promise.resolve().then(() => __importStar(require("../services"))); const clusterSvc = new ClusterService(this.ownership); let cluster = body.clusterSlug ? await clusterSvc.findOne({ slug: body.clusterSlug, workspace: this.workspace._id }) : undefined; // get default cluster if (!cluster) { const defaultCluster = await clusterSvc.findOne({ isDefault: true, workspace: this.workspace._id }); if (defaultCluster) cluster = defaultCluster; } // get random cluster if (!cluster) { const randomCluster = await clusterSvc.findOne({ workspace: this.workspace._id }); if (randomCluster) cluster = randomCluster; } if (!cluster) throw new Error(`Unable to deploy: no clusters in this workspace.`); // create deploy environment (if not exists): const { DeployEnvironmentService } = await Promise.resolve().then(() => __importStar(require("../services"))); const deployEnvSvc = new DeployEnvironmentService(this.ownership); let deployEnvironment = await (0, get_app_environment_1.getDeployEvironmentByApp)(app, env); if (!deployEnvironment) { try { app = await deployEnvSvc.createDeployEnvironment(app.slug, { env, deployEnvironmentData: { registry: defaultRegistry.slug, cluster: cluster.slug, port: (0, lodash_1.toNumber)(body.port), imageURL: `${defaultRegistry.imageBaseURL}/${app.projectSlug}/${app.slug}`, buildTag: (0, makeDaySlug_1.makeDaySlug)({ divider: "" }), }, }, this.ownership); // assign new created deploy environment: deployEnvironment = app.deployEnvironment[env]; } catch (e) { return (0, interfaces_1.respondFailure)(e.toString()); } } // validate deploy params if (!deployEnvironment.cluster) deployEnvironment.cluster = cluster.slug; if (!deployEnvironment.registry) deployEnvironment.registry = defaultRegistry.slug; // start build & deploy from source (repo): const buildParams = { appSlug: app.slug, buildTag: (0, makeDaySlug_1.makeDaySlug)({ divider: "" }), gitBranch: body.gitBranch, registrySlug: deployEnvironment.registry, }; const deployParams = body.deployParams; deployParams.author = mongodb_1.MongoDB.toString(this.user._id); const buildAndDeployParams = { buildParams, deployParams }; return this.buildAndDeploy(buildAndDeployParams); } /** * Deploy app to target environment from a "success" build. */ async deployFromBuild(body, queryParams) { const { DB } = await Promise.resolve().then(() => __importStar(require("../modules/api/DB"))); const { buildSlug } = body; if (!buildSlug) return (0, interfaces_1.respondFailure)(`Build "slug" is required`); const build = await DB.findOne("build", { slug: buildSlug }, { ignorable: true }); if (!build) return (0, interfaces_1.respondFailure)(`Build not found.`); const deployBuildOptions = { owner: this.user, workspace: this.workspace, env: body.env, shouldUseFreshDeploy: body.shouldUseFreshDeploy, skipReadyCheck: body.skipReadyCheck, forceRollOut: body.forceRollOut, }; console.log("deployBuildOptions :>> ", deployBuildOptions); // DEPLOY A BUILD: try { const result = await this.service.deployBuildV2(build, deployBuildOptions); const { release } = result; if (!release) return (0, interfaces_1.respondFailure)(`Failed to deploy from a build (${buildSlug}).`); return { messages: [], status: 1, data: result }; } catch (e) { return (0, interfaces_1.respondFailure)(`${e}`); } } /** * Deploy app to target environment from a release. */ async deployFromRelease(body, queryParams) { const { DB } = await Promise.resolve().then(() => __importStar(require("../modules/api/DB"))); const { releaseSlug } = body; if (!releaseSlug) return (0, interfaces_1.respondFailure)(`Build "slug" is required`); const release = await DB.findOne("release", { slug: releaseSlug }, { populate: ["build"], ignorable: true }); if (!release) return (0, interfaces_1.respondFailure)(`Release not found.`); const build = release.build; if (!build) return (0, interfaces_1.respondFailure)(`Build not found.`); const deployBuildOptions = { owner: this.user, workspace: this.workspace, env: body.env, shouldUseFreshDeploy: body.shouldUseFreshDeploy, skipReadyCheck: body.skipReadyCheck, forceRollOut: body.forceRollOut, }; console.log("deployBuildOptions :>> ", deployBuildOptions); // DEPLOY A BUILD: try { const result = await this.service.deployRelease(release, deployBuildOptions); } catch (e) { return (0, interfaces_1.respondFailure)(e.toString()); } } /** * Promote a deploy environment to another deploy environment (default: "production"). */ async promoteDeployEnvironment(body) { var _a; const deployBuildOptions = { ...body, owner: this.user, workspace: this.workspace, }; if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.isDebugging) console.log("deployBuildOptions :>> ", deployBuildOptions); // DEPLOY A BUILD FROM A SOURCE DEPLOY ENVIRONMENT TO A DESTINATION DEPLOY ENVIRONMENT: try { const data = await this.service.promoteDeployEnvironment(deployBuildOptions); return (0, interfaces_1.respondSuccess)({ data, msg: deployBuildOptions.deployInBackground ? "Deploying..." : "" }); } catch (e) { return (0, interfaces_1.respondFailure)(e.toString()); } } }; __decorate([ (0, dist_1.Security)("api_key"), (0, dist_1.Security)("jwt"), (0, dist_1.Post)("/"), (0, dist_1.Deprecated)(), __param(0, (0, dist_1.Body)()), __param(1, (0, dist_1.Queries)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", void 0) ], DeployController.prototype, "deployFromSource", null); __decorate([ (0, dist_1.Security)("api_key"), (0, dist_1.Security)("jwt"), (0, dist_1.Post)("/build-first"), __param(0, (0, dist_1.Body)()), __param(1, (0, dist_1.Queries)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], DeployController.prototype, "buildAndDeploy", null); __decorate([ (0, dist_1.Security)("api_key"), (0, dist_1.Security)("jwt"), (0, dist_1.Post)("/from-source"), __param(0, (0, dist_1.Body)()), __param(1, (0, dist_1.Queries)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", void 0) ], DeployController.prototype, "buildFromSourceAndDeploy", null); __decorate([ (0, dist_1.Security)("api_key"), (0, dist_1.Security)("jwt"), (0, dist_1.Post)("/from-app"), __param(0, (0, dist_1.Body)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], DeployController.prototype, "buildFromAppAndDeploy", null); __decorate([ (0, dist_1.Security)("api_key"), (0, dist_1.Security)("jwt"), (0, dist_1.Post)("/from-git"), __param(0, (0, dist_1.Body)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], DeployController.prototype, "buildFromGitRepoAndDeploy", null); __decorate([ (0, dist_1.Security)("api_key"), (0, dist_1.Security)("jwt"), (0, dist_1.Post)("/from-build"), __param(0, (0, dist_1.Body)()), __param(1, (0, dist_1.Queries)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], DeployController.prototype, "deployFromBuild", null); __decorate([ (0, dist_1.Security)("api_key"), (0, dist_1.Security)("jwt"), (0, dist_1.Post)("/from-release"), __param(0, (0, dist_1.Body)()), __param(1, (0, dist_1.Queries)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], DeployController.prototype, "deployFromRelease", null); __decorate([ (0, dist_1.Security)("api_key"), (0, dist_1.Security)("jwt"), (0, dist_1.Post)("/promote"), __param(0, (0, dist_1.Body)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], DeployController.prototype, "promoteDeployEnvironment", null); DeployController = __decorate([ (0, dist_1.Tags)("Deploy"), (0, dist_1.Route)("deploy") ], DeployController); exports.default = DeployController;