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