This commit is contained in:
1 parent
e3a713d2df
commit
e842177d9b
163 files changed
+7390
-1240
No files matched your search
@@ -1,26 +1,70 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import Docker from "dockerode";
|
||||
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||
|
||||
import { env } from "./env";
|
||||
import type { DockerEndpointCapability, DockerEndpointDescriptor } from "./endpoint-descriptor";
|
||||
import {
|
||||
createLegacySocketDescriptor,
|
||||
createLegacyTcpDescriptor,
|
||||
getDockerEndpointHost,
|
||||
parseDockerEndpointDescriptors,
|
||||
} from "./endpoint-descriptor";
|
||||
|
||||
export interface DockerInstance {
|
||||
endpointId: string;
|
||||
endpointName: string;
|
||||
host: string;
|
||||
descriptor: DockerEndpointDescriptor;
|
||||
instance: Docker;
|
||||
}
|
||||
|
||||
export interface DockerEndpointInitializationFailure {
|
||||
descriptor: DockerEndpointDescriptor;
|
||||
host: string;
|
||||
}
|
||||
|
||||
const logger = createLogger({ module: "dockerSingleton" });
|
||||
|
||||
export class DockerSingleton {
|
||||
private static instances: DockerInstance[] | null = null;
|
||||
private static initializationFailures: DockerEndpointInitializationFailure[] = [];
|
||||
|
||||
private createInstanceSafely(descriptor: DockerEndpointDescriptor): DockerInstance[] {
|
||||
try {
|
||||
return [createInstance(descriptor)];
|
||||
} catch (error) {
|
||||
const host = getDockerEndpointHost(descriptor);
|
||||
DockerSingleton.initializationFailures.push({ descriptor, host });
|
||||
logger.warn(
|
||||
new ErrorWithMetadata(
|
||||
"Failed to initialize Docker endpoint",
|
||||
{ endpointId: descriptor.id, host },
|
||||
{ cause: error },
|
||||
),
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private createInstances() {
|
||||
if (env.DOCKER_ENDPOINTS) {
|
||||
return parseDockerEndpointDescriptors(env.DOCKER_ENDPOINTS).flatMap((descriptor) =>
|
||||
this.createInstanceSafely(descriptor),
|
||||
);
|
||||
}
|
||||
|
||||
const socketPaths = env.DOCKER_SOCKET_PATHS;
|
||||
const hostVariable = env.DOCKER_HOSTNAMES;
|
||||
const portVariable = env.DOCKER_PORTS;
|
||||
|
||||
// Socket instances from DOCKER_SOCKET_PATHS
|
||||
const socketInstances: DockerInstance[] = socketPaths
|
||||
? socketPaths.split(",").map((socketPath) => ({
|
||||
host: socketPath,
|
||||
instance: new Docker({ socketPath }),
|
||||
}))
|
||||
? socketPaths
|
||||
.split(",")
|
||||
.flatMap((socketPath) => this.createInstanceSafely(createLegacySocketDescriptor(socketPath)))
|
||||
: [];
|
||||
|
||||
// TCP instances from existing DOCKER_HOSTNAMES/DOCKER_PORTS
|
||||
@@ -33,25 +77,34 @@ export class DockerSingleton {
|
||||
throw new Error("The number of hosts and ports must match");
|
||||
}
|
||||
|
||||
tcpInstances = hostnames.map((host, i) => ({
|
||||
host: `${host}:${ports[i]}`,
|
||||
tcpInstances = hostnames.flatMap((host, i) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
instance: new Docker({ host, port: parseInt(ports[i]!, 10) }),
|
||||
}));
|
||||
this.createInstanceSafely(createLegacyTcpDescriptor(host, parseInt(ports[i]!, 10))),
|
||||
);
|
||||
}
|
||||
|
||||
const instances = [...socketInstances, ...tcpInstances];
|
||||
|
||||
// Default: local socket if nothing else configured
|
||||
if (instances.length === 0) {
|
||||
return [{ host: "socket", instance: new Docker() }];
|
||||
const descriptor = {
|
||||
...createLegacySocketDescriptor("/var/run/docker.sock"),
|
||||
id: "socket:default",
|
||||
name: "Local Docker",
|
||||
source: "default" as const,
|
||||
};
|
||||
return [createInstance(descriptor)];
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
public static findInstance(host: string): DockerInstance | undefined {
|
||||
return this.instances?.find((instance) => instance.host === host);
|
||||
public static findInstance(endpointId: string): DockerInstance | undefined {
|
||||
return this.getInstances().find((instance) => instance.endpointId === endpointId);
|
||||
}
|
||||
|
||||
public static hasCapability(endpointId: string, capability: DockerEndpointCapability): boolean {
|
||||
return this.findInstance(endpointId)?.descriptor.capabilities.includes(capability) ?? false;
|
||||
}
|
||||
|
||||
public static getInstances(): DockerInstance[] {
|
||||
@@ -59,7 +112,41 @@ export class DockerSingleton {
|
||||
return this.instances;
|
||||
}
|
||||
|
||||
this.initializationFailures = [];
|
||||
this.instances = new DockerSingleton().createInstances();
|
||||
return this.instances;
|
||||
}
|
||||
|
||||
public static getInitializationFailures(): DockerEndpointInitializationFailure[] {
|
||||
this.getInstances();
|
||||
return [...this.initializationFailures];
|
||||
}
|
||||
|
||||
public static reset(): void {
|
||||
this.instances = null;
|
||||
this.initializationFailures = [];
|
||||
}
|
||||
}
|
||||
|
||||
const createInstance = (descriptor: DockerEndpointDescriptor): DockerInstance => ({
|
||||
endpointId: descriptor.id,
|
||||
endpointName: descriptor.name,
|
||||
host: getDockerEndpointHost(descriptor),
|
||||
descriptor,
|
||||
instance: new Docker(createDockerOptions(descriptor)),
|
||||
});
|
||||
|
||||
const createDockerOptions = (descriptor: DockerEndpointDescriptor) => {
|
||||
const transport = descriptor.transport;
|
||||
if (transport.type === "socket") return { socketPath: transport.path };
|
||||
if (transport.type === "tcp") return { host: transport.host, port: transport.port, protocol: "http" as const };
|
||||
return {
|
||||
host: transport.host,
|
||||
port: transport.port,
|
||||
protocol: "https" as const,
|
||||
ca: readFileSync(transport.caPath),
|
||||
...(transport.certPath && transport.keyPath
|
||||
? { cert: readFileSync(transport.certPath), key: readFileSync(transport.keyPath) }
|
||||
: {}),
|
||||
};
|
||||
};
|
||||
Reference in new issue
Block a user