mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-12 23:15:00 +02:00
docker socket
This commit is contained in:
parent
23b5dcfbed
commit
948eb7f6d0
21 changed files with 1808 additions and 128 deletions
278
server/routers/site/socketIntegration.ts
Normal file
278
server/routers/site/socketIntegration.ts
Normal file
|
@ -0,0 +1,278 @@
|
|||
import { db } from "@server/db";
|
||||
import { newts, sites } from "@server/db/schemas";
|
||||
import logger from "@server/logger";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import response from "@server/lib/response";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import stoi from "@server/lib/stoi";
|
||||
import { sendToClient } from "../ws";
|
||||
import {
|
||||
fetchContainers,
|
||||
dockerSocketCache,
|
||||
dockerSocket
|
||||
} from "../newt/dockerSocket";
|
||||
|
||||
export interface ContainerNetwork {
|
||||
networkId: string;
|
||||
endpointId: string;
|
||||
gateway?: string;
|
||||
ipAddress?: string;
|
||||
ipPrefixLen?: number;
|
||||
macAddress?: string;
|
||||
aliases?: string[];
|
||||
dnsNames?: string[];
|
||||
}
|
||||
|
||||
export interface ContainerPort {
|
||||
privatePort: number;
|
||||
publicPort?: number;
|
||||
type: "tcp" | "udp";
|
||||
ip?: string;
|
||||
}
|
||||
|
||||
export interface Container {
|
||||
id: string;
|
||||
name: string;
|
||||
image: string;
|
||||
state: "running" | "exited" | "paused" | "created";
|
||||
status: string;
|
||||
ports?: ContainerPort[];
|
||||
labels: Record<string, string>;
|
||||
created: number;
|
||||
networks: Record<string, ContainerNetwork>;
|
||||
}
|
||||
|
||||
const siteIdParamsSchema = z
|
||||
.object({
|
||||
siteId: z.string().transform(stoi).pipe(z.number().int().positive())
|
||||
})
|
||||
.strict();
|
||||
|
||||
const DockerStatusSchema = z
|
||||
.object({
|
||||
isAvailable: z.boolean(),
|
||||
socketPath: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
|
||||
function validateSiteIdParams(params: any) {
|
||||
const parsedParams = siteIdParamsSchema.safeParse(params);
|
||||
if (!parsedParams.success) {
|
||||
throw createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error)
|
||||
);
|
||||
}
|
||||
return parsedParams.data;
|
||||
}
|
||||
|
||||
async function getSiteAndValidateNewt(siteId: number) {
|
||||
const [site] = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(eq(sites.siteId, siteId))
|
||||
.limit(1);
|
||||
|
||||
if (!site) {
|
||||
throw createHttpError(HttpCode.NOT_FOUND, "Site not found");
|
||||
}
|
||||
|
||||
if (site.type !== "newt") {
|
||||
throw createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"This endpoint is only for Newt sites"
|
||||
);
|
||||
}
|
||||
|
||||
return site;
|
||||
}
|
||||
|
||||
async function getNewtBySiteId(siteId: number) {
|
||||
const [newt] = await db
|
||||
.select()
|
||||
.from(newts)
|
||||
.where(eq(newts.siteId, siteId))
|
||||
.limit(1);
|
||||
|
||||
if (!newt) {
|
||||
throw createHttpError(HttpCode.NOT_FOUND, "Newt not found for site");
|
||||
}
|
||||
|
||||
return newt;
|
||||
}
|
||||
|
||||
async function getSiteAndNewt(siteId: number) {
|
||||
const site = await getSiteAndValidateNewt(siteId);
|
||||
const newt = await getNewtBySiteId(siteId);
|
||||
return { site, newt };
|
||||
}
|
||||
|
||||
function asyncHandler(
|
||||
operation: (siteId: number) => Promise<any>,
|
||||
successMessage: string
|
||||
) {
|
||||
return async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> => {
|
||||
try {
|
||||
const { siteId } = validateSiteIdParams(req.params);
|
||||
const result = await operation(siteId);
|
||||
|
||||
return response(res, {
|
||||
data: result,
|
||||
success: true,
|
||||
error: false,
|
||||
message: successMessage,
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
if (createHttpError.isHttpError(error)) {
|
||||
return next(error);
|
||||
}
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"An error occurred"
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Core business logic functions
|
||||
async function triggerFetch(siteId: number) {
|
||||
const { newt } = await getSiteAndNewt(siteId);
|
||||
|
||||
logger.info(
|
||||
`Triggering fetch containers for site ${siteId} with Newt ${newt.newtId}`
|
||||
);
|
||||
fetchContainers(newt.newtId);
|
||||
return { siteId, newtId: newt.newtId };
|
||||
}
|
||||
|
||||
async function queryContainers(siteId: number) {
|
||||
const { newt } = await getSiteAndNewt(siteId);
|
||||
|
||||
const result = dockerSocketCache.get(
|
||||
`${newt.newtId}:dockerContainers`
|
||||
) as Container[];
|
||||
if (!result) {
|
||||
throw createHttpError(
|
||||
HttpCode.TOO_EARLY,
|
||||
"Nothing found yet. Perhaps the fetch is still in progress? Wait a bit and try again."
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function isDockerAvailable(siteId: number): Promise<boolean> {
|
||||
const { newt } = await getSiteAndNewt(siteId);
|
||||
|
||||
const key = `${newt.newtId}:isAvailable`;
|
||||
const isAvailable = dockerSocketCache.get(key);
|
||||
|
||||
return !!isAvailable;
|
||||
}
|
||||
|
||||
async function getDockerStatus(
|
||||
siteId: number
|
||||
): Promise<z.infer<typeof DockerStatusSchema>> {
|
||||
const { newt } = await getSiteAndNewt(siteId);
|
||||
|
||||
const keys = ["isAvailable", "socketPath"];
|
||||
const mappedKeys = keys.map((x) => `${newt.newtId}:${x}`);
|
||||
|
||||
const result = {
|
||||
isAvailable: dockerSocketCache.get(mappedKeys[0]) as boolean,
|
||||
socketPath: dockerSocketCache.get(mappedKeys[1]) as string | undefined
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function checkSocket(
|
||||
siteId: number
|
||||
): Promise<{ siteId: number; newtId: string }> {
|
||||
const { newt } = await getSiteAndNewt(siteId);
|
||||
|
||||
logger.info(
|
||||
`Checking Docker socket for site ${siteId} with Newt ${newt.newtId}`
|
||||
);
|
||||
|
||||
// Trigger the Docker socket check
|
||||
dockerSocket(newt.newtId);
|
||||
return { siteId, newtId: newt.newtId };
|
||||
}
|
||||
|
||||
// Export types
|
||||
export type GetDockerStatusResponse = NonNullable<
|
||||
Awaited<ReturnType<typeof getDockerStatus>>
|
||||
>;
|
||||
|
||||
export type ListContainersResponse = Awaited<
|
||||
ReturnType<typeof queryContainers>
|
||||
>;
|
||||
|
||||
export type TriggerFetchResponse = Awaited<ReturnType<typeof triggerFetch>>;
|
||||
|
||||
// Route handlers
|
||||
export const triggerFetchContainers = asyncHandler(
|
||||
triggerFetch,
|
||||
"Fetch containers triggered successfully"
|
||||
);
|
||||
|
||||
export const listContainers = asyncHandler(
|
||||
queryContainers,
|
||||
"Containers retrieved successfully"
|
||||
);
|
||||
|
||||
export const dockerOnline = asyncHandler(async (siteId: number) => {
|
||||
const isAvailable = await isDockerAvailable(siteId);
|
||||
return { isAvailable };
|
||||
}, "Docker availability checked successfully");
|
||||
|
||||
export const dockerStatus = asyncHandler(
|
||||
getDockerStatus,
|
||||
"Docker status retrieved successfully"
|
||||
);
|
||||
|
||||
export async function checkDockerSocket(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const { siteId } = validateSiteIdParams(req.params);
|
||||
const result = await checkSocket(siteId);
|
||||
|
||||
// Notify the Newt client about the Docker socket check
|
||||
sendToClient(result.newtId, {
|
||||
type: "newt/socket/check",
|
||||
data: {}
|
||||
});
|
||||
|
||||
return response(res, {
|
||||
data: result,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Docker socket checked successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
if (createHttpError.isHttpError(error)) {
|
||||
return next(error);
|
||||
}
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue