UNPKG

the-moby-effect

Version:
172 lines (158 loc) 6.63 kB
import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; import * as HttpClient from "effect/unstable/http/HttpClient"; import * as HttpApi from "effect/unstable/httpapi/HttpApi"; import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient"; import * as HttpApiEndpoint from "effect/unstable/httpapi/HttpApiEndpoint"; import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup"; import * as HttpApiSchema from "effect/unstable/httpapi/HttpApiSchema"; import type { MultiplexedSocket, RawSocket } from "../../MobyDemux.ts"; import { MobyConnectionOptions } from "../../MobyConnection.ts"; import { makeAgnosticHttpClientLayer } from "../../MobyPlatforms.ts"; import { responseToStreamingSocketOrFailUnsafe } from "../demux/hijack.ts"; import { ContainerExecStartOptions, ContainerExecOptions as ExecConfig, ContainerExecInspect as ExecInspectResponse, } from "../generated/index.ts"; import { ExecIdentifier } from "../schemas/id.ts"; import { DockerError } from "./circular.ts"; import { BadRequest, Conflict, InternalServerError, NotFound } from "./errors.ts"; /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec/operation/ContainerExec */ const createExecEndpoint = HttpApiEndpoint.post("container", "/containers/:id/exec", { params: { id: Schema.String }, payload: ExecConfig, success: Schema.Struct({ Id: ExecIdentifier }).pipe(HttpApiSchema.status(201)), // 201 Created error: [ NotFound, // 404 No such container Conflict, // 409 Container is not running InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec/operation/ExecStart */ const startExecEndpoint = HttpApiEndpoint.post("start", "/exec/:id/start", { params: { id: ExecIdentifier }, headers: { Upgrade: Schema.Literal("tcp"), Connection: Schema.Literal("Upgrade"), }, payload: ContainerExecStartOptions, success: [ HttpApiSchema.Empty(101), // 101 Switching Protocols HttpApiSchema.Empty(200), // 200 OK ], error: [ NotFound, // 404 No such Exec instance Conflict, // 409 Container is not running InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec/operation/ExecResize */ const resizeExecEndpoint = HttpApiEndpoint.post("resize", "/exec/:id/resize", { params: { id: ExecIdentifier }, query: { h: Schema.Number, w: Schema.Number, }, success: HttpApiSchema.Empty(200), // 200 OK error: [ BadRequest, // 400 Invalid parameters NotFound, // 404 No such Exec instance InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec/operation/ExecInspect */ const inspectExecEndpoint = HttpApiEndpoint.get("inspect", "/exec/:id/json", { params: { id: ExecIdentifier }, success: ExecInspectResponse, // 200 OK error: [ NotFound, // 404 No such Exec instance InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec */ const ExecsGroup = HttpApiGroup.make("exec").add( createExecEndpoint, startExecEndpoint, resizeExecEndpoint, inspectExecEndpoint ); /** * @since 1.0.0 * @category HttpApi * @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec */ export const ExecsApi = HttpApi.make("ExecsApi").add(ExecsGroup); /** * @since 1.0.0 * @category Services * @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec */ export class Execs extends Context.Service<Execs>()("@the-moby-effect/endpoints/Execs", { make: Effect.gen(function* () { const httpClient = yield* HttpClient.HttpClient; const ExecsError = DockerError.WrapForModule("execs"); const client = yield* HttpApiClient.group(ExecsApi, { group: "exec", httpClient }); const container_ = (id: string, payload: (typeof ExecConfig)["~type.make.in"]) => ExecConfig.makeEffect(payload).pipe( Effect.flatMap((payload) => client.container({ params: { id }, payload })), Effect.mapError(ExecsError("container")) ); const start_ = <const T extends boolean = false>( id: ExecIdentifier, payload: Omit<(typeof ContainerExecStartOptions)["~type.make.in"], "Detach"> & { Detach: T; } ): Effect.Effect<[T] extends [false] ? RawSocket | MultiplexedSocket : void, DockerError, never> => ContainerExecStartOptions.makeEffect(payload).pipe( Effect.flatMap((startOptions) => client.start({ params: { id }, payload: startOptions, headers: { Connection: "Upgrade", Upgrade: "tcp" }, // FIXME: Broken on undici responseMode: "response-only", }) ), Effect.flatMap(responseToStreamingSocketOrFailUnsafe), Effect.map( (socket) => (payload.Detach === true ? void 0 : socket) as [T] extends [false] ? RawSocket | MultiplexedSocket : void ), Effect.mapError(ExecsError("start")) ); const resize_ = (id: ExecIdentifier, params: { w: number; h: number }) => client.resize({ params: { id }, query: { ...params } }).pipe(Effect.mapError(ExecsError("resize"))); const inspect_ = (id: ExecIdentifier) => client.inspect({ params: { id } }).pipe(Effect.mapError(ExecsError("inspect"))); 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> = Layer.effect(Execs, Execs.make); /** * @since 1.0.0 * @category Layers * @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec */ export const ExecsLayerLocalSocket: Layer.Layer<Execs, never, HttpClient.HttpClient> = ExecsLayer.pipe( Layer.provide( makeAgnosticHttpClientLayer( MobyConnectionOptions.socket({ socketPath: "/var/run/docker.sock", }) ) ) );