mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-06 04:02:16 +00:00
feat: implement rollback registry functionality in application settings
- Added a new optional field `rollbackRegistryId` to the application schema to support rollback registry selection. - Enhanced the form in the ShowRollbackSettings component to include a dropdown for selecting a rollback registry when rollbacks are enabled. - Updated the application service to handle rollback registry logic during deployment and rollback processes. - Improved error handling and validation for rollback settings, ensuring a registry is selected when rollbacks are active. - Adjusted database schema and migration files to accommodate the new rollback registry feature.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -20,13 +20,37 @@ import {
|
|||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z
|
||||||
rollbackActive: z.boolean(),
|
.object({
|
||||||
});
|
rollbackActive: z.boolean(),
|
||||||
|
rollbackRegistryId: z.string().optional(),
|
||||||
|
})
|
||||||
|
.superRefine((values, ctx) => {
|
||||||
|
if (
|
||||||
|
values.rollbackActive &&
|
||||||
|
(!values.rollbackRegistryId || values.rollbackRegistryId === "none")
|
||||||
|
) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["rollbackRegistryId"],
|
||||||
|
message: "Registry is required when rollbacks are enabled",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
type FormValues = z.infer<typeof formSchema>;
|
type FormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
@@ -49,17 +73,33 @@ export const ShowRollbackSettings = ({ applicationId, children }: Props) => {
|
|||||||
const { mutateAsync: updateApplication, isLoading } =
|
const { mutateAsync: updateApplication, isLoading } =
|
||||||
api.application.update.useMutation();
|
api.application.update.useMutation();
|
||||||
|
|
||||||
|
const { data: registries } = api.registry.all.useQuery();
|
||||||
|
|
||||||
const form = useForm<FormValues>({
|
const form = useForm<FormValues>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
rollbackActive: application?.rollbackActive ?? false,
|
rollbackActive: application?.rollbackActive ?? false,
|
||||||
|
rollbackRegistryId: application?.rollbackRegistryId || "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (application) {
|
||||||
|
form.reset({
|
||||||
|
rollbackActive: application.rollbackActive ?? false,
|
||||||
|
rollbackRegistryId: application.rollbackRegistryId || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [application, form]);
|
||||||
|
|
||||||
const onSubmit = async (data: FormValues) => {
|
const onSubmit = async (data: FormValues) => {
|
||||||
await updateApplication({
|
await updateApplication({
|
||||||
applicationId,
|
applicationId,
|
||||||
rollbackActive: data.rollbackActive,
|
rollbackActive: data.rollbackActive,
|
||||||
|
rollbackRegistryId:
|
||||||
|
data.rollbackRegistryId === "none" || !data.rollbackRegistryId
|
||||||
|
? null
|
||||||
|
: data.rollbackRegistryId,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("Rollback settings updated");
|
toast.success("Rollback settings updated");
|
||||||
@@ -112,6 +152,52 @@ export const ShowRollbackSettings = ({ applicationId, children }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{form.watch("rollbackActive") && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="rollbackRegistryId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Rollback Registry</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={field.value || "none"}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a registry" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="none">
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<span>None</span>
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
{registries?.map((registry) => (
|
||||||
|
<SelectItem
|
||||||
|
key={registry.registryId}
|
||||||
|
value={registry.registryId}
|
||||||
|
>
|
||||||
|
{registry.registryName}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectLabel>
|
||||||
|
Registries ({registries?.length || 0})
|
||||||
|
</SelectLabel>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription>
|
||||||
|
Select a registry where rollback images will be stored.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button type="submit" className="w-full" isLoading={isLoading}>
|
<Button type="submit" className="w-full" isLoading={isLoading}>
|
||||||
Save Settings
|
Save Settings
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "application" ADD COLUMN "rollbackRegistryId" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "application" ADD CONSTRAINT "application_rollbackRegistryId_registry_registryId_fk" FOREIGN KEY ("rollbackRegistryId") REFERENCES "public"."registry"("registryId") ON DELETE set null ON UPDATE no action;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -876,6 +876,13 @@
|
|||||||
"when": 1764571454170,
|
"when": 1764571454170,
|
||||||
"tag": "0124_certain_cloak",
|
"tag": "0124_certain_cloak",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 125,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1764573207555,
|
||||||
|
"tag": "0125_neat_the_phantom",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -187,6 +187,12 @@ export const applications = pgTable("application", {
|
|||||||
registryId: text("registryId").references(() => registry.registryId, {
|
registryId: text("registryId").references(() => registry.registryId, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
}),
|
}),
|
||||||
|
rollbackRegistryId: text("rollbackRegistryId").references(
|
||||||
|
() => registry.registryId,
|
||||||
|
{
|
||||||
|
onDelete: "set null",
|
||||||
|
},
|
||||||
|
),
|
||||||
environmentId: text("environmentId")
|
environmentId: text("environmentId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => environments.environmentId, { onDelete: "cascade" }),
|
.references(() => environments.environmentId, { onDelete: "cascade" }),
|
||||||
@@ -270,6 +276,11 @@ export const applicationsRelations = relations(
|
|||||||
relationName: "applicationBuildRegistry",
|
relationName: "applicationBuildRegistry",
|
||||||
}),
|
}),
|
||||||
previewDeployments: many(previewDeployments),
|
previewDeployments: many(previewDeployments),
|
||||||
|
rollbackRegistry: one(registry, {
|
||||||
|
fields: [applications.rollbackRegistryId],
|
||||||
|
references: [registry.registryId],
|
||||||
|
relationName: "applicationRollbackRegistry",
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ export const registryRelations = relations(registry, ({ many }) => ({
|
|||||||
buildApplications: many(applications, {
|
buildApplications: many(applications, {
|
||||||
relationName: "applicationBuildRegistry",
|
relationName: "applicationBuildRegistry",
|
||||||
}),
|
}),
|
||||||
|
rollbackApplications: many(applications, {
|
||||||
|
relationName: "applicationRollbackRegistry",
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const createSchema = createInsertSchema(registry, {
|
const createSchema = createInsertSchema(registry, {
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ import {
|
|||||||
updatePreviewDeployment,
|
updatePreviewDeployment,
|
||||||
} from "./preview-deployment";
|
} from "./preview-deployment";
|
||||||
import { validUniqueServerAppName } from "./project";
|
import { validUniqueServerAppName } from "./project";
|
||||||
import { createRollback } from "./rollbacks";
|
|
||||||
export type Application = typeof applications.$inferSelect;
|
export type Application = typeof applications.$inferSelect;
|
||||||
|
|
||||||
export const createApplication = async (
|
export const createApplication = async (
|
||||||
@@ -113,6 +112,7 @@ export const findApplicationById = async (applicationId: string) => {
|
|||||||
server: true,
|
server: true,
|
||||||
previewDeployments: true,
|
previewDeployments: true,
|
||||||
buildRegistry: true,
|
buildRegistry: true,
|
||||||
|
rollbackRegistry: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!application) {
|
if (!application) {
|
||||||
@@ -198,7 +198,7 @@ export const deployApplication = async ({
|
|||||||
command += await buildRemoteDocker(application);
|
command += await buildRemoteDocker(application);
|
||||||
}
|
}
|
||||||
|
|
||||||
command += getBuildCommand(application);
|
command += await getBuildCommand(application);
|
||||||
|
|
||||||
const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
|
const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
|
||||||
if (serverId) {
|
if (serverId) {
|
||||||
@@ -211,17 +211,6 @@ export const deployApplication = async ({
|
|||||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||||
await updateApplicationStatus(applicationId, "done");
|
await updateApplicationStatus(applicationId, "done");
|
||||||
|
|
||||||
if (application.rollbackActive) {
|
|
||||||
const tagImage =
|
|
||||||
application.sourceType === "docker"
|
|
||||||
? application.dockerImage
|
|
||||||
: application.appName;
|
|
||||||
await createRollback({
|
|
||||||
appName: tagImage || "",
|
|
||||||
deploymentId: deployment.deploymentId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await sendBuildSuccessNotifications({
|
await sendBuildSuccessNotifications({
|
||||||
projectName: application.environment.project.name,
|
projectName: application.environment.project.name,
|
||||||
applicationName: application.name,
|
applicationName: application.name,
|
||||||
@@ -299,7 +288,7 @@ export const rebuildApplication = async ({
|
|||||||
try {
|
try {
|
||||||
let command = "set -e;";
|
let command = "set -e;";
|
||||||
// Check case for docker only
|
// Check case for docker only
|
||||||
command += getBuildCommand(application);
|
command += await getBuildCommand(application);
|
||||||
const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
|
const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
|
||||||
if (serverId) {
|
if (serverId) {
|
||||||
await execAsyncRemote(serverId, commandWithLog);
|
await execAsyncRemote(serverId, commandWithLog);
|
||||||
@@ -310,17 +299,6 @@ export const rebuildApplication = async ({
|
|||||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||||
await updateApplicationStatus(applicationId, "done");
|
await updateApplicationStatus(applicationId, "done");
|
||||||
|
|
||||||
if (application.rollbackActive) {
|
|
||||||
const tagImage =
|
|
||||||
application.sourceType === "docker"
|
|
||||||
? application.dockerImage
|
|
||||||
: application.appName;
|
|
||||||
await createRollback({
|
|
||||||
appName: tagImage || "",
|
|
||||||
deploymentId: deployment.deploymentId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await sendBuildSuccessNotifications({
|
await sendBuildSuccessNotifications({
|
||||||
projectName: application.environment.project.name,
|
projectName: application.environment.project.name,
|
||||||
applicationName: application.name,
|
applicationName: application.name,
|
||||||
@@ -423,6 +401,10 @@ export const deployPreviewApplication = async ({
|
|||||||
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
|
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
|
||||||
application.buildArgs = `${application.previewBuildArgs}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
|
application.buildArgs = `${application.previewBuildArgs}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
|
||||||
application.buildSecrets = `${application.previewBuildSecrets}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
|
application.buildSecrets = `${application.previewBuildSecrets}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
|
||||||
|
application.rollbackActive = false;
|
||||||
|
application.buildRegistry = null;
|
||||||
|
application.rollbackRegistry = null;
|
||||||
|
application.registry = null;
|
||||||
|
|
||||||
let command = "set -e;";
|
let command = "set -e;";
|
||||||
if (application.sourceType === "github") {
|
if (application.sourceType === "github") {
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import {
|
|||||||
deployments as deploymentsSchema,
|
deployments as deploymentsSchema,
|
||||||
rollbacks,
|
rollbacks,
|
||||||
} from "../db/schema";
|
} from "../db/schema";
|
||||||
import { type ApplicationNested, getAuthConfig } from "../utils/builders";
|
import type { ApplicationNested } from "../utils/builders";
|
||||||
|
import { getRegistryTag } from "../utils/cluster/upload";
|
||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
@@ -22,11 +23,12 @@ import { findDeploymentById } from "./deployment";
|
|||||||
import type { Mount } from "./mount";
|
import type { Mount } from "./mount";
|
||||||
import type { Port } from "./port";
|
import type { Port } from "./port";
|
||||||
import type { Project } from "./project";
|
import type { Project } from "./project";
|
||||||
|
import type { Registry } from "./registry";
|
||||||
|
|
||||||
export const createRollback = async (
|
export const createRollback = async (
|
||||||
input: z.infer<typeof createRollbackSchema>,
|
input: z.infer<typeof createRollbackSchema>,
|
||||||
) => {
|
) => {
|
||||||
await db.transaction(async (tx) => {
|
return await db.transaction(async (tx) => {
|
||||||
const { fullContext, ...other } = input;
|
const { fullContext, ...other } = input;
|
||||||
const rollback = await tx
|
const rollback = await tx
|
||||||
.insert(rollbacks)
|
.insert(rollbacks)
|
||||||
@@ -70,9 +72,11 @@ export const createRollback = async (
|
|||||||
})
|
})
|
||||||
.where(eq(deploymentsSchema.deploymentId, rollback.deploymentId));
|
.where(eq(deploymentsSchema.deploymentId, rollback.deploymentId));
|
||||||
|
|
||||||
await createRollbackImage(rest, tagImage);
|
const updatedRollback = await tx.query.rollbacks.findFirst({
|
||||||
|
where: eq(rollbacks.rollbackId, rollback.rollbackId),
|
||||||
|
});
|
||||||
|
|
||||||
return rollback;
|
return updatedRollback;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,27 +107,6 @@ export const findRollbackById = async (rollbackId: string) => {
|
|||||||
return result;
|
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 deleteRollbackImage = async (image: string, serverId?: string | null) => {
|
||||||
const command = `docker image rm ${image} --force`;
|
const command = `docker image rm ${image} --force`;
|
||||||
|
|
||||||
@@ -179,14 +162,18 @@ export const rollback = async (rollbackId: string) => {
|
|||||||
if (!result.fullContext) {
|
if (!result.fullContext) {
|
||||||
throw new Error("Rollback context not found");
|
throw new Error("Rollback context not found");
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
// Use the full context for rollback
|
// Use the full context for rollback
|
||||||
await rollbackApplication(
|
await rollbackApplication(
|
||||||
application.appName,
|
application.appName,
|
||||||
result.image || "",
|
result.image || "",
|
||||||
application.serverId,
|
application.serverId,
|
||||||
result.fullContext,
|
result.fullContext,
|
||||||
);
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw new Error("Failed to rollback application");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const rollbackApplication = async (
|
const rollbackApplication = async (
|
||||||
@@ -199,6 +186,7 @@ const rollbackApplication = async (
|
|||||||
};
|
};
|
||||||
mounts: Mount[];
|
mounts: Mount[];
|
||||||
ports: Port[];
|
ports: Port[];
|
||||||
|
rollbackRegistry?: Registry;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
if (!fullContext) {
|
if (!fullContext) {
|
||||||
@@ -245,16 +233,24 @@ const rollbackApplication = async (
|
|||||||
fullContext.environment.project.env,
|
fullContext.environment.project.env,
|
||||||
);
|
);
|
||||||
|
|
||||||
// For rollback, we use the provided image instead of calculating it
|
// Build the full registry image path if rollbackRegistry is available
|
||||||
const authConfig = getAuthConfig(fullContext as ApplicationNested);
|
// e.g., "appName:v5" -> "siumauricio/appName:v5" or "registry.com/prefix/appName:v5"
|
||||||
|
let rollbackImage = image;
|
||||||
|
if (fullContext.rollbackRegistry) {
|
||||||
|
rollbackImage = getRegistryTag(fullContext.rollbackRegistry, image);
|
||||||
|
}
|
||||||
|
|
||||||
const settings: CreateServiceOptions = {
|
const settings: CreateServiceOptions = {
|
||||||
authconfig: authConfig,
|
authconfig: {
|
||||||
|
password: fullContext.rollbackRegistry?.password || "",
|
||||||
|
username: fullContext.rollbackRegistry?.username || "",
|
||||||
|
serveraddress: fullContext.rollbackRegistry?.registryUrl || "",
|
||||||
|
},
|
||||||
Name: appName,
|
Name: appName,
|
||||||
TaskTemplate: {
|
TaskTemplate: {
|
||||||
ContainerSpec: {
|
ContainerSpec: {
|
||||||
HealthCheck,
|
HealthCheck,
|
||||||
Image: image,
|
Image: rollbackImage,
|
||||||
Env: envVariables,
|
Env: envVariables,
|
||||||
Mounts: [...volumesMount, ...bindsMount],
|
Mounts: [...volumesMount, ...bindsMount],
|
||||||
...(command
|
...(command
|
||||||
@@ -297,7 +293,8 @@ const rollbackApplication = async (
|
|||||||
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
await docker.createService(settings);
|
await docker.createService(settings);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { InferResultType } from "@dokploy/server/types/with";
|
import type { InferResultType } from "@dokploy/server/types/with";
|
||||||
import type { CreateServiceOptions } from "dockerode";
|
import type { CreateServiceOptions } from "dockerode";
|
||||||
import { uploadImageRemoteCommand } from "../cluster/upload";
|
import { getRegistryTag, uploadImageRemoteCommand } from "../cluster/upload";
|
||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
generateBindMounts,
|
generateBindMounts,
|
||||||
@@ -30,39 +30,45 @@ export type ApplicationNested = InferResultType<
|
|||||||
ports: true;
|
ports: true;
|
||||||
registry: true;
|
registry: true;
|
||||||
buildRegistry: true;
|
buildRegistry: true;
|
||||||
|
rollbackRegistry: true;
|
||||||
|
deployments: true;
|
||||||
environment: { with: { project: true } };
|
environment: { with: { project: true } };
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const getBuildCommand = (application: ApplicationNested) => {
|
export const getBuildCommand = async (application: ApplicationNested) => {
|
||||||
let command = "";
|
let command = "";
|
||||||
const { buildType } = application;
|
|
||||||
|
|
||||||
if (application.sourceType === "docker") {
|
if (application.sourceType !== "docker") {
|
||||||
return "";
|
const { buildType } = application;
|
||||||
|
switch (buildType) {
|
||||||
|
case "nixpacks":
|
||||||
|
command = getNixpacksCommand(application);
|
||||||
|
break;
|
||||||
|
case "heroku_buildpacks":
|
||||||
|
command = getHerokuCommand(application);
|
||||||
|
break;
|
||||||
|
case "paketo_buildpacks":
|
||||||
|
command = getPaketoCommand(application);
|
||||||
|
break;
|
||||||
|
case "static":
|
||||||
|
command = getStaticCommand(application);
|
||||||
|
break;
|
||||||
|
case "dockerfile":
|
||||||
|
command = getDockerCommand(application);
|
||||||
|
break;
|
||||||
|
case "railpack":
|
||||||
|
command = getRailpackCommand(application);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
switch (buildType) {
|
|
||||||
case "nixpacks":
|
if (
|
||||||
command = getNixpacksCommand(application);
|
application.registry ||
|
||||||
break;
|
application.buildRegistry ||
|
||||||
case "heroku_buildpacks":
|
application.rollbackRegistry
|
||||||
command = getHerokuCommand(application);
|
) {
|
||||||
break;
|
command += await uploadImageRemoteCommand(application);
|
||||||
case "paketo_buildpacks":
|
|
||||||
command = getPaketoCommand(application);
|
|
||||||
break;
|
|
||||||
case "static":
|
|
||||||
command = getStaticCommand(application);
|
|
||||||
break;
|
|
||||||
case "dockerfile":
|
|
||||||
command = getDockerCommand(application);
|
|
||||||
break;
|
|
||||||
case "railpack":
|
|
||||||
command = getRailpackCommand(application);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (application.registry || application.buildRegistry) {
|
|
||||||
command += uploadImageRemoteCommand(application);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return command;
|
return command;
|
||||||
@@ -188,17 +194,11 @@ const getImageName = (application: ApplicationNested) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (registry) {
|
if (registry) {
|
||||||
const { registryUrl, imagePrefix, username } = registry;
|
const registryTag = getRegistryTag(registry, imageName);
|
||||||
const registryTag = imagePrefix
|
|
||||||
? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}`
|
|
||||||
: `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`;
|
|
||||||
return registryTag;
|
return registryTag;
|
||||||
}
|
}
|
||||||
if (buildRegistry) {
|
if (buildRegistry) {
|
||||||
const { registryUrl, imagePrefix, username } = buildRegistry;
|
const registryTag = getRegistryTag(buildRegistry, imageName);
|
||||||
const registryTag = imagePrefix
|
|
||||||
? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}`
|
|
||||||
: `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`;
|
|
||||||
return registryTag;
|
return registryTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
|
import { findAllDeploymentsByApplicationId } from "@dokploy/server/services/deployment";
|
||||||
import type { Registry } from "@dokploy/server/services/registry";
|
import type { Registry } from "@dokploy/server/services/registry";
|
||||||
|
import { createRollback } from "@dokploy/server/services/rollbacks";
|
||||||
import type { ApplicationNested } from "../builders";
|
import type { ApplicationNested } from "../builders";
|
||||||
|
|
||||||
export const uploadImageRemoteCommand = (application: ApplicationNested) => {
|
export const uploadImageRemoteCommand = async (
|
||||||
|
application: ApplicationNested,
|
||||||
|
) => {
|
||||||
const registry = application.registry;
|
const registry = application.registry;
|
||||||
const buildRegistry = application.buildRegistry;
|
const buildRegistry = application.buildRegistry;
|
||||||
|
const rollbackRegistry = application.rollbackRegistry;
|
||||||
|
|
||||||
if (!registry && !buildRegistry) {
|
if (!registry && !buildRegistry && !rollbackRegistry) {
|
||||||
throw new Error("No registry found");
|
throw new Error("No registry found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { appName } = application;
|
const { appName } = application;
|
||||||
const imageName = `${appName}:latest`;
|
const imageName =
|
||||||
|
application.sourceType === "docker"
|
||||||
|
? application.dockerImage || ""
|
||||||
|
: `${appName}:latest`;
|
||||||
|
|
||||||
const commands: string[] = [];
|
const commands: string[] = [];
|
||||||
if (registry) {
|
if (registry) {
|
||||||
@@ -35,16 +43,38 @@ export const uploadImageRemoteCommand = (application: ApplicationNested) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rollbackRegistry && application.rollbackActive) {
|
||||||
|
const deployment = await findAllDeploymentsByApplicationId(
|
||||||
|
application.applicationId,
|
||||||
|
);
|
||||||
|
if (!deployment || !deployment[0]) {
|
||||||
|
throw new Error("Deployment not found");
|
||||||
|
}
|
||||||
|
const deploymentId = deployment[0].deploymentId;
|
||||||
|
const rollback = await createRollback({
|
||||||
|
appName: appName,
|
||||||
|
deploymentId: deploymentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const rollbackRegistryTag = getRegistryTag(
|
||||||
|
rollbackRegistry,
|
||||||
|
rollback?.image || "",
|
||||||
|
);
|
||||||
|
if (rollbackRegistryTag) {
|
||||||
|
commands.push(`echo "🔄 [Enabled Rollback Registry]"`);
|
||||||
|
commands.push(
|
||||||
|
getRegistryCommands(rollbackRegistry, imageName, rollbackRegistryTag),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return commands.join("\n");
|
return commands.join("\n");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const getRegistryTag = (registry: Registry | null, imageName: string) => {
|
export const getRegistryTag = (registry: Registry, imageName: string) => {
|
||||||
if (!registry) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const { registryUrl, imagePrefix, username } = registry;
|
const { registryUrl, imagePrefix, username } = registry;
|
||||||
return imagePrefix
|
return imagePrefix
|
||||||
? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}`
|
? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}`
|
||||||
|
|||||||
Reference in New Issue
Block a user