@topgroup/diginext
Version:
A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.
426 lines (425 loc) • 20.6 kB
JavaScript
;
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;