@topgroup/diginext
Version:
A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.
539 lines (538 loc) • 28.5 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.rolloutV2 = exports.cleanUpNamespace = void 0;
const chalk_1 = __importDefault(require("chalk"));
const log_1 = require("diginext-utils/dist/xconsole/log");
const fs_1 = require("fs");
const js_yaml_1 = __importDefault(require("js-yaml"));
const lodash_1 = require("lodash");
const path_1 = __importDefault(require("path"));
const app_config_1 = require("../../app.config");
const const_1 = require("../../config/const");
const k8s_1 = __importDefault(require("../../modules/k8s"));
const kubectl_1 = require("../../modules/k8s/kubectl");
const plugins_1 = require("../../plugins");
const mongodb_1 = require("../../plugins/mongodb");
const slug_1 = require("../../plugins/slug");
const services_1 = require("../../services");
const generate_deployment_name_1 = __importDefault(require("./generate-deployment-name"));
const mark_release_as_active_1 = require("./mark-release-as-active");
const deployReplicas = 2;
const checkDeploymentReady = async (options) => {
const { namespace, appName, appVersion, replicas = 1, onUpdate, isDebugging = false, context } = options;
if (isDebugging)
(0, log_1.log)(`checkDeploymentReady() :>>`, options);
const filterLabel = `main-app=${appName}${appVersion ? `,app-version=${appVersion}` : ""}`;
let pods = await k8s_1.default.getPods(namespace, {
context,
filterLabel,
metrics: false,
isDebugging,
skipOnError: true,
}).catch((e) => {
(0, log_1.logError)(`[CHECK DEPLOYMENT READY] Error: ${e}`);
return [];
});
// Skip crashed pods
if (options.skipCrashedPods) {
console.log("deploy-rollout.ts > checkDeploymentReady() > pods before :>>", pods);
pods = pods.filter((pod) => !pod.status.containerStatuses.some((containerStatus) => { var _a; return ((_a = containerStatus.state.waiting) === null || _a === void 0 ? void 0 : _a.reason) === "CrashLoopBackOff"; }));
console.log("deploy-rollout.ts > checkDeploymentReady() > pods after :>>", pods);
}
if (!pods || pods.length == 0) {
const msg = `Unable to check "${appName}" deployment:\n- Namespace: ${namespace}\n- Context: ${context}\n- Reason: Selected pods not found: ${filterLabel}.`;
if (onUpdate)
onUpdate(msg);
return false;
}
let isReady = false;
let countReady = 0;
// if (isDebugging) log(`[ROLL OUT V2] "${appVersion}" > checking ${pods.length} pods > pod.status.conditions:`);
try {
pods.forEach((pod) => {
var _a, _b, _c, _d, _e, _f;
if (isDebugging)
(0, log_1.log)((_b = (_a = pod.status) === null || _a === void 0 ? void 0 : _a.conditions) === null || _b === void 0 ? void 0 : _b.map((c) => `- ${c.type}: ${c.status} (Reason: "${c.reason}")`).join("\n"));
(_d = (_c = pod.status) === null || _c === void 0 ? void 0 : _c.conditions) === null || _d === void 0 ? void 0 : _d.filter((condition) => condition.type === "Ready").map((condition) => {
if (condition.status === "True")
countReady++;
});
(_f = (_e = pod.status) === null || _e === void 0 ? void 0 : _e.containerStatuses) === null || _f === void 0 ? void 0 : _f.map((containerStatus) => {
if (containerStatus.restartCount > 0)
throw new Error(`App is unable to start up due to some unexpected errors.`);
});
});
}
catch (e) {
if (onUpdate)
onUpdate(e.message);
return false;
}
if (countReady >= replicas)
isReady = true;
// notify to the dashboard:
const msg = `Checking is "${appName}" deployment ready (${countReady}/${replicas}): ${isReady}`;
(0, log_1.log)(msg);
if (onUpdate)
onUpdate(msg);
return isReady;
};
/**
* Clean up namespace's resources by app version
* @param cluster - Cluster
* @param appVersion - App's version
*/
async function cleanUpNamespace(cluster, namespace, appName, appVersion) {
const { contextName: context } = cluster;
// Clean up Prerelease YAML
const cleanUpCommands = [];
// Delete INGRESS to optimize cluster
cleanUpCommands.push(k8s_1.default.deleteIngressByFilter(namespace, {
context,
skipOnError: true,
filterLabel: `main-app=${appName},app-version!=${appVersion}`,
}));
// Delete Prerelease SERVICE to optimize cluster
cleanUpCommands.push(k8s_1.default.deleteServiceByFilter(namespace, { context, filterLabel: `main-app=${appName},app-version!=${appVersion}` }));
// Clean up Prerelease Deployments
cleanUpCommands.push(k8s_1.default.deleteDeploymentsByFilter(namespace, { context, filterLabel: `main-app=${appName},app-version!=${appVersion}` }));
// Clean up immediately & just ignore if any errors
let data;
for (const cmd of cleanUpCommands) {
try {
data = await cmd;
}
catch (e) {
(0, log_1.logWarn)(`[CLEAN UP] Ignore command: ${e}`);
}
}
// * Print success:
let msg = `🎉 NAMESPACE HAS BEEN CLEANED UP SUCCESSFULLY 🎉`;
(0, log_1.logSuccess)(msg);
return { error: null, data };
}
exports.cleanUpNamespace = cleanUpNamespace;
/**
* Roll out a release (V2)
* @param releaseId - Release ID
*/
async function rolloutV2(releaseId, options = {}) {
const { DB } = await Promise.resolve().then(() => __importStar(require("../../modules/api/DB")));
const { onUpdate } = options;
let releaseData = await DB.updateOne("release", { _id: releaseId }, { status: "in_progress" }, { populate: ["owner", "workspace"] });
if ((0, lodash_1.isEmpty)(releaseData)) {
const error = `Unable to roll out: Release "${releaseId}" not found.`;
if (onUpdate)
onUpdate(error);
return { error };
}
const { slug: releaseSlug, projectSlug, // ! This is not PROJECT_ID of Google Cloud provider
cluster: clusterSlug, appSlug, build: buildId, buildNumber, deploymentYaml, endpoint: endpointUrl, namespace, env, message, } = releaseData;
// webhook
const owner = releaseData.owner;
const workspace = releaseData.workspace;
const webhookSvc = new services_1.WebhookService();
webhookSvc.ownership = { owner, workspace };
const webhook = await DB.findOne("webhook", { release: releaseId });
// log(`Rolling out the release: "${releaseSlug}" (ID: ${id})`);
if (onUpdate)
onUpdate(`Rolling out the release: "${releaseSlug}" (ID: ${releaseId})`);
// get the app
const app = await DB.findOne("app", { slug: appSlug }, { populate: ["project"] });
if (!app && onUpdate) {
const error = `Unable to roll out: app "${appSlug}" not found.`;
onUpdate(error);
// update release as "failed"
await DB.update("release", { _id: releaseId }, { status: "failed" }, { select: ["_id", "status"] }).catch(console.error);
// Update "deployStatus" of a build to success
await DB.update("build", { _id: buildId }, { deployStatus: "failed" }, { select: ["_id", "deployStatus"] }).catch(console.error);
return { error };
}
// log(`Rolling out > app:`, app);
const deprecatedMainAppName = (0, slug_1.makeSlug)(app === null || app === void 0 ? void 0 : app.name).toLowerCase();
const mainAppName = await (0, generate_deployment_name_1.default)(app);
const deployEnvironment = app.deployEnvironment[env];
/**
* App's version (for service & deployment selector)
*/
const appVersion = releaseData.appVersion || `${mainAppName}-${buildNumber}`;
// log(`Rolling out > mainAppName:`, mainAppName);
// authenticate cluster's provider & switch kubectl to that cluster:
const cluster = await DB.findOne("cluster", { slug: clusterSlug }, { subpath: "/all" });
if (!cluster) {
(0, log_1.logError)(`Cluster "${clusterSlug}" not found.`);
// dispatch/trigger webhook
if (webhook)
webhookSvc.trigger(mongodb_1.MongoDB.toString(webhook._id), "failed");
// update release as "failed"
await DB.update("release", { _id: releaseId }, { status: "failed" }, { select: ["_id", "status"] }).catch(console.error);
// Update "deployStatus" of a build to success
await DB.update("build", { _id: buildId }, { deployStatus: "failed" }, { select: ["_id", "deployStatus"] }).catch(console.error);
return { error: `Cluster "${clusterSlug}" not found.` };
}
try {
await k8s_1.default.authCluster(cluster, { ownership: { owner, workspace } });
// log(`Rolling out > Checked connectivity of "${clusterSlug}" cluster.`);
}
catch (e) {
const error = `Unable to authenticate the cluster: ${e.message}`;
(0, log_1.logError)(`[ROLL_OUT] ${error}`);
// dispatch/trigger webhook
if (webhook)
webhookSvc.trigger(mongodb_1.MongoDB.toString(webhook._id), "failed");
// update release as "failed"
await DB.update("release", { _id: releaseId }, { status: "failed" }, { select: ["_id", "status"] }).catch(console.error);
// Update "deployStatus" of a build to success
await DB.update("build", { _id: buildId }, { deployStatus: "failed" }, { select: ["_id", "deployStatus"] }).catch(console.error);
return { error };
}
const { contextName: context } = cluster;
if (options === null || options === void 0 ? void 0 : options.isDebugging)
(0, log_1.log)(`Rolling out > Connected to "${clusterSlug}" cluster.`);
// create temporary directory to store release's yaml
const tmpDir = path_1.default.resolve(const_1.CLI_DIR, `storage/releases/${releaseSlug}`);
if (!(0, fs_1.existsSync)(tmpDir))
(0, fs_1.mkdirSync)(tmpDir, { recursive: true });
// ! NEW WAY -> LESS DOWNTIME WHEN ROLLING OUT NEW DEPLOYMENT !
/**
* Check if there is any prod namespace, if not -> create one
*/
const isNsExisted = await k8s_1.default.isNamespaceExisted(namespace, { context });
if (!isNsExisted) {
(0, log_1.log)(`Namespace "${namespace}" not found, creating one...`);
if (onUpdate)
onUpdate(`Namespace "${namespace}" not found, creating one...`);
const createNsRes = await k8s_1.default.createNamespace(namespace, { context });
if (!createNsRes) {
const err = `Unable to create new namespace: ${namespace} (Cluster: ${clusterSlug} / Namespace: ${namespace} / App: ${appSlug} / Env: ${env})`;
(0, log_1.logError)(`[ROLL_OUT]`, err);
if (onUpdate)
onUpdate(err);
// dispatch/trigger webhook
if (webhook)
webhookSvc.trigger(mongodb_1.MongoDB.toString(webhook._id), "failed");
// update release as "failed"
await DB.update("release", { _id: releaseId }, { status: "failed" }, { select: ["_id", "status"] }).catch(console.error);
// Update "deployStatus" of a build to success
await DB.update("build", { _id: buildId }, { deployStatus: "failed" }, { select: ["_id", "deployStatus"] }).catch(console.error);
return { error: err };
}
}
// create "imagePullSecret" in namespace:
try {
const { name: imagePullSecretName } = await k8s_1.default.createImagePullSecretsInNamespace(appSlug, env, clusterSlug, namespace);
if (onUpdate)
onUpdate(`[ROLL OUT V2] Created "${imagePullSecretName}" imagePullSecrets in the "${namespace}" namespace (cluster: "${clusterSlug}").`);
}
catch (e) {
const error = `[ROLL OUT V2] Can't create "imagePullSecrets" in the "${namespace}" namespace (cluster: "${clusterSlug}").`;
// dispatch/trigger webhook
if (webhook)
webhookSvc.trigger(mongodb_1.MongoDB.toString(webhook._id), "failed");
// update release as "failed"
await DB.update("release", { _id: releaseId }, { status: "failed" }, { select: ["_id", "status"] }).catch(console.error);
// Update "deployStatus" of a build to success
await DB.update("build", { _id: buildId }, { deployStatus: "failed" }, { select: ["_id", "deployStatus"] }).catch(console.error);
return { error };
}
/**
* Start applying new deployment YAML
*/
if (options === null || options === void 0 ? void 0 : options.isDebugging)
console.log("[ROLL OUT V2] Deployment YAML :>> ", deploymentYaml);
let newReplicas = 1, currentReplicas = 0, currentDeploymentName, currentAppVersion, envVars = [], resourceQuota = {}, service, svcName, ingress, ingressName, deployment, deploymentName;
let deploymentCfg = js_yaml_1.default.loadAll(deploymentYaml);
if (deploymentCfg.length > 1) {
deploymentCfg.forEach((doc) => {
if (doc && doc.kind == "Ingress") {
ingress = doc;
ingressName = doc.metadata.name;
}
if (doc && doc.kind == "Service") {
service = doc;
svcName = doc.metadata.name;
}
if (doc && doc.kind == "Deployment") {
newReplicas = doc.spec.replicas;
// important: set new deployment's replicas to "deployReplicas" for temporary -> set back later (avoid downtime)
doc.spec.replicas = deployReplicas;
//
envVars = doc.spec.template.spec.containers[0].env;
resourceQuota = doc.spec.template.spec.containers[0].resources;
deployment = doc;
deploymentName = doc.metadata.name;
}
});
}
const tmpDeploymentYaml = (0, plugins_1.objectToDeploymentYaml)(deploymentCfg);
const [currentDeployment] = await k8s_1.default.getDeploys(namespace, { context, filterLabel: `main-app=${mainAppName}`, metrics: false });
currentReplicas = currentDeployment && typeof currentDeployment !== "string" ? currentDeployment.spec.replicas : 1;
currentDeploymentName = currentDeployment && typeof currentDeployment !== "string" ? currentDeployment.metadata.name : "";
currentAppVersion = currentDeployment && typeof currentDeployment !== "string" ? currentDeployment.metadata.labels["app-version"] : undefined;
const currentPods = await k8s_1.default.getPods(namespace, { context, filterLabel: `main-app=${mainAppName}`, metrics: false });
const countCrashedPods = currentPods.filter((pod) => pod.status.containerStatuses.find((containerStatus) => { var _a; return ((_a = containerStatus.state.waiting) === null || _a === void 0 ? void 0 : _a.reason) === "CrashLoopBackOff"; }) !== undefined).length;
const countRunningPods = currentPods.filter((pod) => pod.status.phase === "Running").length;
// check ingress domain has been used yet or not:
let isDomainUsed = false, usedDomain, usedDomainNamespace, deleteIng;
if (ingress) {
const domains = ingress.spec.rules.map((rule) => rule.host) || [];
// console.log("domains :>> ", domains);
if (domains.length > 0) {
const allIngresses = await k8s_1.default.getAllIngresses({ context });
allIngresses.filter((ing) => {
domains.map((domain) => {
if (ing.spec.rules.map((rule) => rule.host).includes(domain) && ing.metadata.namespace !== namespace) {
isDomainUsed = true;
usedDomain = domain;
deleteIng = ing;
usedDomainNamespace = ing.metadata.namespace;
}
});
});
if (isDomainUsed) {
// await ClusterManager.deleteIngress(deleteIng.metadata.name, deleteIng.metadata.namespace, { context });
// if (onUpdate)
// onUpdate(
// `Domain "${usedDomain}" has been used before at "${deleteIng.metadata.namespace}" namespace -> Deleted "${deleteIng.metadata.name}" ingress to create a new one.`
// );
const error = `This domain "${service.metadata.name}" has been using in "${usedDomainNamespace}" namespace.`;
if (onUpdate)
onUpdate(error);
// dispatch/trigger webhook
if (webhook)
webhookSvc.trigger(mongodb_1.MongoDB.toString(webhook._id), "failed");
// update release as "failed"
await DB.update("release", { _id: releaseId }, { status: "failed" }, { select: ["_id", "status"] }).catch(console.error);
// Update "deployStatus" of a build to success
await DB.update("build", { _id: buildId }, { deployStatus: "failed" }, { select: ["_id", "deployStatus"] }).catch(console.error);
return { error };
}
}
}
/**
* Scale current deployment up to many replicas before apply new deployment YAML
* But if there are many crashed pods -> skip scaling up
*/
if (newReplicas === 1 && countCrashedPods === 0 && countRunningPods === 1) {
if (currentDeploymentName) {
if (onUpdate)
onUpdate(`Scaling "${currentDeploymentName}" deployment to ${deployReplicas} & prepare for rolling out new deployment.`);
await k8s_1.default.scaleDeploy(currentDeploymentName, deployReplicas, namespace, { context });
// wait 10 secs
// await wait(30 * 1000);
// await ClusterManager.setDeployImageAll(deploymentName, `${deployEnvironment.imageURL}:${deployEnvironment.buildTag}`, namespace, { context });
// wait until an old deployment has been scaled successfully
const isDeploymentFinishScaling = await (0, plugins_1.waitUntil)(() => checkDeploymentReady({
context,
appName: mainAppName,
namespace,
replicas: deployReplicas,
onUpdate,
// isDebugging: true,
}), 5, 5 * 60).catch((e) => false);
if (!isDeploymentFinishScaling) {
(0, log_1.logWarn)(`Unable to scale up the previous deployment, downtime might happen.`);
if (onUpdate)
onUpdate(`Unable to scale up the previous deployment (${currentDeploymentName}) to ${deployReplicas} replicas, downtime might happen.`);
}
else {
if (onUpdate)
onUpdate(`Scaled "${currentDeploymentName}" deployment to ${deployReplicas} replicas successfully.`);
}
}
}
/**
* Apply new deployment yaml
*/
try {
await k8s_1.default.kubectlApplyContent(tmpDeploymentYaml, { context });
if (onUpdate)
onUpdate(`Applied new deployment YAML of "${appSlug}" successfully.`);
}
catch (e) {
const error = `[ERROR] Unable to apply new deployment "${deploymentName}": ${e}\n\n${deploymentYaml}`;
if (onUpdate)
onUpdate(error);
// dispatch/trigger webhook
if (webhook)
webhookSvc.trigger(mongodb_1.MongoDB.toString(webhook._id), "failed");
// update release as "failed"
await DB.update("release", { _id: releaseId }, { status: "failed" }, { select: ["_id", "status"] }).catch(console.error);
// Update "deployStatus" of a build to success
await DB.update("build", { _id: buildId }, { deployStatus: "failed" }, { select: ["_id", "deployStatus"] }).catch(console.error);
return { error };
}
/**
* Annotate new deployment with app version
*/
try {
await k8s_1.default.kubectlAnnotateDeployment(`kubernetes.io/change-cause="${message || appVersion}"`, deploymentName, namespace, {
context,
});
}
catch (e) {
const error = `Unable to annotate new deployment (Cluster: ${clusterSlug} / Namespace: ${namespace} / App: ${appSlug} / Env: ${env} / Deployment: ${deploymentName})`;
if (onUpdate)
onUpdate(error);
// dispatch/trigger webhook
if (webhook)
webhookSvc.trigger(mongodb_1.MongoDB.toString(webhook._id), "failed");
// update release as "failed"
await DB.update("release", { _id: releaseId }, { status: "failed" }, { select: ["_id", "status"] }).catch(console.error);
// Update "deployStatus" of a build to success
await DB.update("build", { _id: buildId }, { deployStatus: "failed" }, { select: ["_id", "deployStatus"] }).catch(console.error);
return { error };
}
/**
* Wait until the deployment is ready!
* (Ignore crashed pods)
*/
const isNewDeploymentReady = await (0, plugins_1.waitUntil)(async () => {
const isReady = checkDeploymentReady({
context,
appName: mainAppName,
appVersion,
namespace,
onUpdate,
skipCrashedPods: true,
});
if (!isReady) {
// Check if all new pods are crashed
const newPods = await k8s_1.default.getPods(namespace, { context, filterLabel: `app-version=${appVersion}`, metrics: false });
const countNewCrashedPods = newPods.filter((pod) => pod.status.containerStatuses.find((containerStatus) => { var _a; return ((_a = containerStatus.state.waiting) === null || _a === void 0 ? void 0 : _a.reason) === "CrashLoopBackOff"; }) !==
undefined).length;
const isAllNewPodsCrashed = countNewCrashedPods === newPods.length;
if (isAllNewPodsCrashed) {
const error = `All new pods are crashed.`;
if (onUpdate)
onUpdate(error);
// Dispatch/trigger webhook
if (webhook)
webhookSvc.trigger(mongodb_1.MongoDB.toString(webhook._id), "failed");
// Update release as "failed"
await DB.update("release", { _id: releaseId }, { status: "failed" }, { select: ["_id", "status"] }).catch(console.error);
// Update "deployStatus" of a build to "failed"
await DB.update("build", { _id: buildId }, { deployStatus: "failed" }, { select: ["_id", "deployStatus"] }).catch(console.error);
throw new Error(error);
}
}
return isReady;
},
// check interval: 5 secs
5,
// max wait time: 10 mins
10 * 60).catch((e) => {
(0, log_1.logError)(`[ROLL OUT V2] Error:`, e);
return false;
});
if (options === null || options === void 0 ? void 0 : options.isDebugging)
(0, log_1.log)(`[ROLL OUT V2] Checking new deployment's status -> Is Fully Ready:`, isNewDeploymentReady);
// Try to get the container logs and print to the web ui
let containerLogs = isNewDeploymentReady
? await (0, kubectl_1.logPodByFilter)(namespace, { filterLabel: `app-version=${appVersion}`, context }).catch((e) => "")
: await (0, kubectl_1.logPodByFilter)(namespace, { filterLabel: `app-version=${appVersion}`, previous: true, context }).catch((e) => "");
if (onUpdate && containerLogs)
onUpdate(`--------------- APP'S LOGS ON STARTED UP --------------- \n${containerLogs}`);
// throw the error
if (!isNewDeploymentReady ||
containerLogs.indexOf("Error from server") > -1 ||
containerLogs.indexOf("An error occurred") > -1 ||
containerLogs.indexOf("Command failed") > -1 ||
containerLogs.indexOf("Unexpected Server Error") > -1) {
const error = `[ERROR] The application failed to start up properly. To identify the issue, please review the application logs.`;
if (onUpdate)
onUpdate(error);
// print out the logs in server side:
(0, log_1.logError)(containerLogs);
// dispatch/trigger webhook
if (webhook)
webhookSvc.trigger(mongodb_1.MongoDB.toString(webhook._id), "failed");
// update release as "failed"
await DB.update("release", { _id: releaseId }, { status: "failed" }, { select: ["_id", "status"] }).catch(console.error);
// Update "deployStatus" of a build to success
await DB.update("build", { _id: buildId }, { deployStatus: "failed" }, { select: ["_id", "deployStatus"] }).catch(console.error);
return { error };
}
// Scale new deployment to new replicas
await k8s_1.default.scaleDeploy(deploymentName, newReplicas, namespace, { context });
// Print success:
const prodUrlInCLI = chalk_1.default.bold(`https://${endpointUrl}`);
const successMsg = `🎉 PUBLISHED AT: ${prodUrlInCLI} 🎉`;
(0, log_1.logSuccess)(successMsg);
if (onUpdate)
onUpdate(successMsg);
// Mark this latest release as "active":
try {
const latestRelease = await (0, mark_release_as_active_1.markReleaseAsActive)({ id: releaseId, appSlug, env });
if (!latestRelease)
throw new Error(`Release "${releaseId}" not found.`);
}
catch (e) {
const error = `[ERROR] Unable to mark the latest release (${releaseId}) status as "active": ${e.message}`;
if (onUpdate)
onUpdate(error);
// dispatch/trigger webhook
if (webhook)
webhookSvc.trigger(mongodb_1.MongoDB.toString(webhook._id), "failed");
// Update "deployStatus" of a build to "failed"
await DB.update("build", { _id: buildId }, { deployStatus: "failed" }, { select: ["_id", "deployStatus"] });
throw new Error(error);
}
// Update "deployStatus" of a build to success
const build = await DB.updateOne("build", { _id: buildId }, { deployStatus: "success" }, { select: ["_id", "deployStatus"] });
// Update project to sort by latest release
await DB.update("project", { slug: projectSlug }, { lastUpdatedBy: owner.username, latestBuild: build === null || build === void 0 ? void 0 : build._id }, { select: ["_id", "updatedAt"] });
// Assign this release as "latestRelease" of this app's deploy environment
await DB.updateOne("app", { slug: appSlug }, {
[`deployEnvironment.${env}.latestRelease`]: releaseId,
[`deployEnvironment.${env}.appVersion`]: appVersion,
[`deployEnvironment.${env}.buildId`]: buildId,
}, { select: ["_id"] });
/**
* 5. Clean up > Delete old deployments (IF ANY)
* - Skip CLEAN UP task on test environment
*/
if (!(0, app_config_1.IsTest)()) {
/**
* NOTE: Clean up DEPRECATED deployments (from OLD CLI <3.33.11 deployments)
*/
// if (isServerMode && env === "prod") {
if (app_config_1.isServerMode) {
cleanUpNamespace(cluster, namespace, mainAppName, appVersion)
.then(({ error }) => {
if (error)
throw new Error(`Unable to clean up old resources in "${namespace}" namespace.`);
(0, log_1.logSuccess)(`✅ Clean up old resources in "${namespace}" namespace SUCCESSFULLY.`);
})
.catch((e) => (0, log_1.logError)(`Unable to clean up old resources in "${namespace}" namespace:`, e));
}
}
return { error: null, data: releaseData };
}
exports.rolloutV2 = rolloutV2;