Skip to content

Commit

Permalink
Fallback Docker host if unavailable
Browse files Browse the repository at this point in the history
  • Loading branch information
cristianrgreco committed Dec 10, 2021
1 parent df0590a commit 39f3f9d
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 64 deletions.
59 changes: 59 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"dockerode": "^3.3.1",
"get-port": "^5.1.1",
"glob": "^7.2.0",
"ping": "^0.4.1",
"properties-reader": "^2.2.0",
"slash": "^3.0.0",
"ssh-remote-port-forward": "^1.0.4",
Expand All @@ -55,6 +56,7 @@
"@types/node": "^16.9.6",
"@types/node-fetch": "^2.5.11",
"@types/pg": "^8.6.1",
"@types/ping": "^0.4.0",
"@types/properties-reader": "^2.1.1",
"@types/tar-fs": "^2.0.1",
"@typescript-eslint/eslint-plugin": "^4.31.2",
Expand Down
143 changes: 81 additions & 62 deletions src/docker/get-docker-host.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import Dockerode from "dockerode";
import { existsSync } from "fs";
import { promise as ping } from "ping";
import { getDockerHost } from "./get-docker-host";
import { runInContainer } from "./functions/run-in-container";

jest.mock("fs");
const mockedExistsSync = existsSync as jest.Mock;

jest.mock("ping");
const mockedProbe = ping.probe as jest.Mock;

jest.mock("./functions/run-in-container");
const mockedRunInContainer = runInContainer as jest.Mock;

Expand All @@ -17,95 +21,110 @@ describe("getDockerHost", () => {
delete process.env["TESTCONTAINERS_HOST_OVERRIDE"];
});

