import { eq } from "drizzle-orm"; import { db } from "../db"; import { type createRollbackSchema, rollbacks, deployments as deploymentsSchema, } from "../db/schema"; import type { z } from "zod"; import { findApplicationById } from "./application"; import { getRemoteDocker } from "../utils/servers/remote-docker"; import type { ApplicationNested } from "../utils/builders"; import { execAsync, execAsyncRemote } from "../utils/process/execAsync"; import type { CreateServiceOptions } from "dockerode"; import { findDeploymentById } from "./deployment"; import { prepareEnvironmentVariables } from "../utils/docker/utils"; export const createRollback = async ( input: z.infer, ) => { await db.transaction(async (tx) => { const { fullContext, ...other } = input; const rollback = await tx .insert(rollbacks) .values(other) .returning() .then((res) => res[0]); if (!rollback) { throw new Error("Failed to create rollback"); } const tagImage = `${input.appName}:v${rollback.version}`; const deployment = await findDeploymentById(rollback.deploymentId); if (!deployment?.applicationId) { throw new Error("Deployment not found"); } const { deployments: _, bitbucket, github, gitlab, gitea, ...rest } = await findApplicationById(deployment.applicationId); await tx .update(rollbacks) .set({ image: tagImage, fullContext: rest, }) .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); return rollback; }); }; const findRollbackById = async (rollbackId: string) => { const result = await db.query.rollbacks.findFirst({ where: eq(rollbacks.rollbackId, rollbackId), }); if (!result) { throw new Error("Rollback not found"); } return result; }; const createRollbackImage = async ( application: ApplicationNested, tagImage: string, ) => { const docker = await getRemoteDocker(application.serverId); const appTagName = application.sourceType === "docker" ? application.dockerImage : `${application.appName}:latest`; const result = docker.getImage(appTagName || ""); const [repo, version] = tagImage.split(":"); await result.tag({ repo, tag: version, }); }; const deleteRollbackImage = async (image: string, serverId?: string | null) => { const command = `docker image rm ${image} --force`; if (serverId) { await execAsyncRemote(command, serverId); } else { await execAsync(command); } }; export const removeRollbackById = async (rollbackId: string) => { const rollback = await findRollbackById(rollbackId); if (!rollback) { throw new Error("Rollback not found"); } if (rollback?.image) { try { const deployment = await findDeploymentById(rollback.deploymentId); if (!deployment?.applicationId) { throw new Error("Deployment not found"); } const application = await findApplicationById(deployment.applicationId); await deleteRollbackImage(rollback.image, application.serverId); await db .delete(rollbacks) .where(eq(rollbacks.rollbackId, rollbackId)) .returning() .then((res) => res[0]); } catch (error) { console.error(error); } } return rollback; }; export const rollback = async (rollbackId: string) => { const result = await findRollbackById(rollbackId); const deployment = await findDeploymentById(result.deploymentId); if (!deployment?.applicationId) { throw new Error("Deployment not found"); } const application = await findApplicationById(deployment.applicationId); const envVariables = prepareEnvironmentVariables( result?.fullContext?.env || "", result.fullContext?.project?.env || "", ); await rollbackApplication( application.appName, result.image || "", application.serverId, envVariables, ); }; const rollbackApplication = async ( appName: string, image: string, serverId?: string | null, env: string[] = [], ) => { const docker = await getRemoteDocker(serverId); const settings: CreateServiceOptions = { Name: appName, TaskTemplate: { ContainerSpec: { Image: image, Env: env, }, }, }; try { const service = docker.getService(appName); const inspect = await service.inspect(); await service.update({ version: Number.parseInt(inspect.Version.Index), ...settings, TaskTemplate: { ...settings.TaskTemplate, ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1, }, }); } catch (_error: unknown) { await docker.createService(settings); } };