the-moby-effect
Version:
Moby/Docker API client built using effect-ts
196 lines (179 loc) • 6.61 kB
text/typescript
import type * as MobyConnection from "../../MobyConnection.js";
import * as Path from "@effect/platform/Path";
import * as Config from "effect/Config";
import * as ConfigError from "effect/ConfigError";
import * as Data from "effect/Data";
import * as Effect from "effect/Effect";
import * as Function from "effect/Function";
import * as Match from "effect/Match";
import * as Redacted from "effect/Redacted";
/**
* Connection options for how to connect to your moby/docker instance. Can be a
* unix socket on the current machine. Can be an ssh connection to a remote
* machine with a remote user, remote machine, remote port, and remote socket
* path. Can be an http connection to a remote machine with a host, port, and
* path. Or it can be an https connection to a remote machine with a host, port,
* path, cert, ca, key, and passphrase.
*
* @since 1.0.0
* @category Connection Types
*/
export const MobyConnectionOptions = Data.taggedEnum<MobyConnection.MobyConnectionOptions>();
/**
* @since 1.0.0
* @category Connection Constructors
*/
export const SocketConnectionOptions = MobyConnectionOptions.socket;
/**
* Connects to a remote machine over ssh. This specific ssh implementation uses
* the OpenSSH extension "ForwardOutLocalStream" (so you must being running an
* OpenSSH server or something that implements the "ForwardOutLocalStream"
* extension) that opens a connection to a UNIX domain socket at socketPath on
* the server.
*
* @since 1.0.0
* @category Connection Constructors
*/
export const SshConnectionOptions = MobyConnectionOptions.ssh;
/**
* @since 1.0.0
* @category Connection Constructors
*/
export const HttpConnectionOptions = MobyConnectionOptions.http;
/**
* @since 1.0.0
* @category Connection Constructors
*/
export const HttpsConnectionOptions = MobyConnectionOptions.https;
/**
* From
* https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-socket-option
*
* "The Docker client will honor the DOCKER_HOST environment variable to set the
* -H flag for the client"
*
* And then from
* https://docs.docker.com/engine/reference/commandline/dockerd/#bind-docker-to-another-hostport-or-a-unix-socket
*
* "-H accepts host and port assignment in the following format:
* `tcp://[host]:[port][path]` or `unix://path`
*
* For example:
*
* - `unix://path/to/socket` -> Unix socket located at path/to/socket
* - When -H is empty, it will default to the same value as when no -H was passed
* in
* - `http://host:port/path` -> HTTP connection on host:port and prepend path to
* all requests
* - `https://host:port/path` -> HTTPS connection on host:port and prepend path to
* all requests
* - `ssh://me@example.com:22/var/run/docker.sock` -> SSH connection to
* example.com on port 22
*
* @since 1.0.0
* @category Constructors
*/
export const connectionOptionsFromUrl = (
dockerHost: string
): Effect.Effect<MobyConnection.MobyConnectionOptions, ConfigError.ConfigError, never> => {
const url: URL = new URL(dockerHost);
if (url.protocol === "unix:") {
return Effect.succeed(SocketConnectionOptions({ socketPath: url.pathname }));
}
if (url.protocol === "ssh:") {
return Effect.succeed(
SshConnectionOptions({
host: url.hostname,
username: url.username,
password: url.password,
remoteSocketPath: url.pathname,
port: url.port ? Number.parseInt(url.port) : 22,
})
);
}
if (url.protocol === "http:") {
return Effect.succeed(
HttpConnectionOptions({
host: url.hostname ?? "127.0.0.1",
port: url.port ? Number.parseInt(url.port) : 2375,
path: url.pathname,
})
);
}
if (url.protocol === "https:") {
return Effect.succeed(
HttpConnectionOptions({
host: url.hostname ?? "127.0.0.1",
port: url.port ? Number.parseInt(url.port) : 2376,
path: url.pathname,
})
);
}
if (url.protocol === "tcp:") {
const path: string = url.pathname;
const host: string = url.hostname ?? "127.0.0.0.1";
const port: number = url.port ? Number.parseInt(url.port) : 2375;
if (port === 2376) {
return Effect.succeed(HttpsConnectionOptions({ host, port, path }));
} else {
return Effect.succeed(HttpConnectionOptions({ host, port, path }));
}
}
// Any other protocols are not supported
return Effect.fail(ConfigError.InvalidData([], `Unsupported protocol ${url.protocol}`));
};
/**
* Creates a MobyApi layer from the DOCKER_HOST environment variable as a url.
*
* @since 1.0.0
* @category Constructors
*/
export const connectionOptionsFromDockerHostEnvironmentVariable: Effect.Effect<
MobyConnection.MobyConnectionOptions,
ConfigError.ConfigError,
never
> = Config.redacted("DOCKER_HOST")
.pipe(Config.withDefault(Redacted.make("unix:///var/run/docker.sock")))
.pipe(Config.map(Redacted.value))
.pipe(Effect.flatMap(connectionOptionsFromUrl));
/**
* Creates a MobyApi layer from the platform default system socket location.
*
* @since 1.0.0
* @category Constructors
*/
export const connectionOptionsFromPlatformSystemSocketDefault: Effect.Effect<
MobyConnection.MobyConnectionOptions,
ConfigError.ConfigError,
never
> = Function.pipe(
Match.value(process.platform),
Match.when("linux", () => Effect.succeed(SocketConnectionOptions({ socketPath: "/var/run/docker.sock" }))),
Match.when("darwin", () => Effect.succeed(SocketConnectionOptions({ socketPath: "/var/run/docker.sock" }))),
Match.when("win32", () => Effect.succeed(SocketConnectionOptions({ socketPath: "//./pipe/docker_engine" }))),
Match.orElse(() => Effect.fail(ConfigError.InvalidData([], `Unsupported platform ${process.platform}`)))
);
/**
* Creates a MobyApi layer from the platform default system socket location.
*
* @since 1.0.0
* @category Constructors
*/
export const connectionOptionsFromUserSocketDefault: Effect.Effect<
MobyConnection.MobyConnectionOptions,
never,
Path.Path
> = Function.pipe(
Effect.all(
{
pathLazy: Path.Path,
osLazy: Effect.promise(() => import("node:os")),
},
{ concurrency: 2 }
),
Effect.map(({ osLazy, pathLazy }) =>
SocketConnectionOptions({
socketPath: pathLazy.join(osLazy.homedir(), ".docker", "run", "docker.sock"),
})
)
);