it("should return the host from DOCKER_HOST", async () => {
it("should skip a strategy if the probe fails", async () => {
process.env.DOCKER_HOST = "tcp://dockerHost:1000";
const fakeDockerode = {} as Dockerode;
mockedProbe.mockReturnValueOnce({ alive: false }).mockReturnValueOnce({ alive: true });

const host = await getDockerHost(fakeDockerode);
const host = await getDockerHost({ modem: { host: "tcp://modemHost:1000" } } as Dockerode);

expect(host).toBe("dockerHost");
expect(host).toBe("modemHost");
});

it("should return the host from the modem", async () => {
const fakeDockerode = { modem: { host: "tcp://modemHost:1000" } } as Dockerode;
describe("probe succeeds", () => {
beforeEach(() => {
mockedProbe.mockReturnValueOnce({ alive: true });
});

const host = await getDockerHost(fakeDockerode);
it("should return the host from DOCKER_HOST", async () => {
process.env.DOCKER_HOST = "tcp://dockerHost:1000";
const fakeDockerode = {} as Dockerode;

expect(host).toBe("modemHost");
});
const host = await getDockerHost(fakeDockerode);

it("should return the TESTCONTAINERS_HOST_OVERRIDE if it is set", async () => {
process.env["TESTCONTAINERS_HOST_OVERRIDE"] = "tcp://testcontainersHostOverride:1000";
const fakeDockerode = { modem: {} } as Dockerode;
expect(host).toBe("dockerHost");
});

const host = await getDockerHost(fakeDockerode);
it("should return the host from the modem", async () => {
const fakeDockerode = { modem: { host: "tcp://modemHost:1000" } } as Dockerode;

expect(host).toBe("testcontainersHostOverride");
});
const host = await getDockerHost(fakeDockerode);

it("should return localhost if not running within a container", async () => {
mockedExistsSync.mockReturnValueOnce(false);
const fakeDockerode = { modem: {} } as Dockerode;
expect(host).toBe("modemHost");
});

const host = await getDockerHost(fakeDockerode);
it("should return the TESTCONTAINERS_HOST_OVERRIDE if it is set", async () => {
process.env["TESTCONTAINERS_HOST_OVERRIDE"] = "tcp://testcontainersHostOverride:1000";
const fakeDockerode = { modem: {} } as Dockerode;

expect(host).toBe("localhost");
});
const host = await getDockerHost(fakeDockerode);

it("should return localhost if running in container and no network IPAM", async () => {
const fakeInspect = {};
const fakeNetwork = { inspect: () => Promise.resolve(fakeInspect) } as Dockerode.Network;
mockedExistsSync.mockReturnValueOnce(true);
const fakeDockerode = { modem: {}, getNetwork: (networkName: string) => fakeNetwork } as Dockerode;
expect(host).toBe("testcontainersHostOverride");
});

const host = await getDockerHost(fakeDockerode);
it("should return localhost if not running within a container", async () => {
mockedExistsSync.mockReturnValueOnce(false);
const fakeDockerode = { modem: {} } as Dockerode;

expect(host).toBe("localhost");
});
const host = await getDockerHost(fakeDockerode);

it("should return localhost if running in container and no network IPAM config", async () => {
const fakeInspect = { IPAM: {} };
const fakeNetwork = { inspect: () => Promise.resolve(fakeInspect) } as Dockerode.Network;
mockedExistsSync.mockReturnValueOnce(true);
const fakeDockerode = { modem: {}, getNetwork: (networkName: string) => fakeNetwork } as Dockerode;
expect(host).toBe("localhost");
});

const host = await getDockerHost(fakeDockerode);
it("should return localhost if running in container and no network IPAM", async () => {
const fakeInspect = {};
const fakeNetwork = { inspect: () => Promise.resolve(fakeInspect) } as Dockerode.Network;
mockedExistsSync.mockReturnValueOnce(true);
const fakeDockerode = { modem: {}, getNetwork: (networkName: string) => fakeNetwork } as Dockerode;

expect(host).toBe("localhost");
});
const host = await getDockerHost(fakeDockerode);

it("should return gateway from IPAM if running in container with IPAM", async () => {
const fakeInspect = { IPAM: { Config: [{ Gateway: "tcp://ipamGateway:1000" }] } };
const fakeNetwork = { inspect: () => Promise.resolve(fakeInspect) } as Dockerode.Network;
mockedExistsSync.mockReturnValueOnce(true);
const fakeDockerode = { modem: {}, getNetwork: (networkName: string) => fakeNetwork } as Dockerode;
expect(host).toBe("localhost");
});

const host = await getDockerHost(fakeDockerode);
it("should return localhost if running in container and no network IPAM config", async () => {
const fakeInspect = { IPAM: {} };
const fakeNetwork = { inspect: () => Promise.resolve(fakeInspect) } as Dockerode.Network;
mockedExistsSync.mockReturnValueOnce(true);
const fakeDockerode = { modem: {}, getNetwork: (networkName: string) => fakeNetwork } as Dockerode;

expect(host).toBe("ipamGateway");
});
const host = await getDockerHost(fakeDockerode);

it("should return gateway from container", async () => {
const fakeInspect = { IPAM: { Config: [] } };
const fakeNetwork = { inspect: () => Promise.resolve(fakeInspect) } as Dockerode.Network;
mockedExistsSync.mockReturnValueOnce(true);
mockedRunInContainer.mockReturnValueOnce(Promise.resolve("tcp://gatewayFromContainer:1000"));
const fakeDockerode = { modem: {}, getNetwork: (networkName: string) => fakeNetwork } as Dockerode;
expect(host).toBe("localhost");
});

const host = await getDockerHost(fakeDockerode);
it("should return gateway from IPAM if running in container with IPAM", async () => {
const fakeInspect = { IPAM: { Config: [{ Gateway: "tcp://ipamGateway:1000" }] } };
const fakeNetwork = { inspect: () => Promise.resolve(fakeInspect) } as Dockerode.Network;
mockedExistsSync.mockReturnValueOnce(true);
const fakeDockerode = { modem: {}, getNetwork: (networkName: string) => fakeNetwork } as Dockerode;

expect(host).toBe("gatewayFromContainer");
});
const host = await getDockerHost(fakeDockerode);

expect(host).toBe("ipamGateway");
});

it("should return gateway from container", async () => {
const fakeInspect = { IPAM: { Config: [] } };
const fakeNetwork = { inspect: () => Promise.resolve(fakeInspect) } as Dockerode.Network;
mockedExistsSync.mockReturnValueOnce(true);
mockedRunInContainer.mockReturnValueOnce(Promise.resolve("tcp://gatewayFromContainer:1000"));
const fakeDockerode = { modem: {}, getNetwork: (networkName: string) => fakeNetwork } as Dockerode;

const host = await getDockerHost(fakeDockerode);

expect(host).toBe("gatewayFromContainer");
});

it("should return localhost if cannot get gateway from container", async () => {
const fakeInspect = { IPAM: { Config: [] } };
const fakeNetwork = { inspect: () => Promise.resolve(fakeInspect) } as Dockerode.Network;
mockedExistsSync.mockReturnValueOnce(true);
mockedRunInContainer.mockReturnValueOnce(Promise.resolve(undefined));
const fakeDockerode = { modem: {}, getNetwork: (networkName: string) => fakeNetwork } as Dockerode;
it("should return localhost if cannot get gateway from container", async () => {
const fakeInspect = { IPAM: { Config: [] } };
const fakeNetwork = { inspect: () => Promise.resolve(fakeInspect) } as Dockerode.Network;
mockedExistsSync.mockReturnValueOnce(true);
mockedRunInContainer.mockReturnValueOnce(Promise.resolve(undefined));
const fakeDockerode = { modem: {}, getNetwork: (networkName: string) => fakeNetwork } as Dockerode;

const host = await getDockerHost(fakeDockerode);
const host = await getDockerHost(fakeDockerode);

expect(host).toBe("localhost");
expect(host).toBe("localhost");
});
});
});
11 changes: 9 additions & 2 deletions src/docker/get-docker-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Host } from "./types";
import { runInContainer } from "./functions/run-in-container";
import fs from "fs";
import { URL } from "url";
import { promise as ping } from "ping";

const DEFAULT_HOST = "localhost";

Expand All @@ -13,15 +14,21 @@ export const getDockerHost = async (dockerode: Dockerode): Promise<Host> => {

if (result) {
const hostname = result === DEFAULT_HOST ? DEFAULT_HOST : new URL(result).hostname;
log.info(`Docker host strategy ${hostStrategyName}: ${hostname}`);
return hostname;
if (await isHostAvailable(hostname)) {
log.info(`Docker host strategy ${hostStrategyName}: ${hostname}`);
return hostname;
} else {
log.warn(`Docker host strategy ${hostStrategyName}: ${hostname} is not available`);
}
}
}

log.info(`Docker host strategy FALLBACK: ${DEFAULT_HOST}`);
return DEFAULT_HOST;
};

const isHostAvailable = async (host: Host): Promise<boolean> => (await ping.probe(host, { timeout: 10_000 })).alive;

type HostStrategy = () => Promise<Host | undefined>;

const hostStrategies = (dockerode: Dockerode): { [hostStrategyName: string]: HostStrategy } => ({
Expand Down

0 comments on commit 39f3f9d

Please sign in to comment.