feat(rollback): add rollback constraints and snapshots

- Introduced two new SQL files for rollback constraints, updating foreign key relationships with different delete actions (set null and cascade).
- Updated the journal and snapshot files to include the new rollback schema changes for versions 0096 and 0097.
- Enhanced the application service to handle rollback image tagging based on source type.
- Implemented rollback removal logic in the deployment service to ensure proper cleanup of rollback entries.
This commit is contained in:
Mauricio Siu
2025-06-21 23:17:21 -06:00
parent 8c5e34c528
commit 3d285ca437
8 changed files with 11732 additions and 17 deletions

View File

@@ -0,0 +1,3 @@
ALTER TABLE "rollback" DROP CONSTRAINT "rollback_deploymentId_deployment_deploymentId_fk";
--> statement-breakpoint
ALTER TABLE "rollback" ADD CONSTRAINT "rollback_deploymentId_deployment_deploymentId_fk" FOREIGN KEY ("deploymentId") REFERENCES "public"."deployment"("deploymentId") ON DELETE set null ON UPDATE no action;

View File

@@ -0,0 +1,3 @@
ALTER TABLE "rollback" DROP CONSTRAINT "rollback_deploymentId_deployment_deploymentId_fk";
--> statement-breakpoint
ALTER TABLE "rollback" ADD CONSTRAINT "rollback_deploymentId_deployment_deploymentId_fk" FOREIGN KEY ("deploymentId") REFERENCES "public"."deployment"("deploymentId") ON DELETE cascade ON UPDATE no action;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -673,6 +673,20 @@
"when": 1750562292392, "when": 1750562292392,
"tag": "0095_curly_justice", "tag": "0095_curly_justice",
"breakpoints": true "breakpoints": true
},
{
"idx": 96,
"version": "7",
"when": 1750566830268,
"tag": "0096_small_shaman",
"breakpoints": true
},
{
"idx": 97,
"version": "7",
"when": 1750567641441,
"tag": "0097_hard_lizard",
"breakpoints": true
} }
] ]
} }

View File

