the-moby-effect
Version:
Moby/Docker API client built using effect-ts
795 lines (735 loc) • 33.1 kB
text/typescript
import type * as Scope from "effect/Scope";
import * as Array from "effect/Array";
import * as Context from "effect/Context";
import * as Data from "effect/Data";
import * as Effect from "effect/Effect";
import * as Function from "effect/Function";
import * as Layer from "effect/Layer";
import * as Number from "effect/Number";
import * as Option from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as Record from "effect/Record";
import * as Schema from "effect/Schema";
import * as Sink from "effect/Sink";
import * as Stream from "effect/Stream";
import * as String from "effect/String";
import * as Tuple from "effect/Tuple";
import * as Socket from "effect/unstable/socket/Socket";
import type * as DockerComposeEngine from "../../DockerComposeEngine.js";
import type * as IdSchemas from "../schemas/id.js";
import * as DockerEngine from "../../DockerEngine.js";
import * as MobyDemux from "../../MobyDemux.js";
import * as MobyEndpoints from "../../MobyEndpoints.js";
/** @internal */
const makeTempDirScoped = (
containerId: IdSchemas.ContainerIdentifier
): Effect.Effect<string, DockerComposeError, MobyEndpoints.Containers | Scope.Scope> =>
Effect.acquireRelease(
Effect.gen(function* () {
const [stderr, stdout] = yield* Effect.mapError(
DockerEngine.execWebsockets({
containerId,
command: "mktemp -d",
}),
(cause) =>
new DockerComposeError({
cause,
method: "makeTempDirScoped",
})
);
if (String.isNonEmpty(stderr)) {
throw new DockerComposeError({
cause: stderr,
method: "makeTempDirScoped",
});
}
const schema = Schema.String.pipe(Schema.check(Schema.isStartsWith("/tmp/")));
const tempDir = yield* Effect.mapError(
Schema.decodeEffect(schema)(stdout),
(cause) =>
new DockerComposeError({
cause,
method: "makeTempDirScoped",
})
);
yield* Effect.annotateCurrentSpan("tempDir", tempDir);
return tempDir;
}),
(tempDir) =>
Effect.orDie(
Effect.mapError(
DockerEngine.execWebsockets({
containerId,
command: `rm -rf ${tempDir}`,
}),
(cause) =>
new DockerComposeError({
cause,
method: "makeTempDirScoped",
})
)
)
);
/** @internal */
const uploadProject = Function.dual<
<E1, R1>(
project: Stream.Stream<Uint8Array, E1, R1>
) => (
containerId: IdSchemas.ContainerIdentifier
) => Effect.Effect<string, E1 | DockerComposeError, R1 | MobyEndpoints.Containers | Scope.Scope>,
<E1, R1>(
containerId: IdSchemas.ContainerIdentifier,
project: Stream.Stream<Uint8Array, E1, R1>
) => Effect.Effect<string, E1 | DockerComposeError, R1 | MobyEndpoints.Containers | Scope.Scope>
>(
2,
<E1, R1>(
containerId: IdSchemas.ContainerIdentifier,
project: Stream.Stream<Uint8Array, E1, R1>
): Effect.Effect<string, E1 | DockerComposeError, R1 | MobyEndpoints.Containers | Scope.Scope> =>
Effect.gen(function* () {
const containers = yield* MobyEndpoints.Containers;
const projectUploadDir = yield* makeTempDirScoped(containerId);
yield* Effect.annotateCurrentSpan("projectUploadDir", projectUploadDir);
yield* containers.putArchive(containerId, project, { path: projectUploadDir });
return projectUploadDir;
}).pipe(
Effect.catchTag(
"DockerError",
(cause) =>
new DockerComposeError({
cause,
method: "uploadProject",
})
)
)
);
/** @internal */
const camelToKebab = Function.compose(String.camelToSnake, String.snakeToKebab);
/** @internal */
const stringifyOptions = (
options: Record<
string,
| string
| number
| boolean
| undefined
| Array<string | number | boolean>
| Record<string, string | number | boolean>
>
): string =>
Function.pipe(
options,
Record.toEntries,
Array.filter(([, value]) => Predicate.isNotUndefined(value)),
Array.flatMap(([key, value]): Array<readonly [string, string | number | boolean]> => {
if (Predicate.isString(value) || Predicate.isNumber(value) || Predicate.isBoolean(value)) {
return [Tuple.make(key, value)];
} else if (Array.isArray(value)) {
return Array.map(value, (v) => [key, v]);
} else if (Predicate.isObject(value)) {
return Record.toEntries(value);
} else {
return Function.absurd(value as never);
}
}),
Array.map(([key, value]) => `--${camelToKebab(key)}=${value}`),
Array.join(" ")
);
/** @internal */
const runCommand = (
containerId: IdSchemas.ContainerIdentifier,
projectPath: string,
method: string,
services: ReadonlyArray<string> | undefined = [],
options:
| Record<
string,
| string
| number
| boolean
| undefined
| Array<string | number | boolean>
| Record<string, string | number | boolean>
>
| undefined = {}
): Stream.Stream<Uint8Array, DockerComposeError, MobyEndpoints.Containers> =>
Effect.gen(function* () {
const multiplexed = yield* DockerEngine.execWebsocketsNonBlocking({
containerId,
cwd: projectPath,
command: `COMPOSE_STATUS_STDOUT=1 docker compose ${method} ${stringifyOptions(options)} ${Array.join(services, " ")}`,
});
const { stderr, stdin, stdout } = yield* MobyDemux.fan(multiplexed, { requestedCapacity: 16 });
const streamFailableEnsuring = <A, X, E1, E2, R1, R2>(
stream: Stream.Stream<A, E1, R1>,
effect: Effect.Effect<X, E2, R2>
): Stream.Stream<A, E1 | E2, R1 | R2> =>
Stream.flatMap(stream, (a: A) => Stream.fromEffect(Effect.map(effect, Function.constant(a))));
return streamFailableEnsuring(
MobyDemux.mergeRawToTaggedStream(stdout, stderr),
MobyDemux.demuxRawToSingleSink(stdin, Stream.make(new Socket.CloseEvent()), Sink.drain)
);
}).pipe(
Stream.unwrap,
Stream.mapEffect((entry) => {
if (Predicate.isTagged(entry, "stdout")) {
return Effect.succeed(entry.value);
} else {
const decoded = new TextDecoder("utf-8").decode(entry.value);
return Effect.fail(new DockerComposeError({ method, cause: decoded }));
}
}),
Stream.mapError(
(cause) =>
new DockerComposeError({
cause,
method,
})
)
);
/** @internal */
export const DockerComposeErrorTypeId: DockerComposeEngine.DockerComposeErrorTypeId = Symbol.for(
"@the-moby-effect/engines/DockerCompose/DockerComposeError"
) as DockerComposeEngine.DockerComposeErrorTypeId;
/** @internal */
export const TypeId: DockerComposeEngine.TypeId = Symbol.for(
"@the-moby-effect/engines/DockerCompose"
) as DockerComposeEngine.TypeId;
/** @internal */
export const DockerComposeProjectTypeId: DockerComposeEngine.DockerComposeProjectTypeId = Symbol.for(
"@the-moby-effect/engines/DockerComposeProject"
) as DockerComposeEngine.DockerComposeProjectTypeId;
/** @internal */
export const isDockerComposeError = (u: unknown): u is DockerComposeError =>
Predicate.hasProperty(u, DockerComposeErrorTypeId);
/** @internal */
export class DockerComposeError extends Data.TaggedError("DockerComposeError")<{
method: string;
cause: Schema.SchemaError | Socket.SocketError | DockerEngine.DockerError | unknown;
}> {
public readonly [DockerComposeErrorTypeId]: DockerComposeEngine.DockerComposeErrorTypeId = DockerComposeErrorTypeId;
public override get message() {
return `${String.capitalize(this.method)}`;
}
}
/** @internal */
export const DockerCompose: Context.Service<DockerComposeEngine.DockerCompose, DockerComposeEngine.DockerCompose> =
Context.Service<DockerComposeEngine.DockerCompose>("@the-moby-effect/engines/DockerCompose");
/** @internal */
export const make: (options?: {
dockerEngineSocket?: string | undefined;
}) => Effect.Effect<
DockerComposeEngine.DockerCompose,
DockerEngine.DockerError,
MobyEndpoints.Containers | MobyEndpoints.System | MobyEndpoints.Images | Scope.Scope
> = Effect.fnUntraced(function* (options) {
const containers = yield* MobyEndpoints.Containers;
const runCommandHelper = Function.flow(runCommand, Stream.provideService(MobyEndpoints.Containers, containers));
yield* DockerEngine.pingHead();
const build =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.BuildOptions | undefined
): Stream.Stream<string, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "build", services, { ...options }),
Stream.decodeText(),
Stream.splitLines
);
const config =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.ConfigOptions | undefined
): Effect.Effect<string, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "config", services, { ...options }),
Stream.decodeText(),
Stream.splitLines,
Stream.mkString
);
const cpTo =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
<E1, R1>(
service: string,
localSrc: Stream.Stream<Uint8Array, E1, R1>,
remoteDestLocation: string,
options?: DockerComposeEngine.CopyOptions | undefined
): Effect.Effect<void, E1 | DockerComposeError, R1> =>
Effect.gen(function* () {
const remoteTransferDir = yield* Effect.provideService(
makeTempDirScoped(dindContainerId),
MobyEndpoints.Containers,
containers
);
yield* Effect.mapError(
containers.putArchive(dindContainerId, localSrc, { path: remoteTransferDir }),
(cause) => new DockerComposeError({ method: "cpTo", cause })
);
const command = `cp ${remoteTransferDir} ${service}:${remoteDestLocation}`;
return yield* Stream.runDrain(
runCommandHelper(dindContainerId, projectPath, command, [], { ...options })
);
}).pipe(Effect.scoped);
const cpFrom =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
service: string,
remoteSrcLocation: string,
options?: DockerComposeEngine.CopyOptions | undefined
): Stream.Stream<Uint8Array, DockerComposeError, never> =>
Function.pipe(
makeTempDirScoped(dindContainerId),
Stream.fromEffect,
Stream.scoped,
Stream.provideService(MobyEndpoints.Containers, containers),
Stream.tap((remoteTransferDir) =>
Stream.runDrain(
runCommandHelper(
dindContainerId,
projectPath,
`cp ${service}:${remoteSrcLocation} ${remoteTransferDir}`,
[],
{
...options,
}
)
)
),
Stream.flatMap((remoteTransferDir) =>
Stream.mapError(
containers.archive(dindContainerId, { path: remoteTransferDir }),
(cause) =>
new DockerComposeError({
cause,
method: "cpFrom",
})
)
)
);
const create =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.CreateOptions | undefined
): Effect.Effect<void, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "create", services, { ...options }),
Stream.runDrain
);
const down =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.DownOptions | undefined
): Effect.Effect<void, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "down", services, { ...options }),
Stream.runDrain
);
const events =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.EventsOptions | undefined
): Stream.Stream<string, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "events", services, { ...options }),
Stream.decodeText(),
Stream.splitLines
);
const exec =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
service: string,
command: string,
args?: Array<string> | undefined,
options?: DockerComposeEngine.ExecOptions | undefined
): Effect.Effect<
MobyDemux.MultiplexedChannel<never, DockerEngine.DockerError | Socket.SocketError, never>,
DockerComposeError,
Scope.Scope
> =>
Effect.provideService(
DockerEngine.execWebsocketsNonBlocking({
containerId: dindContainerId,
cwd: projectPath,
command: `COMPOSE_STATUS_STDOUT=1 docker compose exec ${stringifyOptions({ ...options })} ${service} ${command} ${Array.join(args ?? [], " ")}`,
}),
MobyEndpoints.Containers,
containers
);
const images =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.ImagesOptions | undefined
): Stream.Stream<string, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "images", services, { ...options }),
Stream.decodeText(),
Stream.splitLines
);
const kill =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.KillOptions | undefined
): Effect.Effect<void, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "kill", services, { ...options }),
Stream.runDrain
);
const logs =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.LogsOptions | undefined
): Stream.Stream<string, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "logs", services, { ...options }),
Stream.decodeText(),
Stream.splitLines
);
const ls =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(options?: DockerComposeEngine.ListOptions | undefined): Stream.Stream<string, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "ls", [], { ...options }),
Stream.decodeText(),
Stream.splitLines
);
const pause =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(services?: Array<string> | undefined): Effect.Effect<void, DockerComposeError, never> =>
Function.pipe(runCommandHelper(dindContainerId, projectPath, "pause", services), Stream.runDrain);
const port =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
service: string,
privatePort: number,
options?: DockerComposeEngine.PortOptions | undefined
): Effect.Effect<number, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "port", [service, privatePort.toString()], {
...options,
}),
Stream.decodeText(),
Stream.splitLines,
Stream.take(1),
Stream.runHead,
Effect.map(Option.flatMap(Number.parse)),
Effect.map(Option.getOrThrow)
);
const ps =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.PsOptions | undefined
): Stream.Stream<string, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "ps", services, { ...options }),
Stream.decodeText(),
Stream.splitLines
);
const pull =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.PullOptions | undefined
): Stream.Stream<string, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "pull", services, { ...options }),
Stream.decodeText(),
Stream.splitLines
);
const push =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.PushOptions | undefined
): Effect.Effect<void, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "push", services, { ...options }),
Stream.runDrain
);
const restart =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.RestartOptions | undefined
): Effect.Effect<void, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "restart", services, { ...options }),
Stream.runDrain
);
const rm =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.RmOptions | undefined
): Effect.Effect<void, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "rm", services, { ...options }),
Stream.runDrain
);
const run =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
service: string,
command: string,
args?: Array<string> | undefined,
options?: DockerComposeEngine.RunOptions | undefined
): Effect.Effect<
MobyDemux.MultiplexedChannel<never, DockerEngine.DockerError | Socket.SocketError, never>,
DockerComposeError,
Scope.Scope
> =>
Effect.provideService(
DockerEngine.execWebsocketsNonBlocking({
containerId: dindContainerId,
cwd: projectPath,
command: `COMPOSE_STATUS_STDOUT=1 docker compose run ${stringifyOptions({ ...options })} ${service} ${command} ${Array.join(args ?? [], " ")}`,
}),
MobyEndpoints.Containers,
containers
);
const start =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(services?: Array<string> | undefined): Effect.Effect<void, DockerComposeError, never> =>
Function.pipe(runCommandHelper(dindContainerId, projectPath, "start", services), Stream.runDrain);
const stop =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.StopOptions | undefined
): Effect.Effect<void, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "stop", services, { ...options }),
Stream.runDrain
);
const top =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(services?: Array<string> | undefined): Stream.Stream<string, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "top", services),
Stream.decodeText(),
Stream.splitLines
);
const unpause =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(services?: Array<string> | undefined): Effect.Effect<void, DockerComposeError, never> =>
Function.pipe(runCommandHelper(dindContainerId, projectPath, "unpause", services), Stream.runDrain);
const up =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services?: Array<string> | undefined,
options?: DockerComposeEngine.UpOptions | undefined
): Effect.Effect<void, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "up", services, { ...options }),
Stream.runDrain
);
const version =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(options?: DockerComposeEngine.VersionOptions | undefined): Effect.Effect<string, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "version", [], { ...options }),
Stream.decodeText(),
Stream.mkString
);
const wait =
(dindContainerId: IdSchemas.ContainerIdentifier, projectPath: string) =>
(
services: Array.NonEmptyReadonlyArray<string>,
options?: DockerComposeEngine.WaitOptions | undefined
): Effect.Effect<void, DockerComposeError, never> =>
Function.pipe(
runCommandHelper(dindContainerId, projectPath, "wait", services, { ...options }),
Stream.runDrain
);
const pullStream = DockerEngine.pull({ image: "docker.io/library/docker:latest" });
yield* Stream.runDrain(pullStream);
const dindContainerIdResource = yield* DockerEngine.runScoped({
Image: "docker.io/library/docker:latest",
Entrypoint: ["/bin/sh"],
Tty: false,
OpenStdin: true,
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
HostConfig: {
Privileged: true,
Binds: [`${options?.dockerEngineSocket ?? "/var/run/docker.sock"}:/var/run/docker.sock`],
},
}).pipe(Effect.map(({ Id }) => Id));
const uncurryEffectWithUploadProject =
(dindContainerId: IdSchemas.ContainerIdentifier) =>
<P extends Array<any>, A, E, R>(
func: (
dindContainerId: IdSchemas.ContainerIdentifier,
projectPath: string
) => (...args: P) => Effect.Effect<A, E, R>
) =>
<E1, R1>(
project: Stream.Stream<Uint8Array, E1, R1>,
...args: P
): Effect.Effect<A, E | E1 | DockerComposeError, Exclude<R, Scope.Scope> | Exclude<R1, Scope.Scope>> =>
uploadProject(project)(dindContainerId).pipe(
Effect.provideService(MobyEndpoints.Containers, containers),
Effect.flatMap((cwd) => func(dindContainerId, cwd)(...args)),
Effect.scoped
);
const uncurryStreamWithUploadProject =
(dindContainerId: IdSchemas.ContainerIdentifier) =>
<P extends Array<any>, A, E, R>(
func: (
dindContainerId: IdSchemas.ContainerIdentifier,
projectPath: string
) => (...args: P) => Stream.Stream<A, E, R>
) =>
<E1, R1>(
project: Stream.Stream<Uint8Array, E1, R1>,
...args: P
): Stream.Stream<A, E | E1 | DockerComposeError, R | R1> =>
uploadProject(project)(dindContainerId).pipe(
Stream.fromEffect,
Stream.scoped,
Stream.provideService(MobyEndpoints.Containers, containers),
Stream.flatMap((cwd) => func(dindContainerId, cwd)(...args))
);
const uncurryEffect = uncurryEffectWithUploadProject(dindContainerIdResource);
const uncurryStream = uncurryStreamWithUploadProject(dindContainerIdResource);
// Actual compose implementation
return DockerCompose.of({
[TypeId]: TypeId,
build: uncurryStream(build),
config: uncurryEffect(config),
cpTo: <E1, R1, E2, R2>(
project: Stream.Stream<Uint8Array, E1, R1>,
service: string,
localSrc: Stream.Stream<Uint8Array, E2, R2>,
remoteDestLocation: string,
options?: DockerComposeEngine.CopyOptions | undefined
): Effect.Effect<void, E1 | E2 | DockerComposeError, Exclude<R1, Scope.Scope> | Exclude<R2, Scope.Scope>> =>
Effect.gen(function* () {
const cwd = yield* Effect.provideService(
uploadProject(project)(dindContainerIdResource),
MobyEndpoints.Containers,
containers
);
yield* cpTo(dindContainerIdResource, cwd)(service, localSrc, remoteDestLocation, options);
}).pipe(Effect.scoped),
cpFrom: uncurryStream(cpFrom),
create: uncurryEffect(create),
down: uncurryEffect(down),
events: uncurryStream(events),
exec: uncurryEffect(exec),
images: uncurryStream(images),
kill: uncurryEffect(kill),
logs: uncurryStream(logs),
ls: uncurryStream(ls),
pause: uncurryEffect(pause),
port: uncurryEffect(port),
ps: uncurryStream(ps),
pull: uncurryStream(pull),
push: uncurryEffect(push),
restart: uncurryEffect(restart),
rm: uncurryEffect(rm),
run: uncurryEffect(run),
start: uncurryEffect(start),
stop: uncurryEffect(stop),
top: uncurryStream(top),
unpause: uncurryEffect(unpause),
up: uncurryEffect(up),
version: uncurryEffect(version),
wait: uncurryEffect(wait),
forProject: <E1>(
project: Stream.Stream<Uint8Array, E1, never>
): Effect.Effect<
DockerComposeEngine.DockerComposeProject,
E1 | DockerComposeError | DockerEngine.DockerError,
Scope.Scope
> =>
Effect.gen(function* () {
const projectDindContainerIdResource = yield* DockerEngine.runScoped({
Image: "docker.io/library/docker:latest",
Entrypoint: ["/bin/sh"],
Tty: false,
OpenStdin: true,
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
HostConfig: {
Privileged: true,
Binds: [`${options?.dockerEngineSocket ?? "/var/run/docker.sock"}:/var/run/docker.sock`],
},
}).pipe(
Effect.map(({ Id }) => Id),
Effect.provideService(MobyEndpoints.Containers, containers)
);
const cwd = yield* Effect.provideService(
uploadProject(projectDindContainerIdResource, project),
MobyEndpoints.Containers,
containers
);
return {
[DockerComposeProjectTypeId]: DockerComposeProjectTypeId,
build: build(projectDindContainerIdResource, cwd),
config: config(projectDindContainerIdResource, cwd),
cpTo: cpTo(projectDindContainerIdResource, cwd),
cpFrom: cpFrom(projectDindContainerIdResource, cwd),
create: create(projectDindContainerIdResource, cwd),
down: down(projectDindContainerIdResource, cwd),
events: events(projectDindContainerIdResource, cwd),
exec: exec(projectDindContainerIdResource, cwd),
images: images(projectDindContainerIdResource, cwd),
kill: kill(projectDindContainerIdResource, cwd),
logs: logs(projectDindContainerIdResource, cwd),
ls: ls(projectDindContainerIdResource, cwd),
pause: pause(projectDindContainerIdResource, cwd),
port: port(projectDindContainerIdResource, cwd),
ps: ps(projectDindContainerIdResource, cwd),
pull: pull(projectDindContainerIdResource, cwd),
push: push(projectDindContainerIdResource, cwd),
restart: restart(projectDindContainerIdResource, cwd),
rm: rm(projectDindContainerIdResource, cwd),
run: run(projectDindContainerIdResource, cwd),
start: start(projectDindContainerIdResource, cwd),
stop: stop(projectDindContainerIdResource, cwd),
top: top(projectDindContainerIdResource, cwd),
unpause: unpause(projectDindContainerIdResource, cwd),
up: up(projectDindContainerIdResource, cwd),
version: version(projectDindContainerIdResource, cwd),
wait: wait(projectDindContainerIdResource, cwd),
};
}),
});
});
/** @internal */
export const layer = (
options?: { dockerEngineSocket?: string | undefined } | undefined
): Layer.Layer<
DockerComposeEngine.DockerCompose,
DockerEngine.DockerError,
MobyEndpoints.Containers | MobyEndpoints.System | MobyEndpoints.Images
> => Layer.effect(DockerCompose, make(options));
/** @internal */
export const layerProject: <E1>(
project: Stream.Stream<Uint8Array, E1, never>,
tagIdentifier: string
) => {
readonly tag: Context.Service<DockerComposeEngine.DockerComposeProject, DockerComposeEngine.DockerComposeProject>;
readonly layer: Layer.Layer<
DockerComposeEngine.DockerComposeProject,
E1 | DockerComposeError | DockerEngine.DockerError,
DockerComposeEngine.DockerCompose
>;
} = <E1>(project: Stream.Stream<Uint8Array, E1, never>, tagIdentifier: string) => {
const tag = Context.Service<DockerComposeEngine.DockerComposeProject>(tagIdentifier);
const effect = Effect.flatMap(DockerCompose, ({ forProject }) => forProject(project));
const layer = Layer.effect(tag, effect);
return { tag, layer } as const;
};