@topgroup/diginext
Version:
A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.
276 lines (275 loc) • 12.7 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 __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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.requestDeploy = void 0;
const node_fs_1 = require("node:fs");
const chalk_1 = __importDefault(require("chalk"));
const log_1 = require("diginext-utils/dist/xconsole/log");
const socket_io_client_1 = require("socket.io-client");
const config_1 = require("../../config/config");
const fetchApi_1 = require("../../modules/api/fetchApi");
const plugins_1 = require("../../plugins");
const generate_build_tag_1 = require("../build/generate-build-tag");
const get_server_info_1 = require("../cli/get-server-info");
const git_utils_1 = require("../git/git-utils");
const ask_ai_generate_dockerfile_1 = require("./ask-ai-generate-dockerfile");
const ask_deploy_environment_info_1 = require("./ask-deploy-environment-info");
const create_build_slug_1 = require("./create-build-slug");
const parse_options_to_app_config_1 = require("./parse-options-to-app-config");
/**
* Request the build server to start building & deploying
*/
async function requestDeploy(options) {
var _a;
if (process.env.CLI_MODE === "server") {
(0, log_1.logError)(`This command is only available at CLIENT MODE.`);
return;
}
if (!options)
return (0, log_1.logError)(`Failed to request deploying app: Missing CLI options.`);
const { DB } = await Promise.resolve().then(() => __importStar(require("../../modules/api/DB")));
if (!options.targetDirectory)
options.targetDirectory = process.cwd();
if (options.isDebugging)
console.log("requestDeploy() > options.targetDirectory :>> ", options.targetDirectory);
const { buildServerUrl } = (0, config_1.getCliConfig)();
const { env, targetDirectory, projectSlug, appSlug } = options;
// check Dockerfile -> no dockerfile, no build -> failed
let dockerFile = (0, plugins_1.resolveDockerfilePath)({ targetDirectory, env, ignoreIfNotExisted: true });
if (options.isDebugging)
console.log("requestDeploy() > dockerFile :>> ", dockerFile);
if (!dockerFile) {
// ask to use AI for generating "Dockerfile"
await (0, ask_ai_generate_dockerfile_1.askAiGenerateDockerfile)(options);
dockerFile = (0, plugins_1.resolveDockerfilePath)({ targetDirectory, env });
let successMsg = `Double check your ${chalk_1.default.yellow(`"Dockerfile.${env}"`)}, test building container locally, for example:`;
successMsg += `\n ${chalk_1.default.cyan(`$ docker build -t ${projectSlug}/${appSlug} -f Dockerfile.${env} .`)}`;
successMsg += `\n ${chalk_1.default.cyan(`$ docker run -p <host_port>:<container_port> ${projectSlug}/${appSlug}`)}`;
successMsg += `\n ${chalk_1.default.gray(`# access: http://localhost:<host_port>`)}`;
successMsg += `\nIf everything is good, commit & push the changes to the git remote origin, then deploy again:`;
successMsg += `\n ${chalk_1.default.cyan(`$ dx up --${env}`)}`;
successMsg += `\nHave fun!`;
(0, log_1.logSuccess)(successMsg);
return;
}
// [SECURITY] Check ".dockerignore"
let dockerIgnoreFile = (0, plugins_1.resolveFilePath)(".dockerignore", {
targetDirectory,
env,
msg: `You should have ".dockerignore" file and exclude ".git/" directory or any sensitive directories/files for security reason.`,
ignoreIfNotExisted: true,
});
if (dockerIgnoreFile) {
const dockerIgnoreContent = (0, node_fs_1.readFileSync)(dockerIgnoreFile, "utf8");
if (dockerIgnoreContent.indexOf(".git") === -1) {
(0, log_1.logError)(`You need to add ".git/" to your ".dockerignore" file due to security reason.`);
return;
}
}
// Warn about uncommited files
const shouldShowGitWarning = await (0, git_utils_1.isUnstagedFiles)(options.targetDirectory);
if (shouldShowGitWarning)
(0, log_1.logWarn)(`Please stage files & commit before deploying.`);
/**
* [1] Parse cli options, validate the input params
* and save it to deploy environment config on Diginext workspace
*/
let appConfig = await (0, parse_options_to_app_config_1.parseOptionsToAppConfig)(options);
if (!appConfig)
return;
if (options.isDebugging) {
console.log("requestDeploy() > appConfig :>>");
console.dir(appConfig, { depth: 10 });
}
/**
* [2] Compare LOCAL & SERVER App Config,
* then upload local app config to server.
*/
// console.log("requestDeploy() > options.author :>> ", options.author);
const deployInfo = await (0, ask_deploy_environment_info_1.askForDeployEnvironmentInfo)(options);
if (options.isDebugging)
console.log("requestDeploy() > askForDeployEnvironmentInfo() :>> ", deployInfo);
if (!deployInfo.appConfig || !deployInfo.deployEnvironment)
return;
const { deployEnvironment, appConfig: validatedAppConfig } = deployInfo;
appConfig = validatedAppConfig;
if (options.isDebugging) {
console.log("requestDeploy() > appConfig :>>");
console.dir(appConfig, { depth: 10 });
console.log("requestDeploy() > deployEnvironment :>>");
console.dir(deployEnvironment, { depth: 10 });
}
/**
* [3] Generate build number & build image as docker image tag
*/
const { imageURL } = deployEnvironment;
const tagInfo = await (0, generate_build_tag_1.generateBuildTagBySourceDir)(options.targetDirectory, { branch: options.gitBranch });
if (options.isDebugging)
console.log("requestDeploy() > generateBuildTagBySourceDir() :>> ", tagInfo);
options.buildTag = tagInfo.tag;
options.buildImage = `${imageURL}:${options.buildTag}`;
options.SOCKET_ROOM = (0, create_build_slug_1.createBuildSlug)({ projectSlug: appConfig.project, appSlug: appConfig.slug, buildTag: options.buildTag });
const { SOCKET_ROOM } = options;
/**
* [5] Notify the commander & call API to request server build:
*/
(0, log_1.log)(`Requesting BUILD SERVER to deploy this app: "${appConfig.project}/${appConfig.slug}/${env}"`);
options.projectSlug = appConfig.project;
options.appSlug = appConfig.slug;
options.slug = appConfig.slug;
/**
* [6] Get server info
*/
const { version: serverVersion, location: serverLocation } = await (0, get_server_info_1.getServerInfo)();
if (options.isDebugging)
console.log("requestDeploy() > getServerInfo() :>> ", { serverVersion, serverLocation });
// Make an API to request server to build:
const requestDeployData = {
buildParams: {
env,
buildTag: options.buildTag,
buildNumber: tagInfo.number,
message: options.message,
gitBranch: options.gitBranch,
registrySlug: deployEnvironment.registry,
appSlug: options.appSlug,
cliVersion: (0, plugins_1.currentVersion)(),
serverVersion,
serverLocation,
},
deployParams: {
env,
forceRollOut: options.shouldRollOut,
skipReadyCheck: false,
shouldUseFreshDeploy: options.shouldUseFreshDeploy,
healthzPath: options.healthz,
},
};
if (options.isDebugging) {
console.log("Request deploy data :>> ");
console.dir(requestDeployData, { depth: 10 });
}
try {
const url = `${buildServerUrl}/api/v1/deploy/from-source`;
if (options.isDebugging)
console.log("requestDeploy() > deploy API url :>> ", url);
const requestResult = await (0, fetchApi_1.fetchApi)({
url,
method: "POST",
data: requestDeployData,
});
if (options.isDebugging) {
console.log("requestDeploy() > Request deploy result :>> ");
console.dir(requestResult, { depth: 10 });
}
// check errors
if (!requestResult.status) {
(0, log_1.logError)(`Failed to request server to build & deploy: ${requestResult.messages.join("\n")}` || `Unable to call Request Deploy API.`);
return;
}
if (options === null || options === void 0 ? void 0 : options.isDebugging)
console.log("requestResult.data :>> ", requestResult.data);
const defaultLogURL = `${buildServerUrl}/build/logs?build_slug=${SOCKET_ROOM}&env=${env}`;
(0, log_1.log)(`-> Check build status here: ${((_a = requestResult === null || requestResult === void 0 ? void 0 : requestResult.data) === null || _a === void 0 ? void 0 : _a.logURL) || defaultLogURL} `);
}
catch (e) {
(0, log_1.logError)(`Unable to call Request Deploy API:`, e);
return;
}
// update the project so it can be sorted on top
try {
await DB.updateOne("project", { slug: options.projectSlug }, { lastUpdatedBy: options.username });
}
catch (e) {
(0, log_1.logWarn)(e);
}
// friendly reminder
if (env == "prod")
(0, log_1.log)(chalk_1.default.red(`⚠️⚠️⚠️ REMEMBER TO CREATE PULL REQUEST TO "master" (or "main") BRANCH ⚠️⚠️⚠️`));
if (options.isTail) {
let socketURL = buildServerUrl.replace(/https/gi, "wss");
socketURL = buildServerUrl.replace(/http/gi, "ws");
let pingInt;
const socket = (0, socket_io_client_1.io)(socketURL, {
transports: ["websocket"],
timeout: 60000,
requestTimeout: 60000,
});
socket.on("error", (e) => (0, log_1.logError)(e));
socket.on("connect_error", (e) => (0, log_1.logError)(e));
const ping = () => {
const start = Date.now();
socket.emit("ping", (location) => {
const duration = Date.now() - start;
console.log(`[DXUP Websocket] Ping: ${duration}ms - ${socketURL}${location ? ` (${location})` : ""}`);
});
};
socket.on("disconnect", () => {
(0, log_1.log)("[DXUP Websocket] Disconnected.");
socket.emit("leave", { room: SOCKET_ROOM });
clearInterval(pingInt);
});
socket.on("connect", () => {
(0, log_1.log)("[DXUP Websocket] Connected.");
socket.emit("join", { room: SOCKET_ROOM });
clearInterval(pingInt);
pingInt = setInterval(ping, 15 * 1000);
ping();
});
return new Promise((resolve, reject) => {
socket.on("message", ({ action, message, type }) => {
if (message) {
const errorWordIndex = message.toLowerCase().indexOf("error");
if (errorWordIndex > -1) {
console.warn(message);
}
else {
console.log(message);
}
}
if (action == "end") {
socket.disconnect();
if (type === "error") {
// process.exit(1);
reject(message);
}
else {
// process.exit(0);
resolve(true);
}
}
});
// Max build duration: 60 mins
setTimeout(() => reject(`[DXUP Websocket] Request timeout (>60 minutes)`), 60 * 60 * 1000);
});
}
else {
return true;
}
}
exports.requestDeploy = requestDeploy;