the-moby-effect 1.51.0 → 1.51.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Callbacks.d.ts +3 -3
- package/dist/DindEngine.d.ts +3 -3
- package/dist/DockerComposeEngine.d.ts +4 -4
- package/dist/DockerEngine.d.ts +12 -5
- package/dist/DockerEngine.d.ts.map +1 -1
- package/dist/DockerEngine.js +7 -0
- package/dist/DockerEngine.js.map +1 -1
- package/dist/MobyConvey.d.ts +1 -1
- package/dist/MobyDemux.d.ts +21 -1
- package/dist/MobyDemux.d.ts.map +1 -1
- package/dist/MobyDemux.js +20 -0
- package/dist/MobyDemux.js.map +1 -1
- package/dist/MobyEndpoints.d.ts +15 -15
- package/dist/MobyPlatforms.d.ts +1 -1
- package/dist/MobySchemas.d.ts +1 -1
- package/dist/PodmanEngine.d.ts +2 -2
- package/dist/Promises.d.ts +3 -3
- package/dist/internal/demux/multiplexed.js +4 -0
- package/dist/internal/demux/multiplexed.js.map +1 -1
- package/dist/internal/demux/raw.js +4 -0
- package/dist/internal/demux/raw.js.map +1 -1
- package/dist/internal/endpoints/auth.d.ts +56 -0
- package/dist/internal/endpoints/auth.d.ts.map +1 -0
- package/dist/internal/endpoints/auth.js +82 -0
- package/dist/internal/endpoints/auth.js.map +1 -0
- package/dist/internal/endpoints/containers.d.ts.map +1 -1
- package/dist/internal/endpoints/containers.js +3 -3
- package/dist/internal/endpoints/containers.js.map +1 -1
- package/dist/internal/endpoints/execs.d.ts.map +1 -1
- package/dist/internal/endpoints/execs.js +1 -1
- package/dist/internal/endpoints/execs.js.map +1 -1
- package/dist/internal/endpoints/images.d.ts +8 -8
- package/dist/internal/endpoints/images.d.ts.map +1 -1
- package/dist/internal/endpoints/images.js +21 -17
- package/dist/internal/endpoints/images.js.map +1 -1
- package/dist/internal/endpoints/networks.d.ts +26 -4
- package/dist/internal/endpoints/networks.d.ts.map +1 -1
- package/dist/internal/endpoints/networks.js +5 -5
- package/dist/internal/endpoints/networks.js.map +1 -1
- package/dist/internal/endpoints/plugins.d.ts +3 -3
- package/dist/internal/endpoints/plugins.d.ts.map +1 -1
- package/dist/internal/endpoints/plugins.js +12 -11
- package/dist/internal/endpoints/plugins.js.map +1 -1
- package/dist/internal/endpoints/services.d.ts +25 -3
- package/dist/internal/endpoints/services.d.ts.map +1 -1
- package/dist/internal/endpoints/services.js +8 -11
- package/dist/internal/endpoints/services.js.map +1 -1
- package/dist/internal/endpoints/system.d.ts +1 -1
- package/dist/internal/endpoints/system.d.ts.map +1 -1
- package/dist/internal/endpoints/system.js.map +1 -1
- package/dist/internal/engines/dind.js +9 -2
- package/dist/internal/engines/dind.js.map +1 -1
- package/dist/internal/generated/ContainerConfig.generated.d.ts +3 -3
- package/dist/internal/generated/ContainerConfig.generated.js +1 -1
- package/dist/internal/generated/ContainerConfig.generated.js.map +1 -1
- package/dist/internal/generated/ContainerContainerJSONBase.generated.d.ts +3 -3
- package/dist/internal/generated/ContainerContainerJSONBase.generated.js +2 -1
- package/dist/internal/generated/ContainerContainerJSONBase.generated.js.map +1 -1
- package/dist/internal/generated/ContainerCreateRequest.generated.d.ts +3 -3
- package/dist/internal/generated/ContainerDefaultNetworkSettings.generated.d.ts +24 -24
- package/dist/internal/generated/ContainerDefaultNetworkSettings.generated.js +15 -8
- package/dist/internal/generated/ContainerDefaultNetworkSettings.generated.js.map +1 -1
- package/dist/internal/generated/ContainerInspectResponse.generated.d.ts +3 -3
- package/dist/internal/generated/ContainerNetworkSettings.generated.d.ts +45 -45
- package/dist/internal/generated/ContainerNetworkSettingsBase.generated.d.ts +21 -21
- package/dist/internal/generated/ContainerNetworkSettingsBase.generated.js +11 -6
- package/dist/internal/generated/ContainerNetworkSettingsBase.generated.js.map +1 -1
- package/dist/internal/generated/ImageInspectResponse.generated.d.ts +14 -14
- package/dist/internal/generated/ImageInspectResponse.generated.js +8 -4
- package/dist/internal/generated/ImageInspectResponse.generated.js.map +1 -1
- package/dist/internal/generated/NetworkCreateOptions.generated.d.ts +66 -34
- package/dist/internal/generated/NetworkCreateOptions.generated.d.ts.map +1 -1
- package/dist/internal/generated/NetworkCreateOptions.generated.js +28 -12
- package/dist/internal/generated/NetworkCreateOptions.generated.js.map +1 -1
- package/dist/internal/generated/NetworkCreateRequest.generated.d.ts +66 -34
- package/dist/internal/generated/NetworkCreateRequest.generated.d.ts.map +1 -1
- package/dist/internal/generated/NetworkInspect.generated.d.ts +11 -11
- package/dist/internal/generated/NetworkInspect.generated.js +5 -3
- package/dist/internal/generated/NetworkInspect.generated.js.map +1 -1
- package/dist/internal/generated/TypesDiskUsage.generated.d.ts +3 -3
- package/dist/internal/generated/TypesDiskUsage.generated.js +2 -1
- package/dist/internal/generated/TypesDiskUsage.generated.js.map +1 -1
- package/dist/internal/platforms/undici.js +88 -59
- package/dist/internal/platforms/undici.js.map +1 -1
- package/dist/internal/schemas/id.js +1 -1
- package/dist/internal/schemas/id.js.map +1 -1
- package/package.json +1 -1
- package/src/Callbacks.ts +3 -3
- package/src/DindEngine.ts +4 -4
- package/src/DockerComposeEngine.ts +4 -4
- package/src/DockerEngine.ts +16 -7
- package/src/MobyConnection.ts +1 -1
- package/src/MobyConvey.ts +2 -2
- package/src/MobyDemux.ts +31 -7
- package/src/MobyEndpoints.ts +15 -15
- package/src/MobyPlatforms.ts +8 -8
- package/src/MobySchemas.ts +1 -1
- package/src/PodmanEngine.ts +3 -3
- package/src/Promises.ts +3 -3
- package/src/internal/demux/multiplexed.ts +9 -0
- package/src/internal/demux/raw.ts +6 -0
- package/src/internal/endpoints/auth.ts +120 -0
- package/src/internal/endpoints/containers.ts +7 -4
- package/src/internal/endpoints/execs.ts +7 -2
- package/src/internal/endpoints/images.ts +59 -24
- package/src/internal/endpoints/networks.ts +11 -5
- package/src/internal/endpoints/plugins.ts +17 -7
- package/src/internal/endpoints/services.ts +21 -6
- package/src/internal/endpoints/system.ts +1 -1
- package/src/internal/engines/dind.ts +8 -2
- package/src/internal/generated/ContainerConfig.generated.ts +1 -1
- package/src/internal/generated/ContainerContainerJSONBase.generated.ts +1 -1
- package/src/internal/generated/ContainerDefaultNetworkSettings.generated.ts +8 -8
- package/src/internal/generated/ContainerNetworkSettingsBase.generated.ts +6 -6
- package/src/internal/generated/ImageInspectResponse.generated.ts +4 -4
- package/src/internal/generated/NetworkCreateOptions.generated.ts +10 -10
- package/src/internal/generated/NetworkInspect.generated.ts +3 -3
- package/src/internal/generated/TypesDiskUsage.generated.ts +1 -1
- package/src/internal/platforms/undici.ts +121 -90
- package/src/internal/schemas/id.ts +1 -1
package/src/MobyPlatforms.ts
CHANGED
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
import type * as HttpClient from "@effect/platform/HttpClient";
|
|
8
8
|
import type * as Socket from "@effect/platform/Socket";
|
|
9
|
-
import type * as MobyConnection from "./MobyConnection.
|
|
9
|
+
import type * as MobyConnection from "./MobyConnection.ts";
|
|
10
10
|
|
|
11
11
|
import type * as Layer from "effect/Layer";
|
|
12
|
-
import * as internalAgnostic from "./internal/platforms/agnostic.
|
|
13
|
-
import * as internalBun from "./internal/platforms/bun.
|
|
14
|
-
import * as internalDeno from "./internal/platforms/deno.
|
|
15
|
-
import * as internalFetch from "./internal/platforms/fetch.
|
|
16
|
-
import * as internalNode from "./internal/platforms/node.
|
|
17
|
-
import * as internalUndici from "./internal/platforms/undici.
|
|
18
|
-
import * as internalWeb from "./internal/platforms/web.
|
|
12
|
+
import * as internalAgnostic from "./internal/platforms/agnostic.ts";
|
|
13
|
+
import * as internalBun from "./internal/platforms/bun.ts";
|
|
14
|
+
import * as internalDeno from "./internal/platforms/deno.ts";
|
|
15
|
+
import * as internalFetch from "./internal/platforms/fetch.ts";
|
|
16
|
+
import * as internalNode from "./internal/platforms/node.ts";
|
|
17
|
+
import * as internalUndici from "./internal/platforms/undici.ts";
|
|
18
|
+
import * as internalWeb from "./internal/platforms/web.ts";
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Given the moby connection options, it will construct a layer that provides a
|
package/src/MobySchemas.ts
CHANGED
package/src/PodmanEngine.ts
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
import type * as HttpClient from "@effect/platform/HttpClient";
|
|
8
8
|
import type * as Socket from "@effect/platform/Socket";
|
|
9
|
-
import type * as MobyConnection from "./MobyConnection.
|
|
9
|
+
import type * as MobyConnection from "./MobyConnection.ts";
|
|
10
10
|
|
|
11
11
|
import * as Function from "effect/Function";
|
|
12
12
|
import * as Layer from "effect/Layer";
|
|
13
|
-
import * as MobyEndpoints from "./MobyEndpoints.
|
|
14
|
-
import * as MobyPlatforms from "./MobyPlatforms.
|
|
13
|
+
import * as MobyEndpoints from "./MobyEndpoints.ts";
|
|
14
|
+
import * as MobyPlatforms from "./MobyPlatforms.ts";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @since 1.0.0
|
package/src/Promises.ts
CHANGED
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type * as Layer from "effect/Layer";
|
|
8
|
-
import type * as MobySchemas from "./MobySchemas.
|
|
8
|
+
import type * as MobySchemas from "./MobySchemas.ts";
|
|
9
9
|
|
|
10
10
|
import * as Chunk from "effect/Chunk";
|
|
11
11
|
import * as Effect from "effect/Effect";
|
|
12
12
|
import * as Function from "effect/Function";
|
|
13
13
|
import * as ManagedRuntime from "effect/ManagedRuntime";
|
|
14
14
|
import * as Stream from "effect/Stream";
|
|
15
|
-
import * as DockerEngine from "./DockerEngine.
|
|
16
|
-
import * as MobyConvey from "./MobyConvey.
|
|
15
|
+
import * as DockerEngine from "./DockerEngine.ts";
|
|
16
|
+
import * as MobyConvey from "./MobyConvey.ts";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Create a promise client for the docker engine
|
|
@@ -70,6 +70,15 @@ export const isMultiplexedChannel = <IE = unknown, OE = Socket.SocketError, R =
|
|
|
70
70
|
u: unknown
|
|
71
71
|
): u is MobyDemux.MultiplexedChannel<IE, IE | OE, R> => Predicate.hasProperty(u, MultiplexedChannelTypeId);
|
|
72
72
|
|
|
73
|
+
/** @internal */
|
|
74
|
+
export const never: MobyDemux.MultiplexedChannel<never, never, never> = makeMultiplexedChannel<never, never, never>(
|
|
75
|
+
Channel.never
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
/** @internal */
|
|
79
|
+
export const neverWith = <IE>(): MobyDemux.MultiplexedChannel<IE, IE, never> =>
|
|
80
|
+
makeMultiplexedChannel<IE, never, never>(Channel.never);
|
|
81
|
+
|
|
73
82
|
/** @internal */
|
|
74
83
|
export const responseIsMultiplexedResponse = (response: HttpClientResponse.HttpClientResponse): boolean =>
|
|
75
84
|
response.headers["content-type"] === MultiplexedContentType;
|
|
@@ -65,6 +65,12 @@ export const isRawChannel = <IE = unknown, OE = Socket.SocketError, R = never>(
|
|
|
65
65
|
u: unknown
|
|
66
66
|
): u is MobyDemux.RawChannel<IE, IE | OE, R> => Predicate.hasProperty(u, RawChannelTypeId);
|
|
67
67
|
|
|
68
|
+
/** @internal */
|
|
69
|
+
export const never: MobyDemux.RawChannel<never, never, never> = makeRawChannel<never, never, never>(Channel.never);
|
|
70
|
+
|
|
71
|
+
/** @internal */
|
|
72
|
+
export const neverWith = <IE>(): MobyDemux.RawChannel<IE, IE, never> => makeRawChannel<IE, never, never>(Channel.never);
|
|
73
|
+
|
|
68
74
|
/** @internal */
|
|
69
75
|
export const responseIsRawResponse = (response: HttpClientResponse.HttpClientResponse): boolean =>
|
|
70
76
|
response.headers["content-type"] === RawContentType;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { HttpClient, HttpClientRequest, type HttpApiEndpoint } from "@effect/platform";
|
|
2
|
+
import { Effect, Layer, Option, pipe, Predicate, Redacted, type Array } from "effect";
|
|
3
|
+
|
|
4
|
+
import { DockerError } from "./circular.ts";
|
|
5
|
+
import { System } from "./system.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @since 1.0.0
|
|
9
|
+
* @category Auth
|
|
10
|
+
* @see https://docs.docker.com/reference/api/engine/version/v1.51/#section/Authentication
|
|
11
|
+
*/
|
|
12
|
+
export class RegistryAuth extends Effect.Service<RegistryAuth>()(
|
|
13
|
+
"the-moby-effect/endpoints/httpApiHacks/RegistryAuth",
|
|
14
|
+
{
|
|
15
|
+
accessors: false,
|
|
16
|
+
dependencies: [],
|
|
17
|
+
effect: (authHeader: Redacted.Redacted<string>) =>
|
|
18
|
+
Effect.gen(function* () {
|
|
19
|
+
return { authHeader } as const;
|
|
20
|
+
}),
|
|
21
|
+
}
|
|
22
|
+
) {
|
|
23
|
+
/**
|
|
24
|
+
* @since 1.0.0
|
|
25
|
+
* @category Auth
|
|
26
|
+
* @see https://docs.docker.com/reference/api/engine/version/v1.51/#section/Authentication
|
|
27
|
+
*/
|
|
28
|
+
static readonly Live = RegistryAuth.Default;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @since 1.0.0
|
|
32
|
+
* @category Auth
|
|
33
|
+
* @see https://docs.docker.com/reference/api/engine/version/v1.51/#section/Authentication
|
|
34
|
+
*/
|
|
35
|
+
static readonly Credentials = (credentials: {
|
|
36
|
+
serverAddress?: string | undefined;
|
|
37
|
+
username: Redacted.Redacted<string>;
|
|
38
|
+
password: Redacted.Redacted<string>;
|
|
39
|
+
email?: Redacted.Redacted<string> | undefined;
|
|
40
|
+
}) =>
|
|
41
|
+
Layer.sync(RegistryAuth, () => {
|
|
42
|
+
const encoded = Buffer.from(
|
|
43
|
+
JSON.stringify({
|
|
44
|
+
serveraddress: credentials.serverAddress,
|
|
45
|
+
username: Redacted.value(credentials.username),
|
|
46
|
+
password: Redacted.value(credentials.password),
|
|
47
|
+
email: credentials.email ? Redacted.value(credentials.email) : undefined,
|
|
48
|
+
})
|
|
49
|
+
).toString("base64");
|
|
50
|
+
|
|
51
|
+
const redacted = Redacted.make(encoded);
|
|
52
|
+
return RegistryAuth.make({ authHeader: redacted });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @since 1.0.0
|
|
57
|
+
* @category Auth
|
|
58
|
+
* @see https://docs.docker.com/reference/api/engine/version/v1.51/#section/Authentication
|
|
59
|
+
*/
|
|
60
|
+
static readonly Token = (credentials: {
|
|
61
|
+
serverAddress?: string | undefined;
|
|
62
|
+
username: Redacted.Redacted<string>;
|
|
63
|
+
password: Redacted.Redacted<string>;
|
|
64
|
+
email?: Redacted.Redacted<string> | undefined;
|
|
65
|
+
}) =>
|
|
66
|
+
pipe(
|
|
67
|
+
System.use((systems) =>
|
|
68
|
+
systems.auth({
|
|
69
|
+
serveraddress: credentials.serverAddress,
|
|
70
|
+
username: Redacted.value(credentials.username),
|
|
71
|
+
password: Redacted.value(credentials.password),
|
|
72
|
+
email: credentials.email ? Redacted.value(credentials.email) : undefined,
|
|
73
|
+
})
|
|
74
|
+
),
|
|
75
|
+
Effect.flatMap((response) => {
|
|
76
|
+
const WrapError = DockerError.WrapForModule("system")("auth");
|
|
77
|
+
|
|
78
|
+
if (Predicate.isObject(response) && "IdentityToken" in response) {
|
|
79
|
+
return Effect.succeed(response.IdentityToken);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return WrapError(new Error("Registry authentication returned no response"));
|
|
83
|
+
}),
|
|
84
|
+
Effect.map((token) =>
|
|
85
|
+
RegistryAuth.make({
|
|
86
|
+
authHeader: Redacted.make(token),
|
|
87
|
+
})
|
|
88
|
+
),
|
|
89
|
+
Layer.effect(RegistryAuth)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @since 1.0.0
|
|
95
|
+
* @category Auth
|
|
96
|
+
* @see https://docs.docker.com/reference/api/engine/version/v1.51/#section/Authentication
|
|
97
|
+
*/
|
|
98
|
+
export const WithRegistryAuthHeader = (
|
|
99
|
+
...sendHeaderWithEndpoints: Array.NonEmptyReadonlyArray<HttpApiEndpoint.HttpApiEndpoint.Any & { path: string }>
|
|
100
|
+
) =>
|
|
101
|
+
HttpClient.mapRequestEffect(
|
|
102
|
+
Effect.fn("HttpApiHacks.withRegistryAUthHeader")(function* (request: HttpClientRequest.HttpClientRequest) {
|
|
103
|
+
const url = HttpClientRequest.toUrl(request);
|
|
104
|
+
if (Option.isNone(url)) {
|
|
105
|
+
return request;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!sendHeaderWithEndpoints.some((endpoint) => url.value.pathname.startsWith(endpoint.path))) {
|
|
109
|
+
return request;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const auth = yield* Effect.serviceOption(RegistryAuth);
|
|
113
|
+
if (Option.isNone(auth)) {
|
|
114
|
+
return request;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const header = Redacted.value(auth.value.authHeader);
|
|
118
|
+
return HttpClientRequest.setHeader(request, "X-Registry-Auth", header);
|
|
119
|
+
})
|
|
120
|
+
);
|
|
@@ -512,7 +512,7 @@ export class Containers extends Effect.Service<Containers>()("@the-moby-effect/e
|
|
|
512
512
|
)({
|
|
513
513
|
path: { identifier },
|
|
514
514
|
urlParams: { ...options },
|
|
515
|
-
headers: { Connection: "Upgrade", Upgrade: "tcp" },
|
|
515
|
+
headers: { Connection: "Upgrade", Upgrade: "tcp" }, // FIXME: Broken on undici
|
|
516
516
|
}),
|
|
517
517
|
ContainersError("attach")
|
|
518
518
|
);
|
|
@@ -522,7 +522,10 @@ export class Containers extends Effect.Service<Containers>()("@the-moby-effect/e
|
|
|
522
522
|
ContainersApi,
|
|
523
523
|
"containers",
|
|
524
524
|
"attachWebsocket"
|
|
525
|
-
)({
|
|
525
|
+
)({
|
|
526
|
+
path: { identifier },
|
|
527
|
+
urlParams: { ...options },
|
|
528
|
+
}).pipe(Effect.provide(context)),
|
|
526
529
|
ContainersError("attachWebsocket")
|
|
527
530
|
);
|
|
528
531
|
const wait_ = (identifier: ContainerIdentifier, options?: Options<"wait">) =>
|
|
@@ -550,7 +553,7 @@ export class Containers extends Effect.Service<Containers>()("@the-moby-effect/e
|
|
|
550
553
|
Effect.flatMap(
|
|
551
554
|
HttpClientResponse.schemaHeaders(
|
|
552
555
|
Schema.Struct({
|
|
553
|
-
"
|
|
556
|
+
"x-docker-container-path-stat": Schema.compose(
|
|
554
557
|
Schema.StringFromBase64,
|
|
555
558
|
Schema.parseJson()
|
|
556
559
|
).pipe(Schema.optional),
|
|
@@ -558,7 +561,7 @@ export class Containers extends Effect.Service<Containers>()("@the-moby-effect/e
|
|
|
558
561
|
)
|
|
559
562
|
)
|
|
560
563
|
)
|
|
561
|
-
.pipe(Effect.map(({ "
|
|
564
|
+
.pipe(Effect.map(({ "x-docker-container-path-stat": pathStat }) => pathStat))
|
|
562
565
|
.pipe(Effect.mapError(ContainersError("archiveInfo")));
|
|
563
566
|
const putArchive_ = <E, R>(
|
|
564
567
|
identifier: ContainerIdentifier,
|
|
@@ -24,7 +24,12 @@ const createExecEndpoint = HttpApiEndpoint.post("container", "/containers/:id/ex
|
|
|
24
24
|
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Exec/operation/ExecStart */
|
|
25
25
|
const startExecEndpoint = HttpApiEndpoint.post("start", "/exec/:id/start")
|
|
26
26
|
.setPath(Schema.Struct({ id: ExecIdentifier }))
|
|
27
|
-
.setHeaders(
|
|
27
|
+
.setHeaders(
|
|
28
|
+
Schema.Struct({
|
|
29
|
+
Upgrade: Schema.Literal("tcp"),
|
|
30
|
+
Connection: Schema.Literal("Upgrade"),
|
|
31
|
+
})
|
|
32
|
+
)
|
|
28
33
|
.setPayload(ContainerExecStartOptions)
|
|
29
34
|
.addSuccess(HttpApiSchema.Empty(101))
|
|
30
35
|
.addSuccess(HttpApiSchema.Empty(200))
|
|
@@ -102,7 +107,7 @@ export class Execs extends Effect.Service<Execs>()("@the-moby-effect/endpoints/E
|
|
|
102
107
|
)({
|
|
103
108
|
path: { id },
|
|
104
109
|
payload: ContainerExecStartOptions.make(payload),
|
|
105
|
-
headers: { Connection: "Upgrade", Upgrade: "tcp" },
|
|
110
|
+
headers: { Connection: "Upgrade", Upgrade: "tcp" }, // FIXME: Broken on undici
|
|
106
111
|
})
|
|
107
112
|
.pipe(
|
|
108
113
|
Effect.map(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, HttpClient } from "@effect/platform";
|
|
2
2
|
import { Effect, Schema, Stream, type Layer } from "effect";
|
|
3
3
|
|
|
4
|
+
import * as ParseResult from "effect/ParseResult";
|
|
4
5
|
import { MobyConnectionOptions } from "../../MobyConnection.js";
|
|
5
6
|
import { makeAgnosticHttpClientLayer } from "../../MobyPlatforms.js";
|
|
6
7
|
import { mapError } from "../convey/sinks.ts";
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
JSONMessage,
|
|
14
15
|
RegistrySearchResult,
|
|
15
16
|
} from "../generated/index.js";
|
|
17
|
+
import { WithRegistryAuthHeader } from "./auth.ts";
|
|
16
18
|
import { DockerError } from "./circular.ts";
|
|
17
19
|
import {
|
|
18
20
|
BadRequest,
|
|
@@ -38,8 +40,20 @@ export class ListFilters extends Schema.parseJson(
|
|
|
38
40
|
/** @since 1.0.0 */
|
|
39
41
|
export class SearchFilters extends Schema.parseJson(
|
|
40
42
|
Schema.Struct({
|
|
41
|
-
"is-official": Schema.
|
|
42
|
-
|
|
43
|
+
"is-official": Schema.transformOrFail(Schema.Tuple(Schema.String), Schema.BooleanFromString, {
|
|
44
|
+
decode: (_fromA, _options, ast) =>
|
|
45
|
+
ParseResult.fail(
|
|
46
|
+
new ParseResult.Forbidden(ast, _fromA, "Decoding 'is-official' filter is not supported")
|
|
47
|
+
),
|
|
48
|
+
encode: (automated) => ParseResult.succeed([automated] as const),
|
|
49
|
+
}).pipe(Schema.optional),
|
|
50
|
+
"is-automated": Schema.transformOrFail(Schema.Tuple(Schema.String), Schema.BooleanFromString, {
|
|
51
|
+
decode: (_fromA, _options, ast) =>
|
|
52
|
+
ParseResult.fail(
|
|
53
|
+
new ParseResult.Forbidden(ast, _fromA, "Decoding 'is-automated' filter is not supported")
|
|
54
|
+
),
|
|
55
|
+
encode: (automated) => ParseResult.succeed([automated] as const),
|
|
56
|
+
}).pipe(Schema.optional),
|
|
43
57
|
stars: Schema.optional(Schema.NumberFromString),
|
|
44
58
|
})
|
|
45
59
|
) {}
|
|
@@ -155,7 +169,7 @@ const historyImageEndpoint = HttpApiEndpoint.get("history", "/images/:name/histo
|
|
|
155
169
|
const pushImageEndpoint = HttpApiEndpoint.post("push", "/images/:name/push")
|
|
156
170
|
.setPath(Schema.Struct({ name: Schema.String }))
|
|
157
171
|
.setUrlParams(Schema.Struct({ tag: Schema.optional(Schema.String), platform: Schema.optional(Schema.String) }))
|
|
158
|
-
.setHeaders(Schema.Struct({ "X-Registry-Auth": Schema.String }))
|
|
172
|
+
.setHeaders(Schema.Struct({ "X-Registry-Auth": Schema.optional(Schema.String) }))
|
|
159
173
|
.addSuccess(HttpApiSchema.Empty(200))
|
|
160
174
|
.addError(NotFound); // 404 No such image
|
|
161
175
|
|
|
@@ -203,7 +217,7 @@ const pruneImagesEndpoint = HttpApiEndpoint.post("prune", "/images/prune")
|
|
|
203
217
|
.addSuccess(
|
|
204
218
|
Schema.Struct({
|
|
205
219
|
SpaceReclaimed: Schema.Number,
|
|
206
|
-
ImagesDeleted: Schema.
|
|
220
|
+
ImagesDeleted: Schema.NullishOr(Schema.Array(ImageDeleteResponse)),
|
|
207
221
|
}),
|
|
208
222
|
{ status: 200 }
|
|
209
223
|
);
|
|
@@ -297,7 +311,11 @@ export class Images extends Effect.Service<Images>()("@the-moby-effect/endpoints
|
|
|
297
311
|
HttpApiEndpoint.HttpApiEndpoint.WithName<(typeof ImagesGroup.endpoints)[number], Name>
|
|
298
312
|
>;
|
|
299
313
|
|
|
300
|
-
const httpClient = yield*
|
|
314
|
+
const httpClient = yield* Effect.map(
|
|
315
|
+
HttpClient.HttpClient,
|
|
316
|
+
WithRegistryAuthHeader(buildImageEndpoint, createImageEndpoint, pushImageEndpoint)
|
|
317
|
+
);
|
|
318
|
+
|
|
301
319
|
const ImagesError = DockerError.WrapForModule("images");
|
|
302
320
|
const client = yield* HttpApiClient.group(ImagesApi, { group: "images", httpClient });
|
|
303
321
|
|
|
@@ -328,8 +346,8 @@ export class Images extends Effect.Service<Images>()("@the-moby-effect/endpoints
|
|
|
328
346
|
"create",
|
|
329
347
|
httpClient
|
|
330
348
|
)({
|
|
349
|
+
headers: {},
|
|
331
350
|
urlParams: { ...options },
|
|
332
|
-
headers: { "X-Registry-Auth": undefined },
|
|
333
351
|
})
|
|
334
352
|
.pipe(Stream.decodeText())
|
|
335
353
|
.pipe(Stream.splitLines)
|
|
@@ -349,7 +367,7 @@ export class Images extends Effect.Service<Images>()("@the-moby-effect/endpoints
|
|
|
349
367
|
)({
|
|
350
368
|
path: { name },
|
|
351
369
|
urlParams: { ...options },
|
|
352
|
-
headers: {
|
|
370
|
+
headers: {},
|
|
353
371
|
})
|
|
354
372
|
.pipe(Stream.decodeText())
|
|
355
373
|
.pipe(Stream.splitLines)
|
|
@@ -367,25 +385,42 @@ export class Images extends Effect.Service<Images>()("@the-moby-effect/endpoints
|
|
|
367
385
|
const commit_ = (payload: ContainerCreateRequest, options: Options<"commit">) =>
|
|
368
386
|
Effect.mapError(client.commit({ urlParams: { ...options }, payload }), ImagesError("commit"));
|
|
369
387
|
const export_ = (name: string, options?: Options<"export">) =>
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
388
|
+
HttpApiStreamingResponse(
|
|
389
|
+
ImagesApi,
|
|
390
|
+
"images",
|
|
391
|
+
"export",
|
|
392
|
+
httpClient
|
|
393
|
+
)({ path: { name }, urlParams: { ...options } })
|
|
394
|
+
.pipe(Stream.decodeText())
|
|
395
|
+
.pipe(Stream.splitLines)
|
|
396
|
+
.pipe(Stream.mapEffect(Schema.decode(Schema.parseJson(JSONMessage))))
|
|
397
|
+
.pipe(mapError)
|
|
398
|
+
.pipe(Stream.mapError(ImagesError("export")));
|
|
379
399
|
const exportMany_ = (options?: Options<"exportMany">) =>
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
400
|
+
HttpApiStreamingResponse(
|
|
401
|
+
ImagesApi,
|
|
402
|
+
"images",
|
|
403
|
+
"exportMany",
|
|
404
|
+
httpClient
|
|
405
|
+
)({ urlParams: { ...options } })
|
|
406
|
+
.pipe(Stream.decodeText())
|
|
407
|
+
.pipe(Stream.splitLines)
|
|
408
|
+
.pipe(Stream.mapEffect(Schema.decode(Schema.parseJson(JSONMessage))))
|
|
409
|
+
.pipe(mapError)
|
|
410
|
+
.pipe(Stream.mapError(ImagesError("exportMany")));
|
|
384
411
|
const import_ = <E>(context: Stream.Stream<Uint8Array, E, never>, options?: Options<"import">) =>
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
412
|
+
HttpApiStreamingBoth(
|
|
413
|
+
ImagesApi,
|
|
414
|
+
"images",
|
|
415
|
+
"import",
|
|
416
|
+
httpClient,
|
|
417
|
+
context
|
|
418
|
+
)({ urlParams: { ...options } })
|
|
419
|
+
.pipe(Stream.decodeText())
|
|
420
|
+
.pipe(Stream.splitLines)
|
|
421
|
+
.pipe(Stream.mapEffect(Schema.decode(Schema.parseJson(JSONMessage))))
|
|
422
|
+
.pipe(mapError)
|
|
423
|
+
.pipe(Stream.mapError(ImagesError("import")));
|
|
389
424
|
|
|
390
425
|
return {
|
|
391
426
|
list: list_,
|
|
@@ -81,7 +81,7 @@ const disconnectNetworkEndpoint = HttpApiEndpoint.post("disconnect", "/:id/disco
|
|
|
81
81
|
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Network/operation/NetworkPrune */
|
|
82
82
|
const pruneNetworkEndpoint = HttpApiEndpoint.post("prune", "/prune")
|
|
83
83
|
.setUrlParams(Schema.Struct({ filters: Schema.optional(PruneFilters) }))
|
|
84
|
-
.addSuccess(Schema.Struct({ NetworksDeleted: Schema.
|
|
84
|
+
.addSuccess(Schema.Struct({ NetworksDeleted: Schema.NullishOr(Schema.Array(Schema.String)) }), { status: 200 });
|
|
85
85
|
|
|
86
86
|
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Network */
|
|
87
87
|
const NetworksGroup = HttpApiGroup.make("networks")
|
|
@@ -129,13 +129,19 @@ export class Networks extends Effect.Service<Networks>()("@the-moby-effect/endpo
|
|
|
129
129
|
|
|
130
130
|
const list_ = (filters?: Schema.Schema.Type<ListFilters>) =>
|
|
131
131
|
Effect.mapError(client.list({ urlParams: { filters } }), NetworksError("list"));
|
|
132
|
-
const create_ = (payload: NetworkCreateRequest) =>
|
|
133
|
-
Effect.mapError(client.create({ payload }), NetworksError("create"));
|
|
132
|
+
const create_ = (...payload: ConstructorParameters<typeof NetworkCreateRequest>) =>
|
|
133
|
+
Effect.mapError(client.create({ payload: NetworkCreateRequest.make(...payload) }), NetworksError("create"));
|
|
134
134
|
const inspect_ = (id: string, options?: Options<"inspect">) =>
|
|
135
135
|
Effect.mapError(client.inspect({ path: { id }, urlParams: { ...options } }), NetworksError("inspect"));
|
|
136
136
|
const delete_ = (id: string) => Effect.mapError(client.delete({ path: { id } }), NetworksError("delete"));
|
|
137
|
-
const connect_ = (id: string, payload: NetworkConnectOptions) =>
|
|
138
|
-
Effect.mapError(
|
|
137
|
+
const connect_ = (id: string, ...payload: ConstructorParameters<typeof NetworkConnectOptions>) =>
|
|
138
|
+
Effect.mapError(
|
|
139
|
+
client.connect({
|
|
140
|
+
path: { id },
|
|
141
|
+
payload: NetworkConnectOptions.make(...payload),
|
|
142
|
+
}),
|
|
143
|
+
NetworksError("connect")
|
|
144
|
+
);
|
|
139
145
|
const disconnect_ = (
|
|
140
146
|
id: string,
|
|
141
147
|
containerId: ContainerIdentifier,
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, HttpClient } from "@effect/platform";
|
|
2
|
-
import { Array, Effect, Schema, Stream, type Layer } from "effect";
|
|
2
|
+
import { Array, Effect, ParseResult, Schema, Stream, type Layer } from "effect";
|
|
3
3
|
|
|
4
4
|
import { MobyConnectionOptions } from "../../MobyConnection.js";
|
|
5
5
|
import { makeAgnosticHttpClientLayer } from "../../MobyPlatforms.js";
|
|
6
6
|
import { mapError } from "../convey/sinks.ts";
|
|
7
7
|
import { JSONMessage, TypesPlugin as Plugin, RuntimePluginPrivilege as PluginPrivilege } from "../generated/index.js";
|
|
8
|
+
import { WithRegistryAuthHeader } from "./auth.ts";
|
|
8
9
|
import { DockerError } from "./circular.ts";
|
|
9
10
|
import { HttpApiStreamingRequest, HttpApiStreamingResponse, InternalServerError, NotFound } from "./httpApiHacks.js";
|
|
10
11
|
|
|
11
12
|
/** @since 1.0.0 */
|
|
12
13
|
export class ListFilters extends Schema.parseJson(
|
|
13
14
|
Schema.Struct({
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
capability: Schema.optional(Schema.Array(Schema.String)),
|
|
16
|
+
enabled: Schema.transformOrFail(Schema.Tuple(Schema.String), Schema.BooleanFromString, {
|
|
17
|
+
decode: (_fromA, _options, ast) =>
|
|
18
|
+
ParseResult.fail(new ParseResult.Forbidden(ast, _fromA, "Decoding 'enabled' filter is not supported")),
|
|
19
|
+
encode: (automated) => ParseResult.succeed([automated] as const),
|
|
20
|
+
}).pipe(Schema.optional),
|
|
16
21
|
})
|
|
17
22
|
) {}
|
|
18
23
|
|
|
@@ -67,7 +72,8 @@ const upgradePluginEndpoint = HttpApiEndpoint.post("upgrade", "/:name/upgrade")
|
|
|
67
72
|
.setUrlParams(Schema.Struct({ remote: Schema.String }))
|
|
68
73
|
.setHeaders(Schema.Struct({ "X-Registry-Auth": Schema.optional(Schema.String) }))
|
|
69
74
|
.setPayload(Schema.Array(PluginPrivilege))
|
|
70
|
-
.addSuccess(HttpApiSchema.
|
|
75
|
+
.addSuccess(HttpApiSchema.Empty(200)) // 200 OK
|
|
76
|
+
.addSuccess(HttpApiSchema.Empty(204)) // 204 No Content
|
|
71
77
|
.addError(NotFound); // 404 No such plugin
|
|
72
78
|
|
|
73
79
|
/** @see https://docs.docker.com/reference/api/engine/latest/#tag/Plugin/operation/PluginCreate */
|
|
@@ -132,7 +138,11 @@ export class Plugins extends Effect.Service<Plugins>()("@the-moby-effect/endpoin
|
|
|
132
138
|
HttpApiEndpoint.HttpApiEndpoint.WithName<(typeof PluginsGroup.endpoints)[number], Name>
|
|
133
139
|
>;
|
|
134
140
|
|
|
135
|
-
const httpClient = yield*
|
|
141
|
+
const httpClient = yield* Effect.map(
|
|
142
|
+
HttpClient.HttpClient,
|
|
143
|
+
WithRegistryAuthHeader(pullPluginEndpoint, upgradePluginEndpoint)
|
|
144
|
+
);
|
|
145
|
+
|
|
136
146
|
const PluginsError = DockerError.WrapForModule("plugins");
|
|
137
147
|
const client = yield* HttpApiClient.group(PluginsApi, { group: "plugins", httpClient });
|
|
138
148
|
|
|
@@ -153,7 +163,7 @@ export class Plugins extends Effect.Service<Plugins>()("@the-moby-effect/endpoin
|
|
|
153
163
|
"pull",
|
|
154
164
|
httpClient
|
|
155
165
|
)({
|
|
156
|
-
headers: {
|
|
166
|
+
headers: {},
|
|
157
167
|
urlParams: { remote, name: options?.name },
|
|
158
168
|
payload: Array.map(options?.privileges ?? [], (privilege) => PluginPrivilege.make(privilege)),
|
|
159
169
|
})
|
|
@@ -182,9 +192,9 @@ export class Plugins extends Effect.Service<Plugins>()("@the-moby-effect/endpoin
|
|
|
182
192
|
) =>
|
|
183
193
|
Effect.mapError(
|
|
184
194
|
client.upgrade({
|
|
195
|
+
headers: {},
|
|
185
196
|
path: { name },
|
|
186
197
|
urlParams: { remote },
|
|
187
|
-
headers: { "X-Registry-Auth": "" },
|
|
188
198
|
payload: Array.map(privileges ?? [], (privilege) => PluginPrivilege.make(privilege)),
|
|
189
199
|
}),
|
|
190
200
|
PluginsError("upgrade")
|
|
@@ -5,6 +5,7 @@ import { MobyConnectionOptions } from "../../MobyConnection.js";
|
|
|
5
5
|
import { makeAgnosticHttpClientLayer } from "../../MobyPlatforms.js";
|
|
6
6
|
import { SwarmService, SwarmServiceSpec } from "../generated/index.js";
|
|
7
7
|
import { ServiceIdentifier } from "../schemas/id.js";
|
|
8
|
+
import { WithRegistryAuthHeader } from "./auth.ts";
|
|
8
9
|
import { DockerError } from "./circular.ts";
|
|
9
10
|
import {
|
|
10
11
|
BadRequest,
|
|
@@ -151,24 +152,38 @@ export class Services extends Effect.Service<Services>()("@the-moby-effect/endpo
|
|
|
151
152
|
HttpApiEndpoint.HttpApiEndpoint.WithName<(typeof ServicesGroup.endpoints)[number], Name>
|
|
152
153
|
>;
|
|
153
154
|
|
|
154
|
-
const httpClient = yield*
|
|
155
|
+
const httpClient = yield* Effect.map(
|
|
156
|
+
HttpClient.HttpClient,
|
|
157
|
+
WithRegistryAuthHeader(createServiceEndpoint, updateServiceEndpoint)
|
|
158
|
+
);
|
|
159
|
+
|
|
155
160
|
const ServicesError = DockerError.WrapForModule("services");
|
|
156
161
|
const client = yield* HttpApiClient.group(ServicesApi, { group: "services", httpClient });
|
|
157
162
|
|
|
158
163
|
const list_ = (options?: Options<"list">) =>
|
|
159
164
|
Effect.mapError(client.list({ urlParams: { ...options } }), ServicesError("list"));
|
|
160
|
-
const create_ = (payload: SwarmServiceSpec) =>
|
|
161
|
-
Effect.mapError(
|
|
165
|
+
const create_ = (...payload: ConstructorParameters<typeof SwarmServiceSpec>) =>
|
|
166
|
+
Effect.mapError(
|
|
167
|
+
client.create({
|
|
168
|
+
headers: {},
|
|
169
|
+
payload: SwarmServiceSpec.make(...payload),
|
|
170
|
+
}),
|
|
171
|
+
ServicesError("create")
|
|
172
|
+
);
|
|
162
173
|
const delete_ = (id: string) => Effect.mapError(client.delete({ path: { id } }), ServicesError("delete"));
|
|
163
174
|
const inspect_ = (id: string, options?: Options<"inspect">) =>
|
|
164
175
|
Effect.mapError(client.inspect({ path: { id }, urlParams: { ...options } }), ServicesError("inspect"));
|
|
165
|
-
const update_ = (
|
|
176
|
+
const update_ = (
|
|
177
|
+
id: string,
|
|
178
|
+
options: Options<"update">,
|
|
179
|
+
...payload: ConstructorParameters<typeof SwarmServiceSpec>
|
|
180
|
+
) =>
|
|
166
181
|
Effect.mapError(
|
|
167
182
|
client.update({
|
|
168
|
-
|
|
183
|
+
headers: {},
|
|
169
184
|
path: { id },
|
|
170
185
|
urlParams: { ...options },
|
|
171
|
-
|
|
186
|
+
payload: SwarmServiceSpec.make(...payload),
|
|
172
187
|
}),
|
|
173
188
|
ServicesError("update")
|
|
174
189
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, HttpClient } from "@effect/platform";
|
|
2
|
-
import { Effect, Schema, Stream
|
|
2
|
+
import { Effect, type Layer, Schema, Stream } from "effect";
|
|
3
3
|
|
|
4
4
|
import { MobyConnectionOptions } from "../../MobyConnection.js";
|
|
5
5
|
import { makeAgnosticHttpClientLayer } from "../../MobyPlatforms.js";
|
|
@@ -103,7 +103,7 @@ const makeDindBinds = <ExposeDindBy extends MobyConnection.MobyConnectionOptions
|
|
|
103
103
|
Effect.gen(function* () {
|
|
104
104
|
const acquireScopedVolume = Effect.acquireRelease(
|
|
105
105
|
MobyEndpoints.Volumes.use((volumes) => volumes.create({})),
|
|
106
|
-
({ Name }) => Effect.orDie(MobyEndpoints.Volumes.use((volumes) => volumes.delete(Name)))
|
|
106
|
+
({ Name }) => Effect.orDie(MobyEndpoints.Volumes.use((volumes) => volumes.delete(Name, { force: true })))
|
|
107
107
|
);
|
|
108
108
|
|
|
109
109
|
const volume1 = yield* acquireScopedVolume;
|
|
@@ -111,7 +111,13 @@ const makeDindBinds = <ExposeDindBy extends MobyConnection.MobyConnectionOptions
|
|
|
111
111
|
|
|
112
112
|
const tempSocketDirectory = yield* Effect.if(exposeDindBy === "socket", {
|
|
113
113
|
onFalse: () => Effect.succeed(""),
|
|
114
|
-
onTrue: () =>
|
|
114
|
+
onTrue: () =>
|
|
115
|
+
Effect.gen(function* () {
|
|
116
|
+
const fs = yield* FileSystem.FileSystem;
|
|
117
|
+
const folder = yield* fs.makeTempDirectoryScoped();
|
|
118
|
+
yield* fs.chmod(folder, 0o777); // Ew, for github actions where uid != 1000 🤮
|
|
119
|
+
return folder;
|
|
120
|
+
}),
|
|
115
121
|
});
|
|
116
122
|
|
|
117
123
|
const boundDockerSocket = yield* Effect.if(exposeDindBy === "socket", {
|
|
@@ -33,7 +33,7 @@ export class ContainerConfig extends Schema.Class<ContainerConfig>("ContainerCon
|
|
|
33
33
|
.pipe(Schema.withConstructorDefault(() => null)),
|
|
34
34
|
NetworkDisabled: Schema.optional(Schema.Boolean),
|
|
35
35
|
MacAddress: Schema.optional(Schema.String),
|
|
36
|
-
OnBuild: Schema.
|
|
36
|
+
OnBuild: Schema.NullishOr(Schema.Array(Schema.String))
|
|
37
37
|
.pipe(Schema.propertySignature)
|
|
38
38
|
.pipe(Schema.withConstructorDefault(() => null)),
|
|
39
39
|
Labels: Schema.NullOr(Schema.Record({ key: Schema.String, value: Schema.String }))
|
|
@@ -26,7 +26,7 @@ export class ContainerContainerJSONBase extends Schema.Class<ContainerContainerJ
|
|
|
26
26
|
AppArmorProfile: Schema.String,
|
|
27
27
|
ExecIDs: Schema.NullOr(Schema.Array(Schema.String)),
|
|
28
28
|
HostConfig: Schema.NullOr(ContainerHostConfig.ContainerHostConfig),
|
|
29
|
-
GraphDriver: Schema.
|
|
29
|
+
GraphDriver: Schema.NullishOr(StorageDriverData.StorageDriverData), // optional for docker.io/library/docker:26-dind-rootless
|
|
30
30
|
SizeRw: Schema.optionalWith(EffectSchemas.Number.I64, { nullable: true }),
|
|
31
31
|
SizeRootFs: Schema.optionalWith(EffectSchemas.Number.I64, { nullable: true }),
|
|
32
32
|
},
|