@topgroup/diginext
Version:
A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.
871 lines (870 loc) • 70.4 kB
JavaScript
"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) {