UNPKG

@topgroup/diginext

Version:

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

871 lines (870 loc) 70.4 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 }); /* eslint-disable prettier/prettier */ const runtime_1 = require("@tsoa/runtime"); const class_validator_1 = require("class-validator"); const log_1 = require("diginext-utils/dist/xconsole/log"); const lodash_1 = require("lodash"); const const_1 = require("../config/const"); const ResponseData_1 = require("../interfaces/ResponseData"); const app_helper_1 = require("../modules/apps/app-helper"); const get_app_environment_1 = require("../modules/apps/get-app-environment"); const create_release_from_app_1 = require("../modules/build/create-release-from-app"); const deploy_1 = require("../modules/deploy"); const generate_deployment_name_1 = __importDefault(require("../modules/deploy/generate-deployment-name")); const generate_deployment_v2_1 = require("../modules/deploy/generate-deployment-v2"); const dx_domain_1 = require("../modules/diginext/dx-domain"); const k8s_1 = __importDefault(require("../modules/k8s")); const plugins_1 = require("../plugins"); const env_var_1 = require("../plugins/env-var"); const mongodb_1 = require("../plugins/mongodb"); const slug_1 = require("../plugins/slug"); const services_1 = require("../services"); const AppService_1 = require("../services/AppService"); const BaseController_1 = __importDefault(require("./BaseController")); let AppController = class AppController extends BaseController_1.default { constructor() { super(new AppService_1.AppService()); } /** * List of apps */ async read(queryParams) { let apps = await this.service.find(this.filter, this.options, this.pagination); // console.log("apps :>> ", apps); if ((0, lodash_1.isEmpty)(apps)) return (0, ResponseData_1.respondSuccess)({ data: [] }); return (0, ResponseData_1.respondSuccess)({ data: apps }); } async create(body, queryParams) { try { const newApp = await this.service.create(body, { ...this.options, force: body.force, shouldCreateGitRepo: body.shouldCreateGitRepo, }); // delete body.force; // delete body.shouldCreateGitRepo; return (0, ResponseData_1.respondSuccess)({ data: newApp }); } catch (e) { return (0, ResponseData_1.respondFailure)(`Unable to create new app: ${e}`); } } /** * Create new app from a git repo SSH url */ async createFromSshURL(body) { try { const newApp = await this.service.createWithGitURL(body.sshUrl, body.gitProviderID, { workspace: this.workspace, owner: this.user }, { force: body.force }); return (0, ResponseData_1.respondSuccess)({ data: newApp }); } catch (e) { return (0, ResponseData_1.respondFailure)(e.toString()); } } /** * Import a git repo SSH url & create new app from it */ async importFromGitSshURL(body) { try { const newApp = await this.service.createWithGitURL(body.sshUrl, body.gitProviderID, { workspace: this.workspace, owner: this.user }, { force: body.force, gitBranch: body.gitBranch, isDebugging: false, removeCI: true, }); return (0, ResponseData_1.respondSuccess)({ data: newApp }); } catch (e) { (0, ResponseData_1.respondFailure)(`Unable to import: ${e}`); } } async update(body, queryParams) { try { const apps = await this.service.update(this.filter, body, this.options); return (0, ResponseData_1.respondSuccess)({ data: apps }); } catch (e) { console.error(e); return (0, ResponseData_1.respondFailure)(e.message); } } async delete(queryParams) { try { const deleteRes = await this.service.delete(this.filter); return (0, ResponseData_1.respondSuccess)({ data: deleteRes }); } catch (e) { return (0, ResponseData_1.respondFailure)(`Unable to delete this app: ${e}`); } } /** * List of participants in an app */ async participants(queryParams) { try { const app = await this.service.findOne(this.filter); if (!app) throw new Error(`App not found.`); const participants = await this.service.getParticipants(app, this.options); return (0, ResponseData_1.respondSuccess)({ data: participants }); } catch (e) { return (0, ResponseData_1.respondFailure)(`Unable to get participants: ${e}`); } } /** * Take down all deploy environments of this app on the clusters, then mark this app as "archived" in database. */ async archiveApp(queryParams) { const app = await this.service.findOne(this.filter, this.options); if (!app) return (0, ResponseData_1.respondFailure)(`Unable to archive: app not found.`); try { const archivedApp = await this.service.archiveApp(app, this.ownership); return (0, ResponseData_1.respondSuccess)({ data: archivedApp }); } catch (e) { return (0, ResponseData_1.respondFailure)(`Unable to archive this app: ${e}`); } } /** * Mark this app as "unarchived" in database. */ async unarchiveApp(queryParams) { const app = await this.service.findOne(this.filter, this.options); if (!app) return (0, ResponseData_1.respondFailure)(`Unable to archive: app not found.`); try { const unarchivedApp = await this.service.unarchiveApp(app, this.ownership); return (0, ResponseData_1.respondSuccess)({ data: unarchivedApp }); } catch (e) { return (0, ResponseData_1.respondFailure)(`Unable to archive this app: ${e}`); } } async getAppConfig(queryParams) { const app = await this.service.findOne(this.filter, { populate: ["project", "owner", "workspace"] }); if (!app) return this.filter.owner ? (0, ResponseData_1.respondFailure)({ msg: `Unauthorized.` }) : (0, ResponseData_1.respondFailure)({ msg: `App not found.` }); const appConfig = (0, app_helper_1.getAppConfigFromApp)(app); let result = { status: 1, data: appConfig, messages: [] }; return result; } /** * Get new deploy environment of the application. */ async getDeployEnvironmentV2(queryParams) { const { slug, env } = this.filter; if (!slug) return (0, ResponseData_1.respondFailure)({ msg: `App slug is required.` }); if (!env) return (0, ResponseData_1.respondFailure)({ msg: `Deploy environment name is required.` }); const app = await this.service.findOne({ slug }); if (!app) return this.filter.owner ? (0, ResponseData_1.respondFailure)({ msg: `Unauthorized.` }) : (0, ResponseData_1.respondFailure)({ msg: `App not found.` }); if (!app) return (0, ResponseData_1.respondFailure)({ msg: `App "${slug}" not found.` }); if (!app.deployEnvironment[env]) return (0, ResponseData_1.respondFailure)({ msg: `App "${slug}" doesn't have any deploy environment named "${env}".` }); const deployEnvironment = app.deployEnvironment[env]; let result = (0, ResponseData_1.respondSuccess)({ data: deployEnvironment }); return result; } /** * [V2] Get new deploy environment of the application. */ async getDeployEnvironment(queryParams) { const { slug, env } = this.filter; if (!slug) return (0, ResponseData_1.respondFailure)({ msg: `App slug is required.` }); if (!env) return (0, ResponseData_1.respondFailure)({ msg: `Deploy environment name is required.` }); const app = await this.service.findOne({ slug }); if (!app) return this.filter.owner ? (0, ResponseData_1.respondFailure)({ msg: `Unauthorized.` }) : (0, ResponseData_1.respondFailure)({ msg: `App not found.` }); if (!app) return (0, ResponseData_1.respondFailure)({ msg: `App "${slug}" not found.` }); if (!app.deployEnvironment[env]) return (0, ResponseData_1.respondFailure)({ msg: `App "${slug}" doesn't have any deploy environment named "${env}".` }); const deployEnvironment = app.deployEnvironment[env]; let result = (0, ResponseData_1.respondSuccess)({ data: deployEnvironment }); return result; } /** * [V2] Create new deploy environment of the application. */ async createDeployEnvironmentV2( /** * `REQUIRES` * --- * Deploy environment configuration */ body, queryParams) { const { slug, env } = this.filter; const { DeployEnvironmentService } = await Promise.resolve().then(() => __importStar(require("../services"))); const deployEnvSvc = new DeployEnvironmentService(); return deployEnvSvc.createDeployEnvironment(slug, { env, deployEnvironmentData: body }, this.ownership); } /** * [V2] Update new deploy environment of the application. */ async updateDeployEnvironmentV2( /** * `REQUIRES` * --- * Deploy environment configuration */ body, queryParams) { const { slug, env } = this.filter; return this.updateDeployEnvironment({ appSlug: slug, env, deployEnvironmentData: body }); } /** * [V2] Update new deploy environment of the application. */ async deleteDeployEnvironmentV2(queryParams) { const { _id, id, slug, env } = this.filter; return this.deleteDeployEnvironment({ _id, id, slug, env }); } /** * Create new deploy environment of the application. */ async createDeployEnvironment(body, queryParams) { const { DeployEnvironmentService } = await Promise.resolve().then(() => __importStar(require("../services"))); const deployEnvSvc = new DeployEnvironmentService(); const app = await deployEnvSvc.createDeployEnvironment(body.appSlug, body, this.ownership); return (0, ResponseData_1.respondSuccess)({ data: app }); } /** * Create new deploy environment of the application. */ async updateDeployEnvironment(body, queryParams) { var _a; try { const clusterSvc = new services_1.ClusterService(this.ownership); const releaseSvc = new services_1.ReleaseService(this.ownership); const registrySvc = new services_1.ContainerRegistryService(this.ownership); const { appSlug, env, deployEnvironmentData } = body; if (!appSlug) return (0, ResponseData_1.respondFailure)({ msg: `App slug is required.` }); if (!env) return (0, ResponseData_1.respondFailure)({ msg: `Deploy environment name is required.` }); if (!deployEnvironmentData) return (0, ResponseData_1.respondFailure)({ msg: `Deploy environment configuration is required.` }); // get app data: const app = await this.service.findOne({ slug: appSlug }, { populate: ["project"] }); if (!app) return this.filter.owner ? (0, ResponseData_1.respondFailure)({ msg: `Unauthorized.` }) : (0, ResponseData_1.respondFailure)({ msg: `App not found.` }); if (!app.project) return (0, ResponseData_1.respondFailure)({ msg: `This app is orphan, apps should belong to a project.` }); if (!deployEnvironmentData.imageURL) (0, ResponseData_1.respondFailure)({ msg: `Build image URL is required.` }); const currentDeployEnvData = app.deployEnvironment[env]; // build number if (!deployEnvironmentData.buildTag) deployEnvironmentData.buildTag = app.deployEnvironment[env].buildTag; if (!deployEnvironmentData.buildTag) { const releaseFilter = { appSlug: app.slug, buildStatus: "success", env, active: true }; console.log("updateDeployEnvironment() > releaseFilter :>> ", releaseFilter); let latestRelease = await releaseSvc.findOne(releaseFilter, { populate: ["build"], order: { createdAt: -1 }, ignorable: true }); // "sometime" there are no "active" release, so just get the "success" release instead :) if (!latestRelease) { delete releaseFilter.active; latestRelease = await releaseSvc.findOne(releaseFilter, { populate: ["build"], order: { createdAt: -1 }, ignorable: true }); } if (!latestRelease) return (0, ResponseData_1.respondFailure)(`updateDeployEnvironment() > Release not found (app: "${app.slug}" - env: "${env}")`); const latestBuild = latestRelease.build; if (!latestRelease) return (0, ResponseData_1.respondFailure)(`updateDeployEnvironment() > Latest build not found (app: "${app.slug}" - env: "${env}")`); deployEnvironmentData.buildTag = latestBuild.tag; } if (!deployEnvironmentData.buildTag) return (0, ResponseData_1.respondFailure)({ msg: `Build number (image's tag) is required.` }); // finish checking build number const { buildTag } = deployEnvironmentData; const project = app.project; const { slug: projectSlug } = project; // Check DX quota // TODO: Check quota based on CPU & memory (NEW) // if (deployEnvironmentData.size) { // const quotaRes = await checkQuota(this.workspace, { resourceSize: deployEnvironmentData.size }); // if (!quotaRes.status) return respondFailure(quotaRes.messages.join(". ")); // if (quotaRes.data && quotaRes.data.isExceed) // return respondFailure( // `You've exceeded the limit amount of container size (${quotaRes.data.type} / Max size: ${quotaRes.data.limits.size}x).` // ); // } // Validate deploy environment data: // cluster let cluster; if (deployEnvironmentData.cluster) { cluster = await clusterSvc.findOne({ slug: deployEnvironmentData.cluster }, { subpath: "/all" }); // check if change cluster: if (deployEnvironmentData.cluster !== currentDeployEnvData.cluster) { const { DeployEnvironmentService } = await Promise.resolve().then(() => __importStar(require("../services"))); const deployEnvSvc = new DeployEnvironmentService(); try { await deployEnvSvc.changeCluster(app, env, cluster, { user: this.ownership.owner, workspace: this.ownership.workspace }); } catch (e) { return (0, ResponseData_1.respondFailure)(`Unable to change cluster: ${e.message}`); } } } // no cluster changed -> get current cluster if (!cluster && currentDeployEnvData.cluster) cluster = await clusterSvc.findOne({ slug: currentDeployEnvData.cluster }, { subpath: "/all" }); // namespace const namespace = deployEnvironmentData.namespace; if (cluster && namespace) { // Check if namespace is existed... const isNamespaceExisted = await k8s_1.default.isNamespaceExisted(namespace, { context: cluster.contextName }); if (isNamespaceExisted) return (0, ResponseData_1.respondFailure)({ msg: `Namespace "${namespace}" was existed in "${cluster.name}" cluster, please choose different name or leave empty to use generated namespace name.`, }); } // container registry if (deployEnvironmentData.registry) { const registry = await registrySvc.findOne({ slug: deployEnvironmentData.registry }, { subpath: "/all" }); if (!registry) return (0, ResponseData_1.respondFailure)({ msg: `Container Registry "${deployEnvironmentData.registry}" is not existed.` }); } // Domains & SSL certificate... // if (!deployEnvironmentData.domains) deployEnvironmentData.domains = []; if (deployEnvironmentData.useGeneratedDomain) { if (!cluster) return (0, ResponseData_1.respondFailure)(`Param "cluster" must be specified if you want to use "useGeneratedDomain".`); const recordName = `${projectSlug}-${appSlug}.${env}`; // check if the domain is existed: const existedRecord = await (0, dx_domain_1.dxGetDomainRecordByName)({ name: recordName, type: "A" }, this.workspace.dx_key).catch(console.error); if (existedRecord) { // update the domain record await (0, dx_domain_1.dxUpdateDomainRecord)({ name: recordName, type: "A" }, { name: recordName, data: cluster.primaryIP, userId: this.user.dxUserId }, this.workspace.dx_key).catch(console.error); const domain = `${recordName}.${const_1.DIGINEXT_DOMAIN}`; deployEnvironmentData.domains = [domain, ...deployEnvironmentData.domains]; } else { // create the domain record const { status, messages, data: { domain }, } = await (0, dx_domain_1.dxCreateDomain)({ name: recordName, data: cluster.primaryIP, userId: this.user.dxUserId }, this.workspace.dx_key); if (!status) (0, log_1.logWarn)(`[APP_CONTROLLER] ${messages.join(". ")}`); deployEnvironmentData.domains = status ? [domain, ...deployEnvironmentData.domains] : deployEnvironmentData.domains; } } // Exposing ports, enable/disable CDN, and select Ingress type if (!(0, lodash_1.isUndefined)(deployEnvironmentData.port)) { if (!(0, lodash_1.isNumber)(deployEnvironmentData.port)) return (0, ResponseData_1.respondFailure)({ msg: `Param "port" must be a number.` }); } if (!(0, lodash_1.isUndefined)(deployEnvironmentData.cdn) && !(0, lodash_1.isBoolean)(deployEnvironmentData.cdn)) { return (0, ResponseData_1.respondFailure)({ msg: `Param "cdn" must be a boolean.` }); } // deployEnvironmentData.ingress = "nginx"; deployEnvironmentData.lastUpdatedBy = this.user.slug; // create deploy environment in the app: let updateDeployEnvData = {}; Object.keys(deployEnvironmentData).map((key) => (updateDeployEnvData[`deployEnvironment.${env}.${key}`] = deployEnvironmentData[key])); let updatedApp = await this.service.updateOne({ slug: appSlug }, updateDeployEnvData); // console.log("updatedApp :>> ", updatedApp); if (!updatedApp) return (0, ResponseData_1.respondFailure)({ msg: `Failed to create "${env}" deploy environment.` }); const appConfig = await (0, app_helper_1.getAppConfigFromApp)(updatedApp); if (!deployEnvironmentData.tlsSecret) { if (((_a = appConfig.deployEnvironment[env].domains) === null || _a === void 0 ? void 0 : _a.length) > 0 && !appConfig.deployEnvironment[env].tlsSecret) { if (deployEnvironmentData.ssl === "letsencrypt") { deployEnvironmentData.tlsSecret = (0, slug_1.makeSlug)(deployEnvironmentData.domains[0]); } else if (deployEnvironmentData.ssl === "custom") { if (!deployEnvironmentData.tlsSecret) deployEnvironmentData.tlsSecret = (0, slug_1.makeSlug)(deployEnvironmentData.domains[0]); } else { deployEnvironmentData.tlsSecret = ""; } } // update app again updateDeployEnvData = {}; Object.keys(deployEnvironmentData).map((key) => (updateDeployEnvData[`deployEnvironment.${env}.${key}`] = deployEnvironmentData[key])); updatedApp = await this.service.updateOne({ slug: appSlug }, updateDeployEnvData); } // generate deployment files and apply new config let deployment = await (0, generate_deployment_v2_1.generateDeploymentV2)({ appSlug: app.slug, env, username: this.user.slug, workspace: this.workspace, buildTag: buildTag, }); const { deploymentContent } = deployment; // update data to deploy environment: let serverDeployEnvironment = await (0, get_app_environment_1.getDeployEvironmentByApp)(updatedApp, env); serverDeployEnvironment.deploymentYaml = deploymentContent; serverDeployEnvironment.updatedAt = new Date(); serverDeployEnvironment.lastUpdatedBy = this.user.username; serverDeployEnvironment.deploymentName = deployment.deploymentName; if (serverDeployEnvironment.owner) serverDeployEnvironment.owner = mongodb_1.MongoDB.toString(this.user._id); if (serverDeployEnvironment.ownerSlug) serverDeployEnvironment.ownerSlug = this.user.slug; // Update {user}, {project}, {environment} to database before rolling out const updatedAppData = { deployEnvironment: updatedApp.deployEnvironment || {} }; updatedAppData.lastUpdatedBy = this.user.username; updatedAppData.deployEnvironment[env] = serverDeployEnvironment; updatedApp = await this.service.updateOne({ slug: app.slug }, updatedAppData); if (!updatedApp) return (0, ResponseData_1.respondFailure)("Unable to update " + env + " environment of " + app.slug + "app."); if (!cluster) return (0, ResponseData_1.respondSuccess)({ data: updatedApp.deployEnvironment[env] }); // get workloads on cluster const mainAppName = await (0, generate_deployment_name_1.default)(app); const deprecatedMainAppName = (0, slug_1.makeSlug)(app === null || app === void 0 ? void 0 : app.name).toLowerCase(); let workloads = await k8s_1.default.getDeploysByFilter(serverDeployEnvironment.namespace, { context: cluster.contextName, filterLabel: `main-app=${mainAppName}`, }); // Fallback support for deprecated mainAppName const deprecatedWorkloads = await k8s_1.default.getDeploysByFilter(serverDeployEnvironment.namespace, { context: cluster.contextName, filterLabel: `main-app=${deprecatedMainAppName}`, }); if ((deprecatedWorkloads === null || deprecatedWorkloads === void 0 ? void 0 : deprecatedWorkloads.length) > 0) workloads.push(...deprecatedWorkloads); console.log("AppController > updateDeployEnvironment() > workloads :>> ", workloads); console.log("AppController > updateDeployEnvironment() > workloads.length :>> ", workloads === null || workloads === void 0 ? void 0 : workloads.length); // if (workloads && workloads.length > 0) { console.log(`AppController > updateDeployEnvironment() > Applying new deployment yaml...`); // create new release and roll out const release = await (0, create_release_from_app_1.createReleaseFromApp)(updatedApp, env, buildTag, { author: this.user, cliVersion: (0, plugins_1.currentVersion)(), workspace: this.workspace, }); // apply deployment YAML console.log("AppController > updateDeployEnvironment() > cluster :>> ", cluster.slug); console.log("AppController > updateDeployEnvironment() > namespace :>> ", namespace); console.log("AppController > updateDeployEnvironment() > deploymentContent :>> ", deploymentContent); const applyResult = await k8s_1.default.kubectlApplyContent(deploymentContent, { context: cluster.contextName }); console.log("AppController > updateDeployEnvironment() > Applied deployment yaml :>> ", applyResult); // delete deprecated workloads if ((deprecatedWorkloads === null || deprecatedWorkloads === void 0 ? void 0 : deprecatedWorkloads.length) > 0) { const deleteResult = await Promise.all(deprecatedWorkloads.map((workload) => k8s_1.default.deleteDeploy(workload.metadata.name, workload.metadata.namespace, { context: cluster.contextName }))); console.log("AppController > updateDeployEnvironment() > Deleted deprecated deployments :>> ", deleteResult); } return (0, ResponseData_1.respondSuccess)({ data: updatedApp.deployEnvironment[env], msg: `Updated "${env.toUpperCase()}" deploy environment successfully.`, }); } catch (e) { console.error(`AppController > updateDeployEnvironment() :>>`, e); return (0, ResponseData_1.respondFailure)(`Unable to update this deploy environment: ${e}`); } } /** * Delete a deploy environment of the application. */ async deleteDeployEnvironment(body) { let result = { status: 1, data: {}, messages: [] }; const messages = []; // input validation let { _id, id, slug, env } = body; if (!id && _id) id = _id; if (!id && !slug) return (0, ResponseData_1.respondFailure)(`App "id" or "slug" is required.`); if (!env) return (0, ResponseData_1.respondFailure)(`App "env" is required.`); // find the app const appFilter = typeof id != "undefined" ? { _id: id } : { slug }; const app = await this.service.findOne(appFilter, { populate: ["project"] }); // check if the environment is existed if (!app) return (0, ResponseData_1.respondFailure)({ msg: `App not found.` }); // take down the deploy environment const { DeployEnvironmentService } = await Promise.resolve().then(() => __importStar(require("../services"))); const deployEnvSvc = new DeployEnvironmentService(); await deployEnvSvc.takeDownDeployEnvironment(app, env.toString()).catch((e) => { console.error(`deleteDeployEnvironment() :>>`, e); messages.push(`Unable to take down before deleting this deploy environment: ${e}`); }); // delete diginext domain record (if any) const deployEnvironment = app.deployEnvironment[env]; if (deployEnvironment.domains && deployEnvironment.domains.filter((domain) => domain.indexOf(const_1.DIGINEXT_DOMAIN) > -1).length > 0) { if (this.workspace && this.workspace.dx_key) { for (const domain of deployEnvironment.domains.filter((_domain) => _domain.indexOf(const_1.DIGINEXT_DOMAIN) > -1)) { const recordName = domain.replace(const_1.DIGINEXT_DOMAIN, ""); (0, dx_domain_1.dxDeleteDomainRecord)({ name: recordName, type: "A" }, this.workspace.dx_key).catch(console.error); } } else { console.error("AppService > delete() > Delete domain A record > No WORKSPACE or DX_KEY found."); } } // update the app (delete the deploy environment) const updatedApp = await this.service.updateOne(appFilter, { $unset: { [`deployEnvironment.${env}`]: true }, }, { raw: true }); if (this.options.isDebugging) (0, log_1.log)(`[BaseController] deleted Environment`, { appFilter }, ` :>>`, { updatedApp }); // respond the results return (0, ResponseData_1.respondSuccess)({ data: updatedApp, msg: messages }); } /** * Get list of variables on the deploy environment of the application. */ async getEnvVarsOnDeployEnvironment(queryParams) { const { slug, env } = this.filter; if (!slug) return { status: 0, messages: [`App slug (slug) is required.`] }; if (!env) return { status: 0, messages: [`Deploy environment name (env) is required.`] }; const app = await this.service.findOne({ slug }); if (!app) return this.filter.owner ? (0, ResponseData_1.respondFailure)({ msg: `Unauthorized.` }) : (0, ResponseData_1.respondFailure)({ msg: `App not found.` }); const envVars = (0, env_var_1.formatEnvVars)(app.deployEnvironment[env].envVars || []); let result = { status: 1, data: envVars, messages: [] }; return result; } /** * Create new variables on the deploy environment of the application. */ async createEnvVarsOnDeployEnvironment(body, queryParams) { try { let { slug, env, envVars } = body; if (!slug) return { status: 0, messages: [`App slug (slug) is required.`] }; if (!env) return { status: 0, messages: [`Deploy environment name (env) is required.`] }; if (!envVars) return { status: 0, messages: [`Array of environment variables (envVars) is required.`] }; const clusterSvc = new services_1.ClusterService(this.ownership); const appSvc = new AppService_1.AppService(this.ownership); const app = await appSvc.findOne({ ...this.filter, slug }, { populate: ["project"] }); if (!app) return this.filter.owner ? (0, ResponseData_1.respondFailure)({ msg: `Unauthorized.` }) : (0, ResponseData_1.respondFailure)({ msg: `App not found.` }); const mainAppName = await (0, generate_deployment_name_1.default)(app); const deprecatedMainAppName = (0, slug_1.makeSlug)(app === null || app === void 0 ? void 0 : app.name).toLowerCase(); const deployEnvironment = app.deployEnvironment[env]; if (!deployEnvironment) return (0, ResponseData_1.respondFailure)(`Deploy environment "${env}" is not existed in "${slug}" app.`); if (!deployEnvironment.namespace) return (0, ResponseData_1.respondFailure)(`Namespace not existed in deploy environment "${env}" of "${slug}" app.`); if (!deployEnvironment.cluster) return (0, ResponseData_1.respondFailure)(`Cluster not existed in deploy environment "${env}" of "${slug}" app.`); const { namespace, cluster: clusterSlug } = deployEnvironment; const cluster = await clusterSvc.findOne({ slug: clusterSlug }, { subpath: "/all" }); if (!cluster) return (0, ResponseData_1.respondFailure)(`Cluster not found: "${clusterSlug}"`); const newEnvVars = (0, class_validator_1.isJSON)(envVars) ? JSON.parse(envVars) : (0, lodash_1.isArray)(envVars) ? envVars : []; // console.log("updateEnvVars :>> ", updateEnvVars); let [updatedApp] = await appSvc.update({ slug }, { [`deployEnvironment.${env}.envVars`]: (0, env_var_1.formatEnvVars)(newEnvVars) }); if (!updatedApp) return { status: 0, messages: [`Failed to create "${env}" deploy environment.`] }; // generate deployment files and apply new config if (updatedApp.deployEnvironment && updatedApp.deployEnvironment[env] && updatedApp.deployEnvironment[env].deploymentYaml) { const { BUILD_TAG, IMAGE_NAME } = (0, deploy_1.fetchDeploymentFromContent)(updatedApp.deployEnvironment[env].deploymentYaml); let deployment = await (0, generate_deployment_v2_1.generateDeploymentV2)({ appSlug: app.slug, env, username: this.user.slug, workspace: this.workspace, buildTag: BUILD_TAG, buildImage: IMAGE_NAME, }); const { endpoint, deploymentContent } = deployment; // update data to deploy environment: let serverDeployEnvironment = await (0, get_app_environment_1.getDeployEvironmentByApp)(updatedApp, env); serverDeployEnvironment.deploymentYaml = deploymentContent; serverDeployEnvironment.updatedAt = new Date(); serverDeployEnvironment.lastUpdatedBy = this.user.username; // Update {user}, {project}, {environment} to database before rolling out const updatedAppData = { deployEnvironment: updatedApp.deployEnvironment || {} }; updatedAppData.lastUpdatedBy = this.user.username; updatedAppData.deployEnvironment[env] = serverDeployEnvironment; updatedApp = await appSvc.updateOne({ slug: app.slug }, updatedAppData); if (!updatedApp) return (0, ResponseData_1.respondFailure)("Unable to apply new domain configuration for " + env + " environment of " + app.slug + "app."); } // Set environment variables to deployment in the cluster // if the workload has been deployed before -> update the environment variables let workloads = await k8s_1.default.getDeploysByFilter(namespace, { context: cluster.contextName, filterLabel: `main-app=${mainAppName}`, }); // Fallback support for deprecated mainAppName if (!workloads || workloads.length === 0) { workloads = await k8s_1.default.getDeploysByFilter(namespace, { context: cluster.contextName, filterLabel: `main-app=${deprecatedMainAppName}`, }); } if (workloads && workloads.length > 0) { try { const setEnvVarsRes = await k8s_1.default.setEnvVarByFilter(newEnvVars, namespace, { context: cluster.contextName, filterLabel: `main-app=${mainAppName}`, }); console.log("setEnvVarsRes :>> ", setEnvVarsRes); } catch (e) { return (0, ResponseData_1.respondFailure)(e.toString()); } } return (0, ResponseData_1.respondSuccess)({ data: updatedApp.deployEnvironment[env].envVars }); } catch (e) { return (0, ResponseData_1.respondFailure)(e.toString()); } } /** * Update environment variables on the deploy environment. */ async updateEnvVarsOnDeployEnvironment(body, queryParams) { let { slug, env, envVars } = body; if (!slug) return (0, ResponseData_1.respondFailure)(`App slug (slug) is required.`); if (!env) return (0, ResponseData_1.respondFailure)(`Deploy environment name (env) is required.`); if (!envVars) return (0, ResponseData_1.respondFailure)(`Array of environment variables (envVars) is required.`); const app = await this.service.findOne({ ...this.filter, slug }, { populate: ["project"] }); if (!app) return this.filter.owner ? (0, ResponseData_1.respondFailure)({ msg: `Unauthorized.` }) : (0, ResponseData_1.respondFailure)({ msg: `App not found.` }); try { const { DeployEnvironmentService } = await Promise.resolve().then(() => __importStar(require("../services"))); const deployEnvSvc = new DeployEnvironmentService(this.ownership); const data = await deployEnvSvc.updateEnvVars(app, env, envVars); return (0, ResponseData_1.respondSuccess)({ data: data.app, msg: data.message }); } catch (e) { return (0, ResponseData_1.respondFailure)(e.toString()); } } /** * Delete variables on the deploy environment of the application. */ async deleteEnvVarsOnDeployEnvironment(body, queryParams) { let { slug, env } = body; if (!slug) return { status: 0, messages: [`App slug (slug) is required.`] }; if (!env) return { status: 0, messages: [`Deploy environment name (env) is required.`] }; const app = await this.service.findOne({ ...this.filter, slug }, { populate: ["project"] }); if (!app) return this.filter.owner ? (0, ResponseData_1.respondFailure)({ msg: `Unauthorized.` }) : (0, ResponseData_1.respondFailure)({ msg: `App not found.` }); if (!app.deployEnvironment[env]) return { status: 0, messages: [`App "${slug}" doesn't have any deploy environment named "${env}".`] }; if ((0, lodash_1.isEmpty)(app.deployEnvironment[env])) return { status: 0, messages: [`This deploy environment (${env}) of "${slug}" app doesn't have any environment variables.`] }; const mainAppName = await (0, generate_deployment_name_1.default)(app); const deprecatedMainAppName = (0, slug_1.makeSlug)(app === null || app === void 0 ? void 0 : app.name).toLowerCase(); const envVars = (0, env_var_1.formatEnvVars)(app.deployEnvironment[env].envVars); const deployEnvironment = app.deployEnvironment[env]; if (!deployEnvironment) return (0, ResponseData_1.respondFailure)(`Deploy environment "${env}" is not existed in "${slug}" app.`); if (!deployEnvironment.namespace) return (0, ResponseData_1.respondFailure)(`Namespace not existed in deploy environment "${env}" of "${slug}" app.`); if (!deployEnvironment.cluster) return (0, ResponseData_1.respondFailure)(`Cluster not existed in deploy environment "${env}" of "${slug}" app.`); const { namespace, cluster: clusterSlug } = deployEnvironment; const clusterSvc = new services_1.ClusterService(this.ownership); const cluster = await clusterSvc.findOne({ slug: clusterSlug }, { subpath: "/all" }); if (!cluster) return (0, ResponseData_1.respondFailure)(`Cluster not found: "${clusterSlug}"`); // check if deployment is existed in the cluster / namespace let workloads = await k8s_1.default.getDeploysByFilter(namespace, { context: cluster.contextName, filterLabel: `main-app=${mainAppName}`, }); // Fallback support for deprecated mainAppName if (!workloads || workloads.length === 0) { workloads = await k8s_1.default.getDeploysByFilter(namespace, { context: cluster.contextName, filterLabel: `main-app=${deprecatedMainAppName}`, }); } if (!workloads || workloads.length === 0) return (0, ResponseData_1.respondFailure)(`There are no deployments in "${namespace}" namespace of "${clusterSlug}" cluster.`); // delete in database let [updatedApp] = await this.service.update({ _id: app._id }, { [`deployEnvironment.${env}.envVars`]: [] }); if (!updatedApp) return { status: 0, messages: [`Failed to delete environment variables in "${env}" deploy environment of "${slug}" app.`] }; // Set environment variables to deployment in the cluster try { const deleteEnvVarsRes = await k8s_1.default.deleteEnvVarByFilter(envVars.map((_var) => _var.name), namespace, { context: cluster.contextName, filterLabel: `main-app=${mainAppName}`, }); let result = { status: 1, data: (0, env_var_1.formatEnvVars)(updatedApp.deployEnvironment[env].envVars), messages: [deleteEnvVarsRes] }; return result; } catch (e) { return (0, ResponseData_1.respondFailure)(e.toString()); } } /** * Update a variable on the deploy environment of the application. */ async addEnvironmentDomain(body, queryParams) { // validate let { env, domains } = body; if (!env) return { status: 0, messages: [`Deploy environment name (env) is required.`] }; if (!domains) return { status: 0, messages: [`Array of domains is required.`] }; // find app const app = await this.service.findOne(this.filter, { populate: ["project"] }); if (!app) return this.filter.owner ? (0, ResponseData_1.respondFailure)({ msg: `Unauthorized.` }) : (0, ResponseData_1.respondFailure)({ msg: `App not found.` }); if (!app.deployEnvironment) return (0, ResponseData_1.respondFailure)(`App "${app.slug}" doesn't have any deploy environments.`); if (!app.deployEnvironment[env]) return { status: 0, messages: [`App "${app.slug}" doesn't have any deploy environment named "${env}".`] }; const clusterSvc = new services_1.ClusterService(this.ownership); const clusterSlug = app.deployEnvironment[env].cluster; const cluster = await clusterSvc.findOne({ slug: clusterSlug }, { subpath: "/all" }); if (!cluster) return (0, ResponseData_1.respondFailure)(`Cluster not found: "${clusterSlug}"`); const mainAppName = await (0, generate_deployment_name_1.default)(app); const deprecatedMainAppName = (0, slug_1.makeSlug)(app === null || app === void 0 ? void 0 : app.name).toLowerCase(); // validate domain for (const domain of domains) { if (domain.indexOf(`http`) > -1) return (0, ResponseData_1.respondFailure)(`Invalid domain, no "http://" or "https://" needed`); if (domain.indexOf(`/`) > -1) return (0, ResponseData_1.respondFailure)(`Invalid domain, no special characters.`); } // check if added domains are existed let existedDomain; const currentDomains = app.deployEnvironment[env].domains || []; domains.forEach((domain) => { if (currentDomains.includes(domain)) existedDomain = domain; }); if (existedDomain) return (0, ResponseData_1.respondFailure)(`Domain "${existedDomain}" is existed.`); // add new domains const updateData = {}; updateData[`deployEnvironment.${env}.domains`] = [...(app.deployEnvironment[env].domains || []), ...domains]; // update diginext domain record (if any) domains .filter((domain) => domain.indexOf(const_1.DIGINEXT_DOMAIN) > -1) .forEach(async (domain) => { var _a; const recordName = (0, lodash_1.trim)(domain.replace(const_1.DIGINEXT_DOMAIN, ""), "."); // check existed const existedRecord = await (0, dx_domain_1.dxGetDomainRecordByName)({ name: recordName, type: "A" }, this.workspace.dx_key).catch(console.error); if (existedRecord && ((_a = existedRecord.data.domain_records) === null || _a === void 0 ? void 0 : _a.length) > 0) { // if existed, update it (0, dx_domain_1.dxUpdateDomainRecord)({ name: recordName, type: "A" }, { data: cluster.primaryIP, userId: this.user.dxUserId }, this.workspace.dx_key, { isDebugging: this.options.isDebugging }).catch(console.error); } else { // if not existed, create it (0, dx_domain_1.dxCreateDomain)({ name: recordName, data: cluster.primaryIP, userId: this.user.dxUserId }, this.workspace.dx_key, { isDebugging: this.options.isDebugging, }).catch(console.error); } }); let updatedApp = await this.service.updateOne({ slug: app.slug }, updateData); if (!updatedApp) return (0, ResponseData_1.respondFailure)("Failed to update new domains to " + app.slug + "app."); console.log("AppController > addEnvironmentDomain() > updatedApp.deployEnvironment[env] :>> ", updatedApp.deployEnvironment[env]); // generate deployment files and apply new config const { BUILD_TAG, IMAGE_NAME } = (0, deploy_1.fetchDeploymentFromContent)(updatedApp.deployEnvironment[env].deploymentYaml); console.log("AppController > addEnvironmentDomain() > BUILD_TAG :>> ", BUILD_TAG); console.log("AppController > addEnvironmentDomain() > this.user :>> ", this.user); let deployment = await (0, generate_deployment_v2_1.generateDeploymentV2)({ appSlug: app.slug, env, username: this.user.slug, workspace: this.workspace, buildTag: BUILD_TAG, buildImage: IMAGE_NAME, }); const { endpoint, deploymentContent } = deployment; // update data to deploy environment: let serverDeployEnvironment = await (0, get_app_environment_1.getDeployEvironmentByApp)(updatedApp, env); serverDeployEnvironment.deploymentYaml = deploymentContent; serverDeployEnvironment.updatedAt = new Date(); serverDeployEnvironment.lastUpdatedBy = this.user.username; // Update {user}, {project}, {environment} to database before rolling out const updatedAppData = { deployEnvironment: updatedApp.deployEnvironment || {} }; updatedAppData.lastUpdatedBy = this.user.username; updatedAppData.deployEnvironment[env] = serverDeployEnvironment; updatedApp = await this.service.updateOne({ slug: app.slug }, updatedAppData); if (!updatedApp) return (0, ResponseData_1.respondFailure)("Unable to apply new domain configuration for " + env + " environment of " + app.slug + "app."); let workloads = await k8s_1.default.getDeploysByFilter(serverDeployEnvironment.namespace, { context: cluster.contextName, filterLabel: `main-app=${mainAppName}`, }); // Fallback support for deprecated mainAppName let deprecatedWorkloads = await k8s_1.default.getDeploysByFilter(serverDeployEnvironment.namespace, { context: cluster.contextName, filterLabel: `main-app=${deprecatedMainAppName}`, }); if ((deprecatedWorkloads === null || deprecatedWorkloads === void 0 ? void 0 : deprecatedWorkloads.length) > 0) workloads.push(...deprecatedWorkloads); console.log("AppController > addEnvironmentDomain() > workloads :>> ", workloads); console.log("AppController > addEnvironmentDomain() > workloads.length :>> ", workloads === null || workloads === void 0 ? void 0 : workloads.length); if (workloads && workloads.length > 0) { // create new release and roll out const release = await (0, create_release_from_app_1.createReleaseFromApp)(updatedApp, env, BUILD_TAG, { author: this.user, cliVersion: (0, plugins_1.currentVersion)(), workspace: this.workspace, }); // apply deployment YAML await k8s_1.default.kubectlApplyContent(deployment.deploymentContent, { context: cluster.contextName }); } return (0, ResponseData_1.respondSuccess)({ data: updatedApp }); } /** * View app's container logs */ async viewLogs(queryParams) {