UNPKG

@topgroup/diginext

Version:

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

340 lines (339 loc) 17.1 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 __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.askForDeployEnvironmentInfo = exports.askForCertIssuer = void 0; const chalk_1 = __importDefault(require("chalk")); const log_1 = require("diginext-utils/dist/xconsole/log"); const inquirer_1 = __importDefault(require("inquirer")); const lodash_1 = require("lodash"); const interfaces_1 = require("../../interfaces"); const plugins_1 = require("../../plugins"); const number_1 = require("../../plugins/number"); const app_helper_1 = require("../apps/app-helper"); const ask_project_and_app_1 = require("../apps/ask-project-and-app"); const get_app_environment_1 = require("../apps/get-app-environment"); const update_config_1 = require("../apps/update-config"); const update_git_config_1 = require("../apps/update-git-config"); const build_1 = require("../build"); const ask_for_cluster_1 = require("../cluster/ask-for-cluster"); const ask_for_registry_1 = require("../registry/ask-for-registry"); const dotenv_exec_1 = require("./dotenv-exec"); const dotenv_upload_1 = require("./dotenv-upload"); /** * Prompt a question to ask for Cert Issuer: Let's Encrypt, Custom Issuer or None * @param options * @returns */ const askForCertIssuer = async (options = {}) => { const { question, defaultValue } = options; const { selectedSSL } = await inquirer_1.default.prompt({ type: "list", name: "selectedSSL", message: question !== null && question !== void 0 ? question : `Which SSL certificate do you want to use for these domains?`, default: defaultValue !== null && defaultValue !== void 0 ? defaultValue : interfaces_1.availableSslTypes[0], choices: interfaces_1.availableSslTypes, }); return selectedSSL; }; exports.askForCertIssuer = askForCertIssuer; const askForDeployEnvironmentInfo = async (options) => { const { env, targetDirectory: appDirectory = process.cwd(), author } = options; // ask for project & app information let { project, app } = await (0, ask_project_and_app_1.askForProjectAndApp)(options.targetDirectory, options); if (options.isDebugging) console.log("askForDeployEnvironmentInfo > app :>> ", app); if (options.isDebugging) console.log("askForDeployEnvironmentInfo > project :>> ", project); // verify if this app's directory has any git remote integrated if (!app.git || !app.git.provider || !app.git.repoSSH || !app.git.repoURL) { const gitInfo = await (0, plugins_1.getCurrentGitRepoData)(options.targetDirectory); if (options.isDebugging) console.log("askForDeployEnvironmentInfo > gitInfo :>> ", gitInfo); if (!gitInfo) throw new Error(`This app's directory doesn't have any git remote integrated.`); const updateGitInfo = { provider: gitInfo.provider, repoSSH: gitInfo.repoSSH, repoURL: gitInfo.repoURL }; if (options.isDebugging) console.log("askForDeployEnvironmentInfo > updateGitInfo :>> ", updateGitInfo); app = await (0, update_git_config_1.updateAppGitInfo)(app, updateGitInfo); if (options.isDebugging) console.log("askForDeployEnvironmentInfo > app :>> ", app); } /** * ----------------------------------------------------------------------- * FETCH SERVER APP DEPLOYMENT CONFIG & COMPARE WITH THE LOCAL APP CONFIG * ----------------------------------------------------------------------- * If one of these condition matched, terminate the previous deployment & deploy a new one: * - "project" is different * - "app" is different * - "cluster" is different * - "namespace" is different */ let serverAppConfig = (0, app_helper_1.getAppConfigFromApp)(app); if (options.isDebugging) console.log("askForDeployEnvironmentInfo > serverAppConfig :>> ", serverAppConfig); if (typeof serverAppConfig.deployEnvironment === "undefined") serverAppConfig.deployEnvironment = {}; let serverDeployEnvironment = serverAppConfig.deployEnvironment[env] || {}; const environmentDomains = serverDeployEnvironment.domains || []; if (options.isDebugging) console.log("askForDeployEnvironmentInfo > serverDeployEnvironment :>> ", serverDeployEnvironment); // TODO: move this part to server side? // if ( // serverAppConfig.project !== localAppConfig.project || // serverAppConfig.slug !== localAppConfig.slug || // serverDeployEnvironment.cluster !== localDeployEnvironment.cluster || // serverDeployEnvironment.namespace !== localDeployEnvironment.namespace // ) { // // Call API to terminate previous deployment // const { buildServerUrl } = getCliConfig(); // const terminateData = { slug: serverAppConfig.slug, env }; // // console.log("terminateData :>> ", terminateData); // const { status, messages } = await fetchApi({ // url: `${buildServerUrl}/api/v1/app/environment`, // method: "DELETE", // data: terminateData, // }); // if (!status) { // logWarn(`Can't terminate app's deploy environment:`, messages); // } else { // log(`Terminated "${app.slug}" app's "${env}" deploy environment since the app config was changed.`); // } // } /** * PARSE LOCAL DEPLOYMENT CONFIG & ASK FOR MISSING INFO */ const { DB } = await Promise.resolve().then(() => __importStar(require("../../modules/api/DB"))); // request cluster if (!serverDeployEnvironment.cluster) { const clusters = await DB.find("cluster", {}, { subpath: "/all", populate: ["provider"] }, { limit: 20 }); if ((0, lodash_1.isEmpty)(clusters)) { (0, log_1.logError)(`No clusters found in this workspace. Please add one to deploy on.`); return; } const { cluster } = await inquirer_1.default.prompt({ type: "list", name: "cluster", message: `Which cluster do you want to use for "${env}" environment?`, choices: clusters.map((_cluster) => { return { name: _cluster.name, value: _cluster }; }), }); serverDeployEnvironment.cluster = cluster.slug; serverDeployEnvironment.provider = cluster.provider.shortName; } else { if (options.isDebugging) console.log("askForDeployEnvironment() > serverDeployEnvironment.cluster :>> ", serverDeployEnvironment.cluster); let [cluster] = await DB.find("cluster", { slug: serverDeployEnvironment.cluster }, { subpath: "/all", ignorable: true }); if (options.isDebugging) console.log("askForDeployEnvironment() > cluster :>> ", cluster); if (!cluster) { (0, log_1.logWarn)(`Cluster "${serverDeployEnvironment.cluster}" not found or might be modified, please select target cluster.`); cluster = await (0, ask_for_cluster_1.askForCluster)(); serverDeployEnvironment.cluster = cluster.slug; serverDeployEnvironment.provider = cluster.provider.shortName; } } options.cluster = serverDeployEnvironment.cluster; options.provider = serverDeployEnvironment.provider; // request domains // console.log("deployEnvironment.domains :>> ", deployEnvironment.domains); if ((0, lodash_1.isEmpty)(environmentDomains)) { try { const domains = await (0, build_1.askForDomain)(env, project.slug, app.slug, serverDeployEnvironment, { user: author, shouldGenerate: options.domain == true, }); serverDeployEnvironment.domains = (0, lodash_1.isEmpty)(domains) ? [] : domains; } catch (e) { (0, log_1.logError)(`[ASK_DEPLOY_INFO] ${e}`); return; } } else { // TODO: check for domain DNS ? } if (options.isDebugging) console.log("serverDeployEnvironment.domains :>> ", serverDeployEnvironment.domains); if ((0, lodash_1.isEmpty)(serverDeployEnvironment.domains)) { (0, log_1.logWarn)(`This app doesn't have any domains configurated & will be reachable within namespace/cluster scope. To expose this app to the internet later, you can add your own domain to deploy environment config on Diginext workspace & deploy it again.`); } // request container registry let registry; if (serverDeployEnvironment.registry) { registry = await DB.findOne("registry", { slug: serverDeployEnvironment.registry }, { subpath: "/all", ignorable: true }); serverDeployEnvironment.registry = registry === null || registry === void 0 ? void 0 : registry.slug; } if (!serverDeployEnvironment.registry) { registry = await (0, ask_for_registry_1.askForRegistry)(); serverDeployEnvironment.registry = registry === null || registry === void 0 ? void 0 : registry.slug; } if (!registry) return; // ALWAYS UPDATE NEW "imageURL" // if (!serverDeployEnvironment.imageURL) { const imageSlug = `${project.slug}-${app.slug}`; serverDeployEnvironment.imageURL = `${registry.imageBaseURL}/${imageSlug}`; // } options.imageURL = serverDeployEnvironment.imageURL; // request ingress class // deployEnvironment.ingress; // request namespace if (!serverDeployEnvironment.namespace) serverDeployEnvironment.namespace = `${project.slug}-${env}`; options.namespace = serverDeployEnvironment.namespace; // request port if (typeof serverDeployEnvironment.port === "undefined" || (0, lodash_1.isNaN)(serverDeployEnvironment.port)) { const { selectedPort } = await inquirer_1.default.prompt({ type: "number", name: "selectedPort", message: "Which port do you use for this app?", default: 3000, validate: (input) => ((0, lodash_1.isNaN)(input) ? "Port should be a valid number." : true), }); serverDeployEnvironment.port = options.port = selectedPort; } options.port = serverDeployEnvironment.port; // request inherit previous deployment config if (typeof serverDeployEnvironment.shouldInherit === "undefined") serverDeployEnvironment.shouldInherit = true; options.shouldInherit = serverDeployEnvironment.shouldInherit; // request cdn if (typeof serverDeployEnvironment.cdn === "undefined") serverDeployEnvironment.cdn = false; options.shouldEnableCDN = serverDeployEnvironment.cdn; // request replicas if (typeof serverDeployEnvironment.replicas === "undefined") serverDeployEnvironment.replicas = 1; if (!(0, number_1.isNumeric)(serverDeployEnvironment.replicas)) serverDeployEnvironment.replicas = 1; options.replicas = serverDeployEnvironment.replicas; // request container size if (typeof serverDeployEnvironment.size === "undefined") { // const { selectedSize } = await inquirer.prompt<{ selectedSize: ResourceQuotaSize }>({ // type: "list", // name: "selectedSize", // message: "Please select your default container registry:", // choices: availableResourceSizes.map((r) => { // return { name: r, value: r }; // }), // }); // serverDeployEnvironment.size = selectedSize; serverDeployEnvironment.size = "none"; } options.size = serverDeployEnvironment.size; // request SSL config if (serverDeployEnvironment.domains.length > 0) { if (typeof serverDeployEnvironment.ssl === "undefined") { serverDeployEnvironment.ssl = await (0, exports.askForCertIssuer)(); } options.ssl = true; if (serverDeployEnvironment.ssl === "letsencrypt" || serverDeployEnvironment.ssl === "none") { // leave empty so the build server will generate it automatically serverDeployEnvironment.tlsSecret = ""; } else { // if they select "custom" SSL certificate -> ask for secret name: const { customSecretName } = await inquirer_1.default.prompt({ type: "input", name: "customSecretName", message: `Name your predefined custom SSL secret (ENTER to use default):`, default: serverDeployEnvironment.tlsSecret, }); if (customSecretName) serverDeployEnvironment.tlsSecret = customSecretName; } } else { options.ssl = false; serverDeployEnvironment.ssl = "none"; serverDeployEnvironment.tlsSecret = undefined; serverDeployEnvironment.domains = []; // TODO: remove domains from database } /** * UPDATE APP CONFIG ON SERVER: * (save app & its deploy environment data to database) */ if (options.isDebugging) console.log(">>>>>>>>> askDeployEnvironmentInfo > updateAppConfig()"); if (options.isDebugging) console.log("askDeployEnvironmentInfo > serverAppConfig :>> ", serverAppConfig); if (options.isDebugging) console.log("askDeployEnvironmentInfo > serverDeployEnvironment :>> ", serverDeployEnvironment); const appConfig = await (0, update_config_1.updateAppConfig)(app, env, serverDeployEnvironment); serverDeployEnvironment = appConfig.deployEnvironment[env]; if (options.isDebugging) (0, log_1.log)(`[ASK DEPLOY INFO] serverDeployEnvironment :>>`, serverDeployEnvironment); // fetched latest app on server app = await DB.findOne("app", { slug: app.slug }, { populate: ["project", "owner", "workspace"] }); if (options.isDebugging) (0, log_1.log)(`[ASK DEPLOY INFO] updated app :>>`, app); /** * UPLOAD ENVIRONMENT VARIABLES * --- * Check database to see should sync ENV variables or not... */ const { envVars: serverEnvironmentVariables = [] } = await (0, get_app_environment_1.getDeployEvironmentByApp)(app, env); let envFile = (0, plugins_1.resolveEnvFilePath)({ targetDirectory: appDirectory, env, ignoreIfNotExisted: true }); // console.log("envFile :>> ", envFile); if (options.isDebugging) console.log("serverEnvironmentVariables :>> ", serverEnvironmentVariables); // if "--upload-env" flag is specified: if (options.shouldUploadDotenv) { if (!envFile) { (0, log_1.logWarn)(`Can't upload DOTENV since there are no DOTENV files (.env.*) in this directory`); } else { await (0, dotenv_upload_1.uploadDotenvFileByApp)(envFile, app, env, options); } } else { // if ENV file is existed on local & not available on server -> ask to upload local ENV to server: if (envFile && !(0, lodash_1.isEmpty)(serverEnvironmentVariables)) { (0, log_1.logWarn)(`Skipped uploading your local ENV variables to the deploy environment since it's already existed.`); (0, log_1.log)(`(If you want to force upload them, you can deploy again with: ${chalk_1.default.cyan(`dx up --env=${env} --upload-env`)})`); } if (envFile && (0, lodash_1.isEmpty)(serverEnvironmentVariables)) { const { shouldUploadEnv } = await inquirer_1.default.prompt({ type: "confirm", name: "shouldUploadEnv", default: true, message: `Do you want to use your "${envFile}" on ${env.toUpperCase()} environment?`, }); if (shouldUploadEnv) await (0, dotenv_upload_1.uploadDotenvFileByApp)(envFile, app, env, options); } } // [SECURITY CHECK] warns if DOTENV files are not listed in ".gitignore" file await (0, dotenv_exec_1.checkGitignoreContainsDotenvFiles)({ targetDir: appDirectory }); return { project, app, appConfig, deployEnvironment: serverDeployEnvironment }; }; exports.askForDeployEnvironmentInfo = askForDeployEnvironmentInfo;