the-moby-effect
Version:
Moby/Docker API client built using effect-ts
239 lines (221 loc) • 8.91 kB
text/typescript
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 { MobyConnectionOptions } from "../../MobyConnection.ts";
import { makeAgnosticHttpClientLayer } from "../../MobyPlatforms.ts";
import { SwarmSwarm as SwarmData, SwarmInitRequest, SwarmJoinRequest, SwarmSpec } from "../generated/index.ts";
import { DockerError } from "./circular.ts";
import { BadRequest, InternalServerError, NotFound } from "./errors.ts";
/** @since 1.0.0 */
export class NodeNotPartOfSwarm extends Schema.ErrorClass<NodeNotPartOfSwarm>(
"@the-moby-effect/endpoints/NodeNotPartOfSwarm"
)(
{
_tag: Schema.tagDefaultOmit("NodeNotPartOfSwarm"),
message: Schema.optionalKey(Schema.String),
},
{
identifier: "NodeNotPartOfSwarm",
description: "NodeNotPartOfSwarm",
httpApiStatus: 503,
}
) {}
/** @since 1.0.0 */
export class NodeAlreadyPartOfSwarm extends Schema.ErrorClass<NodeAlreadyPartOfSwarm>(
"@the-moby-effect/endpoints/NodeAlreadyPartOfSwarm"
)(
{
_tag: Schema.tagDefaultOmit("NodeAlreadyPartOfSwarm"),
message: Schema.optionalKey(Schema.String),
},
{
identifier: "NodeAlreadyPartOfSwarm",
description: "NodeAlreadyPartOfSwarm",
httpApiStatus: 503,
}
) {}
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Swarm/operation/SwarmInspect */
const inspectSwarmEndpoint = HttpApiEndpoint.get("inspect", "/", {
success: SwarmData, // 200 OK
error: [
NotFound, // 404 No such swarm
NodeNotPartOfSwarm, // 503 Node is not part of a swarm
InternalServerError,
],
});
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Swarm/operation/SwarmInit */
const initSwarmEndpoint = HttpApiEndpoint.post("init", "/init", {
payload: SwarmInitRequest,
success: Schema.String, // 200 OK
error: [
BadRequest, // 400 Bad parameter
NodeAlreadyPartOfSwarm, // 503 Node is already part of a swarm
InternalServerError,
],
});
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Swarm/operation/SwarmJoin */
const joinSwarmEndpoint = HttpApiEndpoint.post("join", "/join", {
payload: SwarmJoinRequest,
success: HttpApiSchema.Empty(200), // 200 OK
error: [
BadRequest, // 400 Bad parameter
NodeAlreadyPartOfSwarm, // 503 Node is already part of a swarm
InternalServerError,
],
});
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Swarm/operation/SwarmLeave */
const leaveSwarmEndpoint = HttpApiEndpoint.post("leave", "/leave", {
query: { force: Schema.optional(Schema.Boolean) },
success: HttpApiSchema.Empty(200), // 200 OK
error: [
NodeNotPartOfSwarm, // 503 Node is not part of a swarm
InternalServerError,
],
});
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Swarm/operation/SwarmUpdate */
const updateSwarmEndpoint = HttpApiEndpoint.post("update", "/update", {
query: {
version: Schema.BigIntFromString,
rotateWorkerToken: Schema.optional(Schema.Boolean),
rotateManagerToken: Schema.optional(Schema.Boolean),
rotateManagerUnlockKey: Schema.optional(Schema.Boolean),
},
payload: SwarmSpec,
success: HttpApiSchema.Empty(200), // 200 OK
error: [
BadRequest, // 400 Bad parameter
NodeNotPartOfSwarm, // 503 Node is not part of a swarm
InternalServerError,
],
});
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Swarm/operation/SwarmUnlockkey */
const unlockkeySwarmEndpoint = HttpApiEndpoint.get("unlockkey", "/unlockkey", {
success: Schema.Struct({ UnlockKey: Schema.String }), // 200 OK
error: [
NodeNotPartOfSwarm, // 503 Node is not part of a swarm
InternalServerError,
],
});
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Swarm/operation/SwarmUnlock */
const unlockSwarmEndpoint = HttpApiEndpoint.post("unlock", "/unlock", {
payload: Schema.Struct({ UnlockKey: Schema.String }),
success: HttpApiSchema.Empty(200), // 200 OK
error: [
NodeNotPartOfSwarm, // 503 Node is not part of a swarm
InternalServerError,
],
});
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Swarm */
const SwarmGroup = HttpApiGroup.make("swarm")
.add(
inspectSwarmEndpoint,
initSwarmEndpoint,
joinSwarmEndpoint,
leaveSwarmEndpoint,
updateSwarmEndpoint,
unlockkeySwarmEndpoint,
unlockSwarmEndpoint
)
.prefix("/swarm");
/**
* Engines can be clustered together in a swarm. Refer to the swarm mode
* documentation for more information.
*
* @since 1.0.0
* @category HttpApi
* @see https://docs.docker.com/reference/api/engine/latest/#tag/Swarm
*/
export const SwarmApi = HttpApi.make("SwarmApi").add(SwarmGroup);
/**
* Engines can be clustered together in a swarm. Refer to the swarm mode
* documentation for more information.
*
* @since 1.0.0
* @category Services
* @see https://docs.docker.com/reference/api/engine/latest/#tag/Swarm
*/
export class Swarm extends Context.Service<Swarm>()("@the-moby-effect/endpoints/Swarm", {
make: Effect.gen(function* () {
const httpClient = yield* HttpClient.HttpClient;
const SwarmsError = DockerError.WrapForModule("swarm");
const client = yield* HttpApiClient.group(SwarmApi, { group: "swarm", httpClient });
const inspect_ = () => client.inspect().pipe(Effect.mapError(SwarmsError("inspect")));
const init_ = (payload: (typeof SwarmInitRequest)["~type.make.in"]) =>
SwarmInitRequest.makeEffect(payload).pipe(
Effect.flatMap((payload) => client.init({ payload })),
Effect.mapError(SwarmsError("init"))
);
const join_ = (payload: (typeof SwarmJoinRequest)["~type.make.in"]) =>
SwarmJoinRequest.makeEffect(payload).pipe(
Effect.flatMap((payload) => client.join({ payload })),
Effect.mapError(SwarmsError("join"))
);
const leave_ = (options?: { force?: boolean | undefined } | undefined) =>
client.leave({ query: { ...options } }).pipe(Effect.mapError(SwarmsError("leave")));
const update_ = (
spec: (typeof SwarmSpec)["~type.make.in"],
version: bigint,
rotate?:
| {
readonly rotateWorkerToken?: boolean | undefined;
readonly rotateManagerToken?: boolean | undefined;
readonly rotateManagerUnlockKey?: boolean | undefined;
}
| undefined
) =>
SwarmSpec.makeEffect(spec).pipe(
Effect.flatMap((payload) =>
client.update({
payload,
query: { version, ...rotate },
})
),
Effect.mapError(SwarmsError("update"))
);
const unlockkey_ = () => client.unlockkey().pipe(Effect.mapError(SwarmsError("unlockkey")));
const unlock_ = (unlockKey: string) =>
client.unlock({ payload: { UnlockKey: unlockKey } }).pipe(Effect.mapError(SwarmsError("unlock")));
return {
inspect: inspect_,
init: init_,
join: join_,
leave: leave_,
update: update_,
unlockkey: unlockkey_,
unlock: unlock_,
};
}),
}) {}
/**
* Engines can be clustered together in a swarm. Refer to the swarm mode
* documentation for more information.
*
* @since 1.0.0
* @category Layers
* @see https://docs.docker.com/reference/api/engine/latest/#tag/Swarm
*/
export const SwarmLayer: Layer.Layer<Swarm, never, HttpClient.HttpClient> = Layer.effect(Swarm, Swarm.make);
/**
* Engines can be clustered together in a swarm. Refer to the swarm mode
* documentation for more information.
*
* @since 1.0.0
* @category Layers
* @see https://docs.docker.com/reference/api/engine/latest/#tag/Swarm
*/
export const SwarmLayerLocalSocket: Layer.Layer<Swarm, never, HttpClient.HttpClient> = SwarmLayer.pipe(
Layer.provide(
makeAgnosticHttpClientLayer(
MobyConnectionOptions.socket({
socketPath: "/var/run/docker.sock",
})
)
)
);