mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-10-15 05:11:26 +00:00
fix: verify swarm task convergence before marking db deploys done
This commit is contained in:
@@ -7,7 +7,10 @@ import {
|
||||
} from "@dokploy/server/db/schema";
|
||||
import { generatePassword } from "@dokploy/server/templates";
|
||||
import { buildLibsql } from "@dokploy/server/utils/databases/libsql";
|
||||
import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||
import {
|
||||
pullImage,
|
||||
waitForSwarmServiceConvergence,
|
||||
} from "@dokploy/server/utils/docker/utils";
|
||||
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
@@ -149,6 +152,7 @@ export const deployLibsql = async (
|
||||
}
|
||||
|
||||
await buildLibsql(libsql);
|
||||
await waitForSwarmServiceConvergence(libsql.appName, libsql.serverId);
|
||||
await updateLibsqlById(libsqlId, {
|
||||
applicationStatus: "done",
|
||||
});
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
} from "@dokploy/server/db/schema";
|
||||
import { generatePassword } from "@dokploy/server/templates";
|
||||
import { buildMariadb } from "@dokploy/server/utils/databases/mariadb";
|
||||
import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||
import {
|
||||
pullImage,
|
||||
waitForSwarmServiceConvergence,
|
||||
} from "@dokploy/server/utils/docker/utils";
|
||||
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
@@ -154,6 +157,7 @@ export const deployMariadb = async (
|
||||
}
|
||||
|
||||
await buildMariadb(mariadb);
|
||||
await waitForSwarmServiceConvergence(mariadb.appName, mariadb.serverId);
|
||||
await updateMariadbById(mariadbId, {
|
||||
applicationStatus: "done",
|
||||
});
|
||||
|
||||
@@ -8,7 +8,10 @@ import {
|
||||
} from "@dokploy/server/db/schema";
|
||||
import { generatePassword } from "@dokploy/server/templates";
|
||||
import { buildMongo } from "@dokploy/server/utils/databases/mongo";
|
||||
import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||
import {
|
||||
pullImage,
|
||||
waitForSwarmServiceConvergence,
|
||||
} from "@dokploy/server/utils/docker/utils";
|
||||
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
@@ -169,6 +172,7 @@ export const deployMongo = async (
|
||||
}
|
||||
|
||||
await buildMongo(mongo);
|
||||
await waitForSwarmServiceConvergence(mongo.appName, mongo.serverId);
|
||||
await updateMongoById(mongoId, {
|
||||
applicationStatus: "done",
|
||||
});
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
} from "@dokploy/server/db/schema";
|
||||
import { generatePassword } from "@dokploy/server/templates";
|
||||
import { buildMysql } from "@dokploy/server/utils/databases/mysql";
|
||||
import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||
import {
|
||||
pullImage,
|
||||
waitForSwarmServiceConvergence,
|
||||
} from "@dokploy/server/utils/docker/utils";
|
||||
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
@@ -152,6 +155,7 @@ export const deployMySql = async (
|
||||
}
|
||||
|
||||
await buildMysql(mysql);
|
||||
await waitForSwarmServiceConvergence(mysql.appName, mysql.serverId);
|
||||
await updateMySqlById(mysqlId, {
|
||||
applicationStatus: "done",
|
||||
});
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
} from "@dokploy/server/db/schema";
|
||||
import { generatePassword } from "@dokploy/server/templates";
|
||||
import { buildPostgres } from "@dokploy/server/utils/databases/postgres";
|
||||
import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||
import {
|
||||
pullImage,
|
||||
waitForSwarmServiceConvergence,
|
||||
} from "@dokploy/server/utils/docker/utils";
|
||||
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
@@ -165,6 +168,8 @@ export const deployPostgres = async (
|
||||
|
||||
await buildPostgres(postgres);
|
||||
|
||||
await waitForSwarmServiceConvergence(postgres.appName, postgres.serverId);
|
||||
|
||||
await updatePostgresById(postgresId, {
|
||||
applicationStatus: "done",
|
||||
});
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
} from "@dokploy/server/db/schema";
|
||||
import { generatePassword } from "@dokploy/server/templates";
|
||||
import { buildRedis } from "@dokploy/server/utils/databases/redis";
|
||||
import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||
import {
|
||||
pullImage,
|
||||
waitForSwarmServiceConvergence,
|
||||
} from "@dokploy/server/utils/docker/utils";
|
||||
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
@@ -119,6 +122,7 @@ export const deployRedis = async (
|
||||
}
|
||||
|
||||
await buildRedis(redis);
|
||||
await waitForSwarmServiceConvergence(redis.appName, redis.serverId);
|
||||
await updateRedisById(redisId, {
|
||||
applicationStatus: "done",
|
||||
});
|
||||
|
||||
@@ -934,6 +934,57 @@ const getSwarmServiceContainerId = async (
|
||||
}
|
||||
};
|
||||
|
||||
export class ServiceConvergenceError extends Error {}
|
||||
|
||||
export const waitForSwarmServiceConvergence = async (
|
||||
appName: string,
|
||||
serverId?: string | null,
|
||||
options?: { timeoutMs?: number; intervalMs?: number },
|
||||
): Promise<void> => {
|
||||
const timeoutMs = options?.timeoutMs ?? 45_000;
|
||||
const intervalMs = options?.intervalMs ?? 2_000;
|
||||
const remoteDocker = await getRemoteDocker(serverId);
|
||||
const service = remoteDocker.getService(appName);
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
|
||||
let lastState = "unknown";
|
||||
while (true) {
|
||||
const info = await service.inspect();
|
||||
const desiredTasksCount = info.Spec?.Mode?.Replicated?.Replicas ?? 1;
|
||||
|
||||
const tasks = await remoteDocker.listTasks({
|
||||
filters: JSON.stringify({ service: [appName] }),
|
||||
});
|
||||
const currentTasks = tasks.filter(
|
||||
(task) => task.DesiredState === "running",
|
||||
);
|
||||
const runningTasksCount = currentTasks.filter(
|
||||
(task) => task.Status?.State === "running",
|
||||
).length;
|
||||
|
||||
if (runningTasksCount >= desiredTasksCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
const failedTask = currentTasks.find((task) =>
|
||||
["failed", "rejected"].includes(task.Status?.State ?? ""),
|
||||
);
|
||||
lastState =
|
||||
failedTask?.Status?.Err ??
|
||||
failedTask?.Status?.State ??
|
||||
currentTasks[0]?.Status?.State ??
|
||||
lastState;
|
||||
|
||||
if (Date.now() >= deadline) {
|
||||
throw new ServiceConvergenceError(
|
||||
`Service ${appName} did not converge within ${timeoutMs}ms: ${runningTasksCount}/${desiredTasksCount} tasks running (last state: ${lastState})`,
|
||||
);
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
||||
}
|
||||
};
|
||||
|
||||
export const checkPostgresHealth = async (): Promise<ServiceHealthStatus> => {
|
||||
const serviceCheck = await checkSwarmServiceRunning("dokploy-postgres");
|
||||
if (serviceCheck.status === "unhealthy") {
|
||||
|
||||
Reference in New Issue
Block a user