UNPKG

the-moby-effect

Version:
745 lines (701 loc) 31.5 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 Stream from "effect/Stream"; import * as String from "effect/String"; import * as HttpClient from "effect/unstable/http/HttpClient"; import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"; import * as Url from "effect/unstable/http/Url"; 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 HttpApiError from "effect/unstable/httpapi/HttpApiError"; import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup"; import * as HttpApiSchema from "effect/unstable/httpapi/HttpApiSchema"; import * as Socket from "effect/unstable/socket/Socket"; import { MobyConnectionOptions } from "../../MobyConnection.ts"; import { makeAgnosticHttpClientLayer } from "../../MobyPlatforms.ts"; import { responseToStreamingSocketOrFailUnsafe } from "../demux/hijack.ts"; import { makeRawSocket } from "../demux/raw.ts"; import { ArchiveChange, ContainerConfig, ContainerCreateRequest, ContainerHealth, ContainerHostConfig, ContainerInspectResponse, ContainerState, ContainerStatsResponse, ContainerSummary, ContainerTopResponse, ContainerWaitResponse, } from "../generated/index.ts"; import { replacer as quoteWireNumbers } from "../platforms/agnostic.ts"; import { ContainerIdentifier } from "../schemas/id.ts"; import { DockerError } from "./circular.ts"; import { BadRequest, Conflict, Forbidden, InternalServerError, NotAcceptable, NotFound } from "./errors.ts"; import { BooleanFilter, StringFilter } from "./filters.ts"; /** @since 1.0.0 */ export const ListFilters = Schema.fromJsonString( Schema.Struct({ ancestor: Schema.optional(Schema.Array(Schema.String)), before: Schema.optional(Schema.Array(Schema.String)), expose: Schema.optional(Schema.Array(Schema.String)), exited: Schema.optional(Schema.Array(Schema.NumberFromString)), health: Schema.optional(Schema.Array(ContainerHealth.fields["Status"])), identifier: Schema.optional(Schema.Array(ContainerIdentifier)), // isolation: Schema.optional(Schema.Array(ContainerHostConfig.fields["Isolation"])), "is-task": Schema.optional(BooleanFilter("is-task")), label: Schema.optional(Schema.Array(Schema.String)), name: Schema.optional(Schema.Array(Schema.String)), network: Schema.optional(Schema.Array(Schema.String)), publish: Schema.optional(Schema.Array(Schema.String)), since: Schema.optional(Schema.Array(Schema.String)), status: Schema.optional(Schema.Array(ContainerState.fields["Status"])), volume: Schema.optional(StringFilter), }) ); /** @since 1.0.0 */ export const PruneFilters = Schema.fromJsonString( Schema.Struct({ until: Schema.optional(StringFilter), label: Schema.optional(Schema.Array(Schema.String)), }) ); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerList */ const listContainersEndpoint = HttpApiEndpoint.get("list", "/json", { query: { all: Schema.optional(Schema.Boolean), limit: Schema.optional(Schema.Number), size: Schema.optional(Schema.Boolean), filters: Schema.optional(ListFilters), }, success: Schema.Array(ContainerSummary), // 200 OK error: [ BadRequest, // 400 Bad parameter InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerCreate */ const createContainerEndpoint = HttpApiEndpoint.post("create", "/create", { query: { platform: Schema.optional(Schema.String), name: Schema.String.pipe(Schema.check(Schema.isPattern(/^[a-zA-Z0-9][a-zA-Z0-9_.-]+/)), Schema.optional), }, payload: ContainerCreateRequest, success: Schema.Struct({ Id: ContainerIdentifier, Warnings: Schema.NullOr(Schema.Array(Schema.String)), }).pipe(HttpApiSchema.status(201)), // 201 Created error: [ BadRequest, // 400 Bad parameter Forbidden, // 403 Forbidden NotFound, // 404 Not Found NotAcceptable, // 406 Not Acceptable Conflict, // 409 Name conflicts InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerInspect */ const inspectContainerEndpoint = HttpApiEndpoint.get("inspect", "/:identifier/json", { params: { identifier: ContainerIdentifier }, query: { size: Schema.optional(Schema.Boolean) }, success: ContainerInspectResponse, // 200 OK error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerTop */ const topContainerEndpoint = HttpApiEndpoint.get("top", "/:identifier/top", { params: { identifier: ContainerIdentifier }, query: { ps_args: Schema.optional(Schema.String) }, success: ContainerTopResponse, // 200 OK error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerLogs */ const logsContainerEndpoint = HttpApiEndpoint.get("logs", "/:identifier/logs", { params: { identifier: ContainerIdentifier }, query: { follow: Schema.optional(Schema.Boolean), stdout: Schema.optional(Schema.Boolean), stderr: Schema.optional(Schema.Boolean), since: Schema.optional(Schema.Number), until: Schema.optional(Schema.Number), timestamps: Schema.optional(Schema.Boolean), tail: Schema.optional(Schema.String), }, success: HttpApiSchema.StreamUint8Array(), // 200 OK (streaming response) error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerChanges */ const changesContainerEndpoint = HttpApiEndpoint.get("changes", "/:identifier/changes", { params: { identifier: ContainerIdentifier }, success: Schema.NullOr(Schema.Array(ArchiveChange)), // 200 OK error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerExport */ const exportContainerEndpoint = HttpApiEndpoint.get("export", "/:identifier/export", { params: { identifier: ContainerIdentifier }, success: HttpApiSchema.StreamUint8Array(), // 200 OK (streaming response) error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerStats */ const statsContainerEndpoint = HttpApiEndpoint.get("stats", "/:identifier/stats", { params: { identifier: ContainerIdentifier }, query: { stream: Schema.optional(Schema.Boolean), "one-shot": Schema.optional(Schema.Boolean), }, success: HttpApiSchema.StreamUint8Array(), // 200 OK (streaming response) error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerResize */ const resizeContainerEndpoint = HttpApiEndpoint.post("resize", "/:identifier/resize", { params: { identifier: ContainerIdentifier }, query: { h: Schema.optional(Schema.Number), w: Schema.optional(Schema.Number), }, success: HttpApiSchema.Empty(200), // 200 OK error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerStart */ const startContainerEndpoint = HttpApiEndpoint.post("start", "/:identifier/start", { params: { identifier: ContainerIdentifier }, query: { detachKeys: Schema.optional(Schema.String), }, success: [ HttpApiSchema.Empty(204), // 204 No Content HttpApiSchema.Empty(304), // 304 Container already started ], error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerStop */ const stopContainerEndpoint = HttpApiEndpoint.post("stop", "/:identifier/stop", { params: { identifier: ContainerIdentifier }, query: { signal: Schema.optional(Schema.String), t: Schema.optional(Schema.Number), }, success: [ HttpApiSchema.Empty(204), // 204 No Content HttpApiSchema.Empty(304), // 304 Container already stopped ], error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerRestart */ const restartContainerEndpoint = HttpApiEndpoint.post("restart", "/:identifier/restart", { params: { identifier: ContainerIdentifier }, query: { signal: Schema.optional(Schema.String), t: Schema.optional(Schema.Number), }, success: HttpApiSchema.Empty(204), // 204 No Content error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerKill */ const killContainerEndpoint = HttpApiEndpoint.post("kill", "/:identifier/kill", { params: { identifier: ContainerIdentifier }, query: { signal: Schema.optional(Schema.String), }, success: HttpApiSchema.Empty(204), // 204 No Content error: [ NotFound, // 404 Not Found Conflict, // 409 Container is not running InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerUpdate */ const updateContainerEndpoint = HttpApiEndpoint.post("update", "/:identifier/update", { params: { identifier: ContainerIdentifier }, payload: ContainerConfig, success: Schema.Struct({ Warnings: Schema.NullOr(Schema.Array(Schema.String)) }), // 200 OK error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerRename */ const renameContainerEndpoint = HttpApiEndpoint.post("rename", "/:identifier/rename", { params: { identifier: ContainerIdentifier }, query: { name: Schema.String }, success: HttpApiSchema.Empty(204), // 204 No Content error: [ NotFound, // 404 Not Found Conflict, // 409 Name already in use InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerPause */ const pauseContainerEndpoint = HttpApiEndpoint.post("pause", "/:identifier/pause", { params: { identifier: ContainerIdentifier }, success: HttpApiSchema.Empty(204), // 204 No Content error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerUnpause */ const unpauseContainerEndpoint = HttpApiEndpoint.post("unpause", "/:identifier/unpause", { params: { identifier: ContainerIdentifier }, success: HttpApiSchema.Empty(204), // 204 No Content error: [ NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerAttach */ const attachContainerEndpoint = HttpApiEndpoint.post("attach", "/:identifier/attach", { params: { identifier: ContainerIdentifier }, query: { detachKeys: Schema.optional(Schema.String), logs: Schema.optional(Schema.Boolean), stream: Schema.optional(Schema.Boolean), stdin: Schema.optional(Schema.Boolean), stdout: Schema.optional(Schema.Boolean), stderr: Schema.optional(Schema.Boolean), }, headers: { Upgrade: Schema.Literal("tcp"), Connection: Schema.Literal("Upgrade"), }, success: [ HttpApiSchema.Empty(101), // 101 Switching Protocols HttpApiSchema.Empty(200), // 200 OK ], error: [ BadRequest, // 400 Bad parameter NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerAttachWebsocket */ const attachWebsocketContainerEndpoint = HttpApiEndpoint.get("attachWebsocket", "/:identifier/attach/ws", { params: { identifier: ContainerIdentifier }, query: { detachKeys: Schema.optional(Schema.String), logs: Schema.optional(Schema.Boolean), stream: Schema.optional(Schema.Boolean), stdin: Schema.optional(Schema.Boolean), stdout: Schema.optional(Schema.Boolean), stderr: Schema.optional(Schema.Boolean), }, success: [ HttpApiSchema.Empty(101), // 101 Switching Protocols HttpApiSchema.Empty(200), // 200 OK ], error: [ BadRequest, // 400 Bad parameter NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerWait */ const waitContainerEndpoint = HttpApiEndpoint.post("wait", "/:identifier/wait", { params: { identifier: ContainerIdentifier }, query: { condition: Schema.optional(Schema.String) }, success: ContainerWaitResponse, // 200 OK error: [ BadRequest, // 400 Bad parameter NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerDelete */ const deleteContainerEndpoint = HttpApiEndpoint.delete("delete", "/:identifier", { params: { identifier: ContainerIdentifier }, query: { v: Schema.optional(Schema.Boolean), force: Schema.optional(Schema.Boolean), link: Schema.optional(Schema.Boolean), }, success: HttpApiSchema.Empty(204), // 204 No Content error: [ BadRequest, // 400 Bad parameter NotFound, // 404 Not Found Conflict, // 409 Conflict InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerArchive */ const archiveContainerEndpoint = HttpApiEndpoint.get("archive", "/:identifier/archive", { params: { identifier: ContainerIdentifier }, query: { path: Schema.String }, success: HttpApiSchema.StreamUint8Array(), // 200 OK (streaming response) error: [ BadRequest, // 400 Bad parameter NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerArchiveInfo */ const archiveInfoContainerEndpoint = HttpApiEndpoint.head("archiveInfo", "/:identifier/archive", { params: { identifier: ContainerIdentifier }, query: { path: Schema.String }, success: HttpApiSchema.Empty(200), // 200 OK error: [ BadRequest, // 400 Bad parameter NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/PutContainerArchive */ const putArchiveContainerEndpoint = HttpApiEndpoint.put("putArchive", "/:identifier/archive", { params: { identifier: ContainerIdentifier }, payload: HttpApiSchema.StreamUint8Array(), query: { path: Schema.String, noOverwriteDirNonDir: Schema.optional(Schema.String), copyUIDGidentifier: Schema.optional(Schema.String), }, success: HttpApiSchema.Empty(200), // 200 OK error: [ BadRequest, // 400 Bad parameter Forbidden, // 403 Forbidden NotFound, // 404 Not Found InternalServerError, ], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container/operation/ContainerPrune */ const pruneContainersEndpoint = HttpApiEndpoint.post("prune", "/prune", { query: { filters: Schema.optional(PruneFilters) }, success: Schema.Struct({ ContainersDeleted: Schema.NullOr(Schema.Array(ContainerIdentifier)), SpaceReclaimed: Schema.BigIntFromString.check(Schema.isBetweenBigInt({ minimum: 0n, maximum: 2n ** 64n - 1n })), }), // 200 OK error: [InternalServerError], }); /** @see https://docs.docker.com/reference/api/engine/latest/#tag/Container */ const ContainersGroup = HttpApiGroup.make("containers") .add( listContainersEndpoint, createContainerEndpoint, inspectContainerEndpoint, topContainerEndpoint, logsContainerEndpoint, changesContainerEndpoint, exportContainerEndpoint, statsContainerEndpoint, resizeContainerEndpoint, startContainerEndpoint, stopContainerEndpoint, restartContainerEndpoint, killContainerEndpoint, updateContainerEndpoint, renameContainerEndpoint, pauseContainerEndpoint, unpauseContainerEndpoint, attachContainerEndpoint, attachWebsocketContainerEndpoint, waitContainerEndpoint, deleteContainerEndpoint, archiveContainerEndpoint, archiveInfoContainerEndpoint, putArchiveContainerEndpoint, pruneContainersEndpoint ) .prefix("/containers"); /** * @since 1.0.0 * @category HttpApi * @see https://docs.docker.com/reference/api/engine/latest/#tag/Container */ export const ContainersApi = HttpApi.make("ContainersApi").add(ContainersGroup); /** * @since 1.0.0 * @category Services * @see https://docs.docker.com/reference/api/engine/latest/#tag/Container */ export class Containers extends Context.Service<Containers>()("@the-moby-effect/endpoints/Containers", { make: Effect.gen(function* () { type ContainersEndpoints = HttpApiGroup.Endpoints<typeof ContainersGroup>; type Options<Name extends ContainersEndpoints["identifier"]> = HttpApiEndpoint.WithIdentifier< ContainersEndpoints, Name >["~Query"]["Type"]; const httpClient = yield* HttpClient.HttpClient; const ContainersError = DockerError.WrapForModule("containers"); const websocketConstructor = yield* Socket.WebSocketConstructor; const client = yield* HttpApiClient.group(ContainersApi, { httpClient, group: "containers", }); const list_ = (options?: Options<"list"> | undefined) => client .list({ query: { all: options?.all, limit: options?.limit, size: options?.size, filters: options?.filters, }, }) .pipe(Effect.mapError(ContainersError("list"))); const create_ = ( options: Omit<(typeof ContainerCreateRequest)["~type.make.in"], "HostConfig"> & { readonly Name?: string | undefined; readonly Platform?: string | undefined; readonly HostConfig?: (typeof ContainerHostConfig)["~type.make.in"] | undefined; } ) => Effect.gen(function* () { const hostConfig = options.HostConfig ? yield* ContainerHostConfig.makeEffect(options.HostConfig) : undefined; const payload = yield* ContainerCreateRequest.makeEffect({ ...options, HostConfig: hostConfig }); return yield* client.create({ query: { name: options.Name, platform: options.Platform }, payload }); }).pipe(Effect.mapError(ContainersError("create"))); const inspect_ = (identifier: ContainerIdentifier, options?: Options<"inspect">) => client .inspect({ params: { identifier }, query: { ...options } }) .pipe(Effect.mapError(ContainersError("inspect"))); const top_ = (identifier: ContainerIdentifier, options?: Options<"top">) => client.top({ params: { identifier }, query: { ...options } }).pipe(Effect.mapError(ContainersError("top"))); const logs_ = (identifier: ContainerIdentifier, options?: Options<"logs">) => client .logs({ params: { identifier }, query: { ...options } }) .pipe(Stream.unwrap, Stream.decodeText(), Stream.splitLines, Stream.mapError(ContainersError("logs"))); const changes_ = (identifier: ContainerIdentifier) => client.changes({ params: { identifier } }).pipe(Effect.mapError(ContainersError("changes"))); const export_ = (identifier: ContainerIdentifier) => client.export({ params: { identifier } }).pipe(Stream.unwrap, Stream.mapError(ContainersError("export"))); const stats_ = (identifier: ContainerIdentifier, options?: Options<"stats">) => client.stats({ params: { identifier }, query: { ...options } }).pipe( Stream.unwrap, Stream.decodeText(), Stream.splitLines, Stream.map(quoteWireNumbers), Stream.mapEffect((line) => Schema.decodeEffect(Schema.fromJsonString(ContainerStatsResponse))(line)), Stream.mapError(ContainersError("stats")) ); const resize_ = (identifier: ContainerIdentifier, options?: Options<"resize">) => client .resize({ params: { identifier }, query: { ...options } }) .pipe(Effect.mapError(ContainersError("resize"))); const start_ = (identifier: ContainerIdentifier, options?: Options<"start">) => client .start({ params: { identifier }, query: { ...options } }) .pipe(Effect.mapError(ContainersError("start"))); const stop_ = (identifier: ContainerIdentifier, options?: Options<"stop">) => client .stop({ params: { identifier }, query: { ...options } }) .pipe(Effect.mapError(ContainersError("stop"))); const restart_ = (identifier: ContainerIdentifier, options?: Options<"restart">) => client .restart({ params: { identifier }, query: { ...options } }) .pipe(Effect.mapError(ContainersError("restart"))); const kill_ = (identifier: ContainerIdentifier, options?: Options<"kill">) => client .kill({ params: { identifier }, query: { ...options } }) .pipe(Effect.mapError(ContainersError("kill"))); const update_ = (identifier: ContainerIdentifier, config: (typeof ContainerConfig)["~type.make.in"]) => ContainerConfig.makeEffect(config).pipe( Effect.flatMap((payload) => client.update({ params: { identifier }, payload })), Effect.mapError(ContainersError("update")) ); const rename_ = (identifier: ContainerIdentifier, name: string) => client.rename({ params: { identifier }, query: { name } }).pipe(Effect.mapError(ContainersError("rename"))); const pause_ = (identifier: ContainerIdentifier) => client.pause({ params: { identifier } }).pipe(Effect.mapError(ContainersError("pause"))); const unpause_ = (identifier: ContainerIdentifier) => client.unpause({ params: { identifier } }).pipe(Effect.mapError(ContainersError("unpause"))); const attach_ = (identifier: ContainerIdentifier, options?: Options<"attach">) => client .attach({ params: { identifier }, query: { ...options }, headers: { Connection: "Upgrade", Upgrade: "tcp" }, // FIXME: Broken on undici responseMode: "response-only", }) .pipe( Effect.flatMap(responseToStreamingSocketOrFailUnsafe), Effect.mapError(ContainersError("attach")) ); const attachWebsocket_ = (identifier: ContainerIdentifier, options?: Options<"attachWebsocket">) => Effect.gen(function* () { const baseUrl = "ws://0.0.0.0"; const stripBaseUrl = String.replace(baseUrl, String.empty); const emptyResponse = new Response(undefined, { status: 200 }); const noopHttpClient = HttpClient.make((request) => Effect.succeed(HttpClientResponse.fromWeb(request, emptyResponse)) ); const client = yield* HttpApiClient.endpoint(ContainersApi, { endpoint: "attachWebsocket", httpClient: noopHttpClient, group: "containers", baseUrl, }); const response = yield* client({ responseMode: "response-only", params: { identifier }, query: { ...options }, }); const { hash, url, urlParams } = response.request; const wsUrl = yield* Url.make(url, urlParams, hash.valueOrUndefined).pipe( Effect.fromResult, Effect.mapError((_cause) => new HttpApiError.BadRequest()), Effect.map((url) => url.toString()), Effect.map(stripBaseUrl) ); const websocket = websocketConstructor(wsUrl); yield* Effect.callback<void, Socket.SocketError>((resume) => { if (websocket.readyState === 1) { resume(Effect.void); return; } const onOpen = () => { cleanup(); resume(Effect.void); }; const onFailure = (cause: unknown) => { cleanup(); resume( Effect.fail( new Socket.SocketError({ reason: new Socket.SocketOpenError({ kind: "Unknown", cause }), }) ) ); }; const cleanup = () => { websocket.removeEventListener("open", onOpen); websocket.removeEventListener("error", onFailure); websocket.removeEventListener("close", onFailure); }; websocket.addEventListener("open", onOpen, { once: true }); websocket.addEventListener("error", onFailure, { once: true }); websocket.addEventListener("close", onFailure, { once: true }); return Effect.sync(cleanup); }); const socket = yield* Socket.fromWebSocket( Effect.acquireRelease(Effect.succeed(websocket), (ws) => Effect.sync(() => ws.close())), { closeCodeIsError: (code) => code !== 1000 } ); return makeRawSocket(socket); }).pipe(Effect.mapError(ContainersError("attachWebsocket"))); const wait_ = (identifier: ContainerIdentifier, options?: Options<"wait">) => client .wait({ params: { identifier }, query: { ...options } }) .pipe(Effect.mapError(ContainersError("wait"))); const delete_ = (identifier: ContainerIdentifier, options?: Options<"delete">) => client .delete({ params: { identifier }, query: { ...options } }) .pipe(Effect.mapError(ContainersError("delete"))); const archive_ = (identifier: ContainerIdentifier, options: Options<"archive">) => client .archive({ params: { identifier }, query: { ...options } }) .pipe(Stream.unwrap, Stream.mapError(ContainersError("archive"))); const archiveInfo_ = (identifier: ContainerIdentifier, options: Options<"archiveInfo">) => client.archiveInfo({ params: { identifier }, query: { ...options }, responseMode: "response-only" }).pipe( Effect.flatMap( HttpClientResponse.schemaHeaders( Schema.Struct({ "x-docker-container-path-stat": Schema.StringFromBase64.pipe( Schema.decodeTo(Schema.fromJsonString(Schema.Unknown)), Schema.optional ), }) ) ), Effect.map(({ "x-docker-container-path-stat": pathStat }) => pathStat), Effect.mapError(ContainersError("archiveInfo")) ); const putArchive_ = <E, R>( identifier: ContainerIdentifier, stream: Stream.Stream<Uint8Array, E, R>, options: Options<"putArchive"> ) => Effect.contextWith((context: Context.Context<R>) => client.putArchive({ params: { identifier }, query: { ...options }, payload: Stream.provideContext(stream, context), }) ).pipe(Effect.mapError(ContainersError("putArchive"))); const prune_ = (filters?: Schema.Schema.Type<typeof PruneFilters> | undefined) => client.prune({ query: { filters } }).pipe(Effect.mapError(ContainersError("prune"))); return { list: list_, create: create_, inspect: inspect_, top: top_, logs: logs_, changes: changes_, export: export_, stats: stats_, resize: resize_, start: start_, stop: stop_, restart: restart_, kill: kill_, update: update_, rename: rename_, pause: pause_, unpause: unpause_, attach: attach_, attachWebsocket: attachWebsocket_, wait: wait_, delete: delete_, archive: archive_, archiveInfo: archiveInfo_, putArchive: putArchive_, prune: prune_, }; }), }) {} /** * @since 1.0.0 * @category Layers * @see https://docs.docker.com/reference/api/engine/latest/#tag/Container */ export const ContainersLayer: Layer.Layer<Containers, never, HttpClient.HttpClient | Socket.WebSocketConstructor> = Layer.effect(Containers, Containers.make); /** * @since 1.0.0 * @category Layers * @see https://docs.docker.com/reference/api/engine/latest/#tag/Container */ export const ContainersLayerLocalSocket: Layer.Layer< Containers, never, HttpClient.HttpClient | Socket.WebSocketConstructor > = ContainersLayer.pipe( Layer.provide( makeAgnosticHttpClientLayer( MobyConnectionOptions.socket({ socketPath: "/var/run/docker.sock", }) ) ) );