Skip to content

Commit

Permalink
Extract Docker client strategies to own modules (#561)
Browse files Browse the repository at this point in the history
  • Loading branch information
cristianrgreco authored May 9, 2023
1 parent 715b282 commit e26e744
Show file tree
Hide file tree
Showing 38 changed files with 238 additions and 233 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { RandomUuid, Uuid } from "../uuid";
import { Environment } from "../docker/types";
import { listContainers } from "../docker/functions/container/list-containers";
import { getContainerById } from "../docker/functions/container/get-container";
import { dockerClient } from "../docker/docker-client";
import { dockerClient } from "../docker/client/docker-client";
import { inspectContainer } from "../docker/functions/container/inspect-container";
import { containerLogs } from "../docker/functions/container/container-logs";
import { StartedDockerComposeEnvironment } from "./started-docker-compose-environment";
Expand Down
2 changes: 1 addition & 1 deletion src/docker-compose/default-docker-compose-options.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IDockerComposeOptions } from "docker-compose";
import { DockerComposeOptions } from "./docker-compose-options";
import { dockerClient } from "../docker/docker-client";
import { dockerClient } from "../docker/client/docker-client";
import { composeLog } from "../logger";
import { EOL } from "os";
import { isNotEmptyString } from "../type-guards";
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from "path";
import { log } from "../logger";
import { log } from "../../logger";
import { homedir } from "os";
import { existsSync } from "fs";
import { readFile } from "fs/promises";
Expand Down
87 changes: 87 additions & 0 deletions src/docker/client/docker-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import Dockerode from "dockerode";
import { log } from "../../logger";
import { HostIps, lookupHostIps } from "../lookup-host-ips";
import { getSystemInfo } from "../../system-info";
import { RootlessUnixSocketStrategy } from "./strategy/rootless-unix-socket-strategy";
import { streamToString } from "../../stream-utils";
import { Readable } from "stream";
import { resolveHost } from "../resolve-host";
import { DockerClientStrategy } from "./strategy/docker-client-strategy";
import { ConfigurationStrategy } from "./strategy/configuration-strategy";
import { UnixSocketStrategy } from "./strategy/unix-socket-strategy";
import { NpipeSocketStrategy } from "./strategy/npipe-socket-strategy";

export type Provider = "docker" | "podman";

export type DockerClient = {
uri: string;
provider: Provider;
host: string;
hostIps: HostIps;
dockerode: Dockerode;
indexServerAddress: string;
composeEnvironment: NodeJS.ProcessEnv;
};

export type DockerClientInit = {
uri: string;
dockerode: Dockerode;
composeEnvironment: NodeJS.ProcessEnv;
};

const getDockerClient = async (): Promise<DockerClient> => {
const strategies: DockerClientStrategy[] = [
new ConfigurationStrategy(),
new UnixSocketStrategy(),
new RootlessUnixSocketStrategy(),
new NpipeSocketStrategy(),
];

for (const strategy of strategies) {
if (strategy.init) {
await strategy.init();
}
if (strategy.isApplicable()) {
log.debug(`Found Docker client strategy "${strategy.getName()}"`);
const { uri, dockerode, composeEnvironment } = await strategy.getDockerClient();

log.debug(`Testing Docker client strategy "${uri}"...`);
if (await isDockerDaemonReachable(dockerode)) {
const indexServerAddress = (await getSystemInfo(dockerode)).dockerInfo.indexServerAddress;
const provider: Provider = uri.includes("podman.sock") ? "podman" : "docker";
const host = await resolveHost(dockerode, provider, indexServerAddress, uri);
const hostIps = await lookupHostIps(host);
logDockerClient(strategy.getName(), host, hostIps);
return { uri, provider, host, hostIps, dockerode, indexServerAddress, composeEnvironment };
} else {
log.warn(`Docker client strategy ${strategy.getName()} is not reachable`);
}
}
}

throw new Error("No Docker client strategy found");
};

const isDockerDaemonReachable = async (dockerode: Dockerode): Promise<boolean> => {
try {
const response = await dockerode.ping();
return (await streamToString(Readable.from(response))) === "OK";
} catch (err) {
log.warn(`Docker daemon is not reachable: ${err}`);
return false;
}
};

const logDockerClient = (strategyName: string, host: string, hostIps: HostIps) => {
const formattedHostIps = hostIps.map((hostIp) => hostIp.address).join(", ");
log.info(`Using Docker client strategy "${strategyName}", Docker host "${host}" (${formattedHostIps})`);
};