@@ -216,8 +216,12 @@ export const deployApplication = async ({
await updateApplicationStatus(applicationId, "done"); await updateApplicationStatus(applicationId, "done");
if (application.rollbackActive) { if (application.rollbackActive) {
const tagImage =
application.sourceType === "docker"
? application.dockerImage
: application.appName;
await createRollback({ await createRollback({
appName: application.appName, appName: tagImage || "",
deploymentId: deployment.deploymentId, deploymentId: deployment.deploymentId,
}); });
} }

View File

@@ -31,6 +31,7 @@ import {
updatePreviewDeployment, updatePreviewDeployment,
} from "./preview-deployment"; } from "./preview-deployment";
import { findScheduleById } from "./schedule"; import { findScheduleById } from "./schedule";
import { removeRollbackById } from "./rollbacks";
export type Deployment = typeof deployments.$inferSelect; export type Deployment = typeof deployments.$inferSelect;
@@ -495,6 +496,9 @@ const getDeploymentsByType = async (
const deploymentList = await db.query.deployments.findMany({ const deploymentList = await db.query.deployments.findMany({
where: eq(deployments[`${type}Id`], id), where: eq(deployments[`${type}Id`], id),
orderBy: desc(deployments.createdAt), orderBy: desc(deployments.createdAt),
with: {
rollback: true,
},
}); });
return deploymentList; return deploymentList;
}; };
@@ -529,6 +533,9 @@ const removeLastTenDeployments = async (
let command = ""; let command = "";
for (const oldDeployment of deploymentsToDelete) { for (const oldDeployment of deploymentsToDelete) {
const logPath = path.join(oldDeployment.logPath); const logPath = path.join(oldDeployment.logPath);
if (oldDeployment.rollbackId) {
await removeRollbackById(oldDeployment.rollbackId);
}
command += ` command += `
rm -rf ${logPath}; rm -rf ${logPath};
@@ -539,8 +546,11 @@ const removeLastTenDeployments = async (
await execAsyncRemote(serverId, command); await execAsyncRemote(serverId, command);
} else { } else {
for (const oldDeployment of deploymentsToDelete) { for (const oldDeployment of deploymentsToDelete) {
if (oldDeployment.rollbackId) {
await removeRollbackById(oldDeployment.rollbackId);
}
const logPath = path.join(oldDeployment.logPath); const logPath = path.join(oldDeployment.logPath);
if (existsSync(logPath)) { if (existsSync(logPath) && !oldDeployment.errorMessage) {
await fsPromises.unlink(logPath); await fsPromises.unlink(logPath);
} }
await removeDeployment(oldDeployment.deploymentId); await removeDeployment(oldDeployment.deploymentId);

View File

@@ -1,6 +1,10 @@
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { db } from "../db"; import { db } from "../db";
import { type createRollbackSchema, rollbacks } from "../db/schema"; import {
type createRollbackSchema,
rollbacks,
deployments as deploymentsSchema,
} from "../db/schema";
import type { z } from "zod"; import type { z } from "zod";
import { findApplicationById } from "./application"; import { findApplicationById } from "./application";
import { getRemoteDocker } from "../utils/servers/remote-docker"; import { getRemoteDocker } from "../utils/servers/remote-docker";
@@ -30,8 +34,14 @@ export const createRollback = async (
throw new Error("Deployment not found"); throw new Error("Deployment not found");
} }
const { deployments, bitbucket, github, gitlab, gitea, ...rest } = const {
await findApplicationById(deployment.applicationId); deployments: _,
bitbucket,
github,
gitlab,
gitea,
...rest
} = await findApplicationById(deployment.applicationId);
await tx await tx
.update(rollbacks) .update(rollbacks)
@@ -41,6 +51,14 @@ export const createRollback = async (
}) })
.where(eq(rollbacks.rollbackId, rollback.rollbackId)); .where(eq(rollbacks.rollbackId, rollback.rollbackId));
// Update the deployment to reference this rollback
await tx
.update(deploymentsSchema)
.set({
rollbackId: rollback.rollbackId,
})
.where(eq(deploymentsSchema.deploymentId, rollback.deploymentId));
await createRollbackImage(rest, tagImage); await createRollbackImage(rest, tagImage);
return rollback; return rollback;
@@ -65,12 +83,17 @@ const createRollbackImage = async (
) => { ) => {
const docker = await getRemoteDocker(application.serverId); const docker = await getRemoteDocker(application.serverId);
const result = docker.getImage(`${application.appName}:latest`); const appTagName =
application.sourceType === "docker"
? application.dockerImage
: `${application.appName}:latest`;
const version = tagImage.split(":")[1]; const result = docker.getImage(appTagName || "");
const [repo, version] = tagImage.split(":");
await result.tag({ await result.tag({
repo: tagImage, repo,
tag: version, tag: version,
}); });
}; };
@@ -86,28 +109,34 @@ const deleteRollbackImage = async (image: string, serverId?: string | null) => {
}; };
export const removeRollbackById = async (rollbackId: string) => { export const removeRollbackById = async (rollbackId: string) => {
const result = await db const rollback = await findRollbackById(rollbackId);
.delete(rollbacks)
.where(eq(rollbacks.rollbackId, rollbackId))
.returning()
.then((res) => res[0]);
if (result?.image) { if (!rollback) {
throw new Error("Rollback not found");
}
if (rollback?.image) {
try { try {
const deployment = await findDeploymentById(result.deploymentId); const deployment = await findDeploymentById(rollback.deploymentId);
if (!deployment?.applicationId) { if (!deployment?.applicationId) {
throw new Error("Deployment not found"); throw new Error("Deployment not found");
} }
const application = await findApplicationById(deployment.applicationId); const application = await findApplicationById(deployment.applicationId);
await deleteRollbackImage(result.image, application.serverId); await deleteRollbackImage(rollback.image, application.serverId);
await db
.delete(rollbacks)
.where(eq(rollbacks.rollbackId, rollbackId))
.returning()
.then((res) => res[0]);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
} }
return result; return rollback;
}; };
export const rollback = async (rollbackId: string) => { export const rollback = async (rollbackId: string) => {