the-moby-effect
Version:
Moby/Docker API client built using effect-ts
322 lines (321 loc) • 13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.version = exports.stop = exports.start = exports.search = exports.runScoped = exports.run = exports.push = exports.pullScoped = exports.pull = exports.ps = exports.pingHead = exports.ping = exports.info = exports.images = exports.execWebsocketsNonBlocking = exports.execWebsockets = exports.execNonBlocking = exports.exec = exports.buildScoped = exports.build = void 0;
var Array = _interopRequireWildcard(require("effect/Array"));
var Channel = _interopRequireWildcard(require("effect/Channel"));
var Effect = _interopRequireWildcard(require("effect/Effect"));
var Function = _interopRequireWildcard(require("effect/Function"));
var Global = _interopRequireWildcard(require("effect/GlobalValue"));
var Match = _interopRequireWildcard(require("effect/Match"));
var MutableHashMap = _interopRequireWildcard(require("effect/MutableHashMap"));
var Option = _interopRequireWildcard(require("effect/Option"));
var Predicate = _interopRequireWildcard(require("effect/Predicate"));
var Schedule = _interopRequireWildcard(require("effect/Schedule"));
var Schema = _interopRequireWildcard(require("effect/Schema"));
var Sink = _interopRequireWildcard(require("effect/Sink"));
var Stream = _interopRequireWildcard(require("effect/Stream"));
var Tuple = _interopRequireWildcard(require("effect/Tuple"));
var MobyDemux = _interopRequireWildcard(require("../../MobyDemux.js"));
var MobyEndpoints = _interopRequireWildcard(require("../../MobyEndpoints.js"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/** @internal */
const pull = ({
auth,
image,
platform
}) => Stream.unwrap(MobyEndpoints.Images.use(images => images.create({
fromImage: image,
"X-Registry-Auth": auth,
platform
})));
/** @internal */
exports.pull = pull;
const pullScoped = ({
auth,
image,
platform
}) => Effect.Do.pipe(Effect.bind("images", () => MobyEndpoints.Images), Effect.let("stream", () => pull({
image,
auth,
platform
})), Effect.let("acquire", ({
images,
stream
}) => Stream.provideService(stream, MobyEndpoints.Images, images)), Effect.let("release", ({
images
}) => images.delete({
name: image
})), Effect.flatMap(({
acquire,
release
}) => Effect.acquireRelease(Effect.sync(() => acquire), () => Effect.orDie(release))));
/** @internal */
exports.pullScoped = pullScoped;
const build = ({
auth,
buildArgs,
context,
dockerfile,
platform,
tag
}) => Stream.unwrap(MobyEndpoints.Images.use(images => images.build({
context,
buildArgs,
dockerfile,
platform,
t: tag,
"X-Registry-Config": auth
})));
/** @internal */
exports.build = build;
const buildScoped = ({
auth,
buildArgs,
context,
dockerfile,
platform,
tag
}) => Effect.Do.pipe(Effect.bind("images", () => MobyEndpoints.Images), Effect.let("stream", () => build({
tag,
buildArgs,
auth,
context,
platform,
dockerfile
})), Effect.let("acquire", ({
images,
stream
}) => Stream.provideService(stream, MobyEndpoints.Images, images)), Effect.let("release", ({
images
}) => images.delete({
name: tag
})), Effect.flatMap(({
acquire,
release
}) => Effect.acquireRelease(Effect.sync(() => acquire), () => Effect.orDie(release))));
/** @internal */
exports.buildScoped = buildScoped;
const start = containerId => MobyEndpoints.Containers.use(containers => containers.start(containerId));
/** @internal */
exports.start = start;
const stop = containerId => MobyEndpoints.Containers.use(containers => containers.stop(containerId));
/** @internal */
exports.stop = stop;
const run = containerOptions => Effect.gen(function* () {
const containers = yield* MobyEndpoints.Containers;
const containerCreateResponse = yield* containers.create(containerOptions);
yield* containers.start(containerCreateResponse.Id);
// Helper to wait until a container is dead or running
const waitUntilContainerDeadOrRunning = Function.pipe(containers.inspect(containerCreateResponse.Id),
// Effect.tap(({ State }) => Effect.log(`Waiting for container to be running, state=${State?.Status}`)),
Effect.flatMap(({
State
}) => Function.pipe(Match.value(State?.Status), Match.when("running", _s => Effect.void), Match.when("created", _s => Effect.fail("Waiting")),
// Match.when(Schemas.ContainerState_Status.RUNNING, (_s) => Effect.void),
// Match.when(Schemas.ContainerState_Status.CREATED, (_s) => Effect.fail("Waiting")),
Match.orElse(_s => Effect.fail("Container is dead or killed"))).pipe(Effect.mapError(s => new MobyEndpoints.ContainersError({
method: "inspect",
cause: new Error(s)
}))))).pipe(Effect.retry(Schedule.spaced(500).pipe(Schedule.whileInput(({
message
}) => message === "Waiting"))));
// Helper for if the container has a healthcheck, wait for it to report healthy
const waitUntilContainerHealthy = Function.pipe(containers.inspect(containerCreateResponse.Id),
// Effect.tap(({ State }) =>
// Effect.log(`Waiting for container to be healthy, health=${State?.Health?.Status}`)
// ),
Effect.flatMap(({
State
}) => Function.pipe(Match.value(State?.Health?.Status), Match.when(undefined, _s => Effect.void), Match.when("healthy", _s => Effect.void), Match.when("starting", _s => Effect.fail("Waiting")),
// Match.when(Schemas.Health_Status.HEALTHY, (_s) => Effect.void),
// Match.when(Schemas.Health_Status.STARTING, (_s) => Effect.fail("Waiting")),
Match.orElse(_s => Effect.fail("Container is unhealthy"))).pipe(Effect.mapError(s => new MobyEndpoints.ContainersError({
method: "inspect",
cause: new Error(s)
}))))).pipe(Effect.retry(Schedule.spaced(500).pipe(Schedule.whileInput(({
message
}) => message === "Waiting"))));
yield* waitUntilContainerDeadOrRunning;
yield* waitUntilContainerHealthy;
return yield* containers.inspect(containerCreateResponse.Id);
});
/** @internal */
exports.run = run;
const runScoped = containerOptions => {
const acquire = run(containerOptions);
const release = containerData => Effect.orDie(Effect.gen(function* () {
const containers = yield* MobyEndpoints.Containers;
// FIXME: this cleanup should be better
yield* Effect.catchTag(containers.stop(containerData.Id), "ContainersError", () => Effect.void);
yield* containers.delete(containerData.Id, {
force: true
});
}));
return Effect.acquireRelease(acquire, release);
};
/** @internal */
exports.runScoped = runScoped;
const execNonBlocking = ({
command,
containerId,
detach
}) => Effect.gen(function* () {
const execs = yield* MobyEndpoints.Execs;
const execId = yield* execs.container(containerId, {
AttachStdin: true,
AttachStderr: true,
AttachStdout: true,
Cmd: Predicate.isString(command) ? command.split(" ") : command
});
const socket = yield* execs.start(execId.Id, {
Detach: detach
});
return Tuple.make(socket, execId.Id);
});
/** @internal */
exports.execNonBlocking = execNonBlocking;
const exec = ({
command,
containerId
}) => Effect.gen(function* () {
const [socket, execId] = yield* execNonBlocking({
command,
containerId,
detach: false
});
const output = yield* MobyDemux.demuxToSingleSink(socket, Stream.never, Sink.mkString);
const execInspectResponse = yield* MobyEndpoints.Execs.use(execs => execs.inspect(execId));
if (execInspectResponse.Running === true) {
return yield* new MobyEndpoints.ExecsError({
method: "exec",
cause: new Error("Exec is still running")
});
} else {
return Tuple.make(execInspectResponse.ExitCode, output);
}
});
/** @internal */
exports.exec = exec;
const execWebsocketsRegistry = /*#__PURE__*/Global.globalValue("the-moby-effect/engines/docker/execWebsocketsRegistry", () => MutableHashMap.empty());
/** @internal */
const execWebsocketsNonBlocking = ({
command,
containerId
}) => Effect.gen(function* () {
const containers = yield* MobyEndpoints.Containers;
const mutex = MutableHashMap.get(execWebsocketsRegistry, containerId).pipe(Option.getOrElse(() => {
const semaphore = Effect.unsafeMakeSemaphore(1);
MutableHashMap.set(execWebsocketsRegistry, containerId, semaphore);
return semaphore;
}));
const acquire = Effect.gen(function* () {
const inspect = yield* containers.inspect(containerId);
const command = Array.join(inspect.Config?.Cmd ?? [], " ");
yield* Effect.mapError(Schema.decodeUnknown(Schema.Literal("/bin/sh",
// Basic shell available in most containers
"/bin/bash",
// Bash shell (common in many Linux distributions)
"/usr/bin/bash",
// Alternative location for bash
"/bin/dash",
// Debian Almquist shell (lightweight shell)
"/bin/ash",
// Lightweight shell used in Alpine Linux
"/bin/zsh",
// Z shell
"/usr/bin/zsh",
// Alternative location for zsh
"/bin/ksh",
// Korn shell
"/bin/tcsh",
// TENEX C shell
"/bin/csh",
// C shell
"/usr/bin/fish",
// Friendly interactive shell
"/usr/local/bin/bash",
// Alternative location for bash
"/usr/local/bin/sh",
// Alternative location for sh
"/busybox/sh" // Busybox shell
))(command), cause => new MobyEndpoints.ContainersError({
method: "exec",
cause
}));
yield* mutex.take(1);
});
// TODO: should this be un-interuptible?
const release = Effect.fnUntraced(function* () {
yield* mutex.release(1);
yield* containers.wait(containerId, {
condition: "not-running"
});
yield* containers.start(containerId);
}, Effect.orDie);
const use = Effect.gen(function* () {
const cmd = Predicate.isString(command) ? command : Array.join(command, " ");
const input = Stream.succeed(`${cmd}; exit\n`);
const stdinSocket = yield* containers.attachWebsocket(containerId, {
stdin: true,
stream: true
});
const stdoutSocket = yield* containers.attachWebsocket(containerId, {
stdout: true,
stream: true
});
const stderrSocket = yield* containers.attachWebsocket(containerId, {
stderr: true,
stream: true
});
const sockets = {
stdin: stdinSocket,
stdout: stdoutSocket,
stderr: stderrSocket
};
const multiplexedSocket = yield* MobyDemux.pack(sockets, {
requestedCapacity: 16
});
const producer = Channel.fromEffect(MobyDemux.demuxRawToSingleSink(stdinSocket, input, Sink.drain));
const consumer = multiplexedSocket.underlying;
const zipped = Channel.zipLeft(producer, consumer, {
concurrent: true
});
return zipped;
});
const multiplexedChannel = Effect.acquireRelease(acquire, release).pipe(Effect.map(() => Channel.unwrap(use))).pipe(Channel.unwrapScoped).pipe(Channel.provideService(MobyEndpoints.Containers, containers));
return MobyDemux.makeMultiplexedChannel(multiplexedChannel);
});
/** @internal */
exports.execWebsocketsNonBlocking = execWebsocketsNonBlocking;
const execWebsockets = ({
command,
containerId
}) => Function.pipe(execWebsocketsNonBlocking({
command,
containerId
}), Effect.flatMap(MobyDemux.demuxMultiplexedToSeparateSinks(Stream.empty, Sink.mkString, Sink.mkString)));
/** @internal */
exports.execWebsockets = execWebsockets;
const ps = options => MobyEndpoints.Containers.use(containers => containers.list(options));
/** @internal */
exports.ps = ps;
const push = options => Stream.unwrap(MobyEndpoints.Images.use(images => images.push(options)));
/** @internal */
exports.push = push;
const images = options => MobyEndpoints.Images.use(images => images.list(options));
/** @internal */
exports.images = images;
const search = options => MobyEndpoints.Images.use(images => images.search(options));
/** @internal */
exports.search = search;
const version = exports.version = /*#__PURE__*/Function.constant(/*#__PURE__*/MobyEndpoints.Systems.use(systems => systems.version()));
/** @internal */
const info = exports.info = /*#__PURE__*/Function.constant(/*#__PURE__*/MobyEndpoints.Systems.use(systems => systems.info()));
/** @internal */
const ping = exports.ping = /*#__PURE__*/Function.constant(/*#__PURE__*/MobyEndpoints.Systems.use(systems => systems.ping()));
/** @internal */
const pingHead = exports.pingHead = /*#__PURE__*/Function.constant(/*#__PURE__*/MobyEndpoints.Systems.use(systems => systems.ping()));
//# sourceMappingURL=docker.js.map