let _dockerClient: Promise<DockerClient>;

export const dockerClient: () => Promise<DockerClient> = () => {
if (!_dockerClient) {
_dockerClient = getDockerClient();
}
return _dockerClient;
};
57 changes: 57 additions & 0 deletions src/docker/client/strategy/configuration-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { DockerClientConfig, getDockerClientConfig } from "../docker-client-config";
import Dockerode, { DockerOptions } from "dockerode";
import { sessionId } from "../../session-id";
import { URL } from "url";
import { promises as fs } from "fs";
import path from "path";
import { DockerClientInit } from "../docker-client";
import { DockerClientStrategy } from "./docker-client-strategy";

export class ConfigurationStrategy implements DockerClientStrategy {
private dockerConfig!: DockerClientConfig;

async init(): Promise<void> {
this.dockerConfig = await getDockerClientConfig();
}

async getDockerClient(): Promise<DockerClientInit> {
const { dockerHost, dockerTlsVerify, dockerCertPath } = this.dockerConfig;

const dockerOptions: DockerOptions = { headers: { "x-tc-sid": sessionId } };

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { pathname, hostname, port } = new URL(dockerHost!);
if (hostname !== "") {
dockerOptions.host = hostname;
dockerOptions.port = port;
} else {
dockerOptions.socketPath = pathname;
}

if (dockerTlsVerify === "1" && dockerCertPath !== undefined) {
dockerOptions.ca = await fs.readFile(path.resolve(dockerCertPath, "ca.pem"));
dockerOptions.cert = await fs.readFile(path.resolve(dockerCertPath, "cert.pem"));
dockerOptions.key = await fs.readFile(path.resolve(dockerCertPath, "key.pem"));
}

return {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uri: dockerHost!,
dockerode: new Dockerode(dockerOptions),
composeEnvironment: {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
DOCKER_HOST: dockerHost!,
DOCKER_TLS_VERIFY: dockerTlsVerify,
DOCKER_CERT_PATH: dockerCertPath,
},
};
}

isApplicable(): boolean {
return this.dockerConfig.dockerHost !== undefined;
}

getName(): string {
return "ConfigurationStrategy";
}
}
11 changes: 11 additions & 0 deletions src/docker/client/strategy/docker-client-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DockerClientInit } from "../docker-client";

export interface DockerClientStrategy {
init?(): Promise<void>;

isApplicable(): boolean;

getDockerClient(): Promise<DockerClientInit>;

getName(): string;
}
21 changes: 21 additions & 0 deletions src/docker/client/strategy/npipe-socket-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Dockerode from "dockerode";
import { DockerClientInit } from "../docker-client";
import { DockerClientStrategy } from "./docker-client-strategy";

export class NpipeSocketStrategy implements DockerClientStrategy {
async getDockerClient(): Promise<DockerClientInit> {
return {
uri: "npipe:////./pipe/docker_engine",
dockerode: new Dockerode({ socketPath: "//./pipe/docker_engine" }),
composeEnvironment: {},
};
}

isApplicable(): boolean {
return process.platform === "win32";
}

getName(): string {
return "NpipeSocketStrategy";
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { isDefined } from "../type-guards";
import { isDefined } from "../../../type-guards";
import { existsSync } from "fs";
import path from "path";
import os from "os";
import Dockerode from "dockerode";
import { DockerClientInit, DockerClientStrategy } from "./docker-client";
import { DockerClientInit } from "../docker-client";
import { DockerClientStrategy } from "./docker-client-strategy";

export class RootlessUnixSocketStrategy implements DockerClientStrategy {
private applicable!: boolean;
Expand Down
22 changes: 22 additions & 0 deletions src/docker/client/strategy/unix-socket-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Dockerode from "dockerode";
import { existsSync } from "fs";
import { DockerClientInit } from "../docker-client";
import { DockerClientStrategy } from "./docker-client-strategy";

export class UnixSocketStrategy implements DockerClientStrategy {
async getDockerClient(): Promise<DockerClientInit> {
return {
uri: "unix:///var/run/docker.sock",
dockerode: new Dockerode({ socketPath: "/var/run/docker.sock" }),
composeEnvironment: {},
};
}

isApplicable(): boolean {
return (process.platform === "linux" || process.platform === "darwin") && existsSync("/var/run/docker.sock");
}

getName(): string {
return "UnixSocketStrategy";
}
}
Loading

0 comments on commit e26e744

Please sign in to comment.