mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-14 21:48:39 -05:00
docker provider
This commit is contained in:
@@ -80,6 +80,7 @@ import { HttpProvider } from "@/service/infrastructure/network/HttpProvider.js";
|
||||
import { AdvancedMatcher } from "@/service/infrastructure/network/AdvancedMatcher.js";
|
||||
import { PageSpeedProvider } from "@/service/infrastructure/network/PageSpeedProvider.js";
|
||||
import { HardwareProvider } from "@/service/infrastructure/network/HardwareProvider.js";
|
||||
import { DockerProvider } from "@/service/infrastructure/network/DockerProvider.js";
|
||||
|
||||
export type InitializedServices = {
|
||||
settingsService: any;
|
||||
@@ -155,6 +156,8 @@ export const initializeServices = async ({
|
||||
const httpProvider = new HttpProvider(got, new AdvancedMatcher(jmespath));
|
||||
const pageSpeedProvider = new PageSpeedProvider(httpProvider, settingsService, logger);
|
||||
const hardwareProvider = new HardwareProvider(httpProvider);
|
||||
const dockerProvider = new DockerProvider(logger, Docker);
|
||||
|
||||
const networkService = new NetworkService(
|
||||
axios,
|
||||
got,
|
||||
@@ -173,7 +176,8 @@ export const initializeServices = async ({
|
||||
pingProvider,
|
||||
httpProvider,
|
||||
pageSpeedProvider,
|
||||
hardwareProvider
|
||||
hardwareProvider,
|
||||
dockerProvider
|
||||
);
|
||||
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
|
||||
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js";
|
||||
import { DockerStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||
import { Monitor, MonitorType } from "@/types/monitor.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import Dockerode from "dockerode";
|
||||
import { NETWORK_ERROR, timeRequest } from "@/service/infrastructure/network/utils.js";
|
||||
|
||||
type DockerodeType = typeof Dockerode;
|
||||
|
||||
const SERVICE_NAME = "DockerProvider";
|
||||
|
||||
export interface DockerError extends Error {
|
||||
statusCode?: number;
|
||||
reason?: string;
|
||||
json?: { message?: string };
|
||||
}
|
||||
|
||||
export class DockerProvider implements IStatusProvider<DockerStatusPayload> {
|
||||
readonly type = "docker";
|
||||
private docker: Dockerode;
|
||||
constructor(
|
||||
private logger: ILogger,
|
||||
private DockerLib: DockerodeType
|
||||
) {
|
||||
this.docker = new this.DockerLib();
|
||||
}
|
||||
|
||||
supports(type: MonitorType): boolean {
|
||||
return type === "docker";
|
||||
}
|
||||
|
||||
private isDockerError(error: unknown): error is DockerError {
|
||||
return error instanceof Error && ("statusCode" in error || "reason" in error || "json" in error);
|
||||
}
|
||||
|
||||
async handle(monitor: Monitor): Promise<MonitorStatusResponse<DockerStatusPayload>> {
|
||||
const { url: containerInput } = monitor;
|
||||
try {
|
||||
if (!containerInput) {
|
||||
throw new Error("Container name or ID is required for Docker monitor");
|
||||
}
|
||||
|
||||
const containers = await this.docker.listContainers({ all: true });
|
||||
const normalizedInput = containerInput.replace(/^\/+/, "").toLowerCase();
|
||||
|
||||
// Priority-based matching to avoid ambiguity:
|
||||
// 1. Exact full ID match (64-char)
|
||||
const exactIdMatch = containers.find((c) => c.Id.toLowerCase() === normalizedInput);
|
||||
|
||||
// 2. Exact container name match (case-insensitive)
|
||||
const exactNameMatch = containers.find((c) =>
|
||||
c.Names.some((name: string) => {
|
||||
const cleanName = name.replace(/^\/+/, "").toLowerCase();
|
||||
return cleanName === normalizedInput;
|
||||
})
|
||||
);
|
||||
|
||||
// 3. Partial ID match (fallback for backwards compatibility)
|
||||
const partialIdMatch = containers.find((c) => c.Id.toLowerCase().startsWith(normalizedInput));
|
||||
|
||||
// Select container based on priority
|
||||
const targetContainer = exactIdMatch || exactNameMatch || partialIdMatch;
|
||||
|
||||
// Handle no match
|
||||
if (!targetContainer) {
|
||||
this.logger.warn({
|
||||
message: `No container found for "${monitor.url}".`,
|
||||
service: SERVICE_NAME,
|
||||
method: "handle",
|
||||
details: { url: monitor.url },
|
||||
});
|
||||
|
||||
return {
|
||||
monitorId: monitor.id,
|
||||
teamId: monitor.teamId,
|
||||
type: monitor.type,
|
||||
status: false,
|
||||
code: 404,
|
||||
message: "Docker container not found",
|
||||
responseTime: 0,
|
||||
payload: null,
|
||||
};
|
||||
}
|
||||
|
||||
// 5. Handle Ambiguity Check
|
||||
const matchTypes: string[] = [];
|
||||
if (exactIdMatch) matchTypes.push("exact ID");
|
||||
if (exactNameMatch) matchTypes.push("exact name");
|
||||
if (partialIdMatch && !exactIdMatch) matchTypes.push("partial ID");
|
||||
|
||||
if (matchTypes.length > 1) {
|
||||
const message = `Ambiguous container match for "${containerInput}". Matched by: ${matchTypes.join(", ")}. Using ${exactIdMatch ? "exact ID" : exactNameMatch ? "exact name" : "partial ID"} match.`;
|
||||
|
||||
this.logger.warn({
|
||||
message,
|
||||
service: SERVICE_NAME,
|
||||
method: "handle",
|
||||
details: { url: containerInput },
|
||||
});
|
||||
|
||||
return {
|
||||
monitorId: monitor.id,
|
||||
teamId: monitor.teamId,
|
||||
type: monitor.type,
|
||||
status: false,
|
||||
code: NETWORK_ERROR,
|
||||
message,
|
||||
responseTime: 0,
|
||||
payload: null,
|
||||
};
|
||||
}
|
||||
|
||||
// 6. Inspect Container Status
|
||||
const container = this.docker.getContainer(targetContainer.Id);
|
||||
const { response, responseTime, error } = await timeRequest(() => container.inspect());
|
||||
|
||||
if (error) {
|
||||
let message = "Failed to fetch Docker container information";
|
||||
let code = NETWORK_ERROR;
|
||||
|
||||
if (this.isDockerError(error)) {
|
||||
code = error.statusCode ?? NETWORK_ERROR;
|
||||
message = error.json?.message ?? error.reason ?? error.message;
|
||||
} else if (error instanceof Error) {
|
||||
message = error.message;
|
||||
}
|
||||
|
||||
return {
|
||||
monitorId: monitor.id,
|
||||
teamId: monitor.teamId,
|
||||
type: monitor.type,
|
||||
status: false,
|
||||
code: code,
|
||||
message: message,
|
||||
responseTime,
|
||||
payload: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
monitorId: monitor.id,
|
||||
teamId: monitor.teamId,
|
||||
type: monitor.type,
|
||||
status: response?.State?.Status === "running",
|
||||
code: 200,
|
||||
message: "Docker container status fetched successfully",
|
||||
responseTime,
|
||||
payload: response as unknown as DockerStatusPayload,
|
||||
};
|
||||
} catch (err: unknown) {
|
||||
throw new AppError({
|
||||
message: err instanceof Error ? err.message : "Error performing Docker request",
|
||||
service: SERVICE_NAME,
|
||||
method: "handle",
|
||||
details: { url: containerInput },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,7 @@ class NetworkService implements INetworkService {
|
||||
private httpProvider;
|
||||
private pageSpeedProvider;
|
||||
private hardwareProvider;
|
||||
private dockerProvider;
|
||||
|
||||
private buildStatusResponse = <T>({
|
||||
monitor,
|
||||
@@ -172,7 +173,8 @@ class NetworkService implements INetworkService {
|
||||
pingProvider: IStatusProvider<PingStatusPayload>,
|
||||
httpProvider: IStatusProvider<HttpStatusPayload>,
|
||||
pagespeedProvider: IStatusProvider<PageSpeedStatusPayload>,
|
||||
hardwareProvider: IStatusProvider<HardwareStatusPayload>
|
||||
hardwareProvider: IStatusProvider<HardwareStatusPayload>,
|
||||
dockerProvider: IStatusProvider<DockerStatusPayload>
|
||||
) {
|
||||
this.TYPE_PING = "ping";
|
||||
this.TYPE_HTTP = "http";
|
||||
@@ -202,6 +204,7 @@ class NetworkService implements INetworkService {
|
||||
this.httpProvider = httpProvider;
|
||||
this.pageSpeedProvider = pagespeedProvider;
|
||||
this.hardwareProvider = hardwareProvider;
|
||||
this.dockerProvider = dockerProvider;
|
||||
const cacheable = new CacheableLookup();
|
||||
|
||||
this.got = got.extend({
|
||||
@@ -254,9 +257,9 @@ class NetworkService implements INetworkService {
|
||||
case this.TYPE_PAGESPEED:
|
||||
return await this.pageSpeedProvider.handle(monitor);
|
||||
case this.TYPE_HARDWARE:
|
||||
return await this.requestHardware(monitor);
|
||||
return await this.hardwareProvider.handle(monitor);
|
||||
case this.TYPE_DOCKER:
|
||||
return await this.requestDocker(monitor);
|
||||
return await this.dockerProvider.handle(monitor);
|
||||
case this.TYPE_PORT:
|
||||
return await this.requestPort(monitor);
|
||||
case this.TYPE_GAME:
|
||||
|
||||
Reference in New Issue
Block a user