the-moby-effect
Version:
Moby/Docker API client built using effect-ts
194 lines (176 loc) • 7.37 kB
text/typescript
import type * as HttpBody from "@effect/platform/HttpBody";
import type * as HttpClientError from "@effect/platform/HttpClientError";
import type * as Socket from "@effect/platform/Socket";
import type * as Layer from "effect/Layer";
import type * as ParseResult from "effect/ParseResult";
import * as PlatformError from "@effect/platform/Error";
import * as HttpClient from "@effect/platform/HttpClient";
import * as HttpClientRequest from "@effect/platform/HttpClientRequest";
import * as HttpClientResponse from "@effect/platform/HttpClientResponse";
import * as Effect from "effect/Effect";
import * as Function from "effect/Function";
import * as Option from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as Schema from "effect/Schema";
import * as Tuple from "effect/Tuple";
import * as MobyDemux from "../../MobyDemux.js";
import {
ContainerExecOptions as ContainerExecStartConfig,
ContainerExecOptions as ExecConfig,
ContainerExecInspect as ExecInspectResponse,
IDResponse,
} from "../generated/index.js";
import { maybeAddQueryParameter } from "./common.js";
/**
* @since 1.0.0
* @category Errors
*/
export const ExecsErrorTypeId: unique symbol = Symbol.for("@the-moby-effect/endpoints/ExecsError") as ExecsErrorTypeId;
/**
* @since 1.0.0
* @category Errors
*/
export type ExecsErrorTypeId = typeof ExecsErrorTypeId;
/**
* @since 1.0.0
* @category Errors
*/
export const isExecsError = (u: unknown): u is ExecsError => Predicate.hasProperty(u, ExecsErrorTypeId);
/**
* @since 1.0.0
* @category Errors
*/
export class ExecsError extends PlatformError.TypeIdError(ExecsErrorTypeId, "ExecsError")<{
method: string;
cause:
| ParseResult.ParseError
| HttpClientError.HttpClientError
| HttpBody.HttpBodyError
| Socket.SocketError
| unknown;
}> {
get message() {
return `${this.method}`;
}
}
/**
* Execs service
*
* @since 1.0.0
* @category Tags
* @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec
*/
export class Execs extends Effect.Service<Execs>()("@the-moby-effect/endpoints/Execs", {
accessors: false,
dependencies: [],
effect: Effect.gen(function* () {
const contextClient = yield* HttpClient.HttpClient;
const client = contextClient.pipe(HttpClient.filterStatusOk);
const maybeUpgradedClient = contextClient.pipe(
HttpClient.filterStatus((status) => (status >= 200 && status < 300) || status === 101)
);
/**
* Create an exec instance
*
* @param execConfig - Exec configuration
* @param id - ID or name of container
* @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec/operation/ContainerExec
*/
const container_ = (
id: string,
execConfig: typeof ExecConfig.Encoded
): Effect.Effect<Readonly<IDResponse>, ExecsError, never> =>
Function.pipe(
Schema.decode(ExecConfig)(execConfig),
Effect.map((body) =>
Tuple.make(HttpClientRequest.post(`/containers/${encodeURIComponent(id)}/exec`), body)
),
Effect.flatMap(Function.tupled(HttpClientRequest.schemaBodyJson(ExecConfig))),
Effect.flatMap(client.execute),
Effect.flatMap(HttpClientResponse.schemaBodyJson(IDResponse)),
Effect.mapError((cause) => new ExecsError({ method: "container", cause }))
);
/**
* Start an exec instance
*
* @param execStartConfig -
* @param id - Exec instance ID
* @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec/operation/ExecStart
*/
const start_ = <T extends boolean | undefined = undefined>(
id: string,
execStartConfig: Omit<typeof ContainerExecStartConfig.Encoded, "Detach"> & { Detach?: T }
): Effect.Effect<
T extends true ? void : MobyDemux.MultiplexedSocket | MobyDemux.RawSocket,
ExecsError,
never
> => {
type U = Effect.Effect<
T extends true ? void : MobyDemux.MultiplexedSocket | MobyDemux.RawSocket,
ExecsError,
never
>;
const response = Function.pipe(
Schema.decode(ContainerExecStartConfig)(execStartConfig),
Effect.map((body) => Tuple.make(HttpClientRequest.post(`/exec/${encodeURIComponent(id)}/start`), body)),
Effect.flatMap(Function.tupled(HttpClientRequest.schemaBodyJson(ContainerExecStartConfig))),
Effect.map(HttpClientRequest.setHeader("Upgrade", "tcp")),
Effect.map(HttpClientRequest.setHeader("Connection", "Upgrade")),
Effect.flatMap(maybeUpgradedClient.execute),
Effect.mapError((cause) => new ExecsError({ method: "start", cause }))
);
const toStreamingSock = Function.compose(
MobyDemux.responseToStreamingSocketOrFailUnsafe,
Effect.mapError((cause) => new ExecsError({ method: "start", cause }))
);
return execStartConfig.Detach
? (Function.pipe(response, Effect.asVoid) as U)
: (Function.pipe(response, Effect.flatMap(toStreamingSock)) as U);
};
/**
* Resize an exec instance
*
* @param id - Exec instance ID
* @param h - Height of the TTY session in characters
* @param w - Width of the TTY session in characters
* @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec/operation/ExecResize
*/
const resize_ = (
id: string,
options?:
| {
readonly h?: number;
readonly w?: number;
}
| undefined
): Effect.Effect<void, ExecsError, never> =>
Function.pipe(
HttpClientRequest.post(`/exec/${encodeURIComponent(id)}/resize`),
maybeAddQueryParameter("h", Option.fromNullable(options?.h)),
maybeAddQueryParameter("w", Option.fromNullable(options?.w)),
client.execute,
Effect.asVoid,
Effect.mapError((cause) => new ExecsError({ method: "resize", cause }))
);
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec/operation/ExecInspect */
const inspect_ = (id: string): Effect.Effect<ExecInspectResponse, ExecsError, never> =>
Function.pipe(
HttpClientRequest.get(`/exec/${encodeURIComponent(id)}/json`),
client.execute,
Effect.flatMap(HttpClientResponse.schemaBodyJson(ExecInspectResponse)),
Effect.mapError((cause) => new ExecsError({ method: "inspect", cause }))
);
return {
container: container_,
start: start_,
resize: resize_,
inspect: inspect_,
};
}),
}) {}
/**
* @since 1.0.0
* @category Layers
* @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec
*/
export const ExecsLayer: Layer.Layer<Execs, never, HttpClient.HttpClient> = Execs.Default;