@mastra/docker 0.8.0-alpha.1 → 0.8.0
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/README.md +33 -0
- package/dist/index.cjs +248 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +248 -28
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts +2 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/sandbox/index.d.ts +80 -0
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/process-manager.d.ts.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -29,6 +29,39 @@ const agent = new Agent({
|
|
|
29
29
|
});
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
### Volume subpath mounts
|
|
33
|
+
|
|
34
|
+
Use `mounts` (mapped 1:1 onto Docker's `HostConfig.Mounts`) when you need mount
|
|
35
|
+
options that the `-v`/`volumes` syntax cannot express — most notably mounting a
|
|
36
|
+
subdirectory of a named volume. Requires Docker Engine 26.0+ (API v1.45+) for
|
|
37
|
+
`subpath`.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
const workspace = new Workspace({
|
|
41
|
+
sandbox: new DockerSandbox({
|
|
42
|
+
image: 'node:22-slim',
|
|
43
|
+
mounts: [
|
|
44
|
+
// Read-only parent from a named volume
|
|
45
|
+
{ type: 'volume', source: 'project-data', target: '/shared', readOnly: true },
|
|
46
|
+
// Writable per-conversation subdirectory of the same volume
|
|
47
|
+
{
|
|
48
|
+
type: 'volume',
|
|
49
|
+
source: 'project-data',
|
|
50
|
+
target: '/work',
|
|
51
|
+
volumeOptions: { subpath: 'conversations/abc123' },
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`volumes` and `mounts` can be combined; both are passed through to Docker.
|
|
59
|
+
|
|
60
|
+
> **Note:** Docker does not create `volumeOptions.subpath` for you — the
|
|
61
|
+
> subdirectory must already exist inside the named volume before the container
|
|
62
|
+
> starts, otherwise the mount fails. Provision it ahead of time (for example,
|
|
63
|
+
> with a one-off container that creates `conversations/abc123` in the volume).
|
|
64
|
+
|
|
32
65
|
## Documentation
|
|
33
66
|
|
|
34
67
|
- [Docker Sandbox integration guide](https://mastra.ai/integrations/sandboxes/docker)
|
package/dist/index.cjs
CHANGED
|
@@ -27,8 +27,109 @@ let _mastra_core_workspace = require("@mastra/core/workspace");
|
|
|
27
27
|
let dockerode = require("dockerode");
|
|
28
28
|
dockerode = __toESM(dockerode, 1);
|
|
29
29
|
let tar_stream = require("tar-stream");
|
|
30
|
+
let crypto = require("crypto");
|
|
30
31
|
//#region src/sandbox/process-manager.ts
|
|
31
32
|
/**
|
|
33
|
+
* Docker Process Manager
|
|
34
|
+
*
|
|
35
|
+
* Implements SandboxProcessManager for Docker containers.
|
|
36
|
+
* Uses `container.exec()` to run commands inside a long-lived container.
|
|
37
|
+
* Each spawned process gets a dedicated exec instance with separate
|
|
38
|
+
* stdout/stderr streams.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Directory (inside the container) where each spawned process records the PGID
|
|
42
|
+
* of its process group. Created with mode 700 so only the (root) exec user can
|
|
43
|
+
* write the PGID files.
|
|
44
|
+
*/
|
|
45
|
+
const PROC_DIR = "/tmp/.mastra-proc";
|
|
46
|
+
/**
|
|
47
|
+
* Wrapper (run as the exec command) that places the user command in its own
|
|
48
|
+
* process group and records the group's PGID so kill() can signal the whole
|
|
49
|
+
* group later.
|
|
50
|
+
*
|
|
51
|
+
* Docker's exec-inspect `Pid` is a host/daemon-namespace PID and cannot be used
|
|
52
|
+
* with an in-container `kill`, so we need a container-namespace identity. We use
|
|
53
|
+
* a *kernel-enforced* process group as that identity:
|
|
54
|
+
*
|
|
55
|
+
* 1. `setsid -w` re-execs the command as a new session/process-group leader,
|
|
56
|
+
* so its PID == PGID. Every descendant inherits that PGID (unless it calls
|
|
57
|
+
* `setsid` itself) and stays reachable even if it re-parents to PID 1.
|
|
58
|
+
* `-w` keeps the wrapper (and thus the exec) alive for the whole lifetime
|
|
59
|
+
* and propagates the child's exit status — without it `setsid` forks and
|
|
60
|
+
* returns immediately, so the exec would appear to finish while the real
|
|
61
|
+
* work keeps running.
|
|
62
|
+
* 2. The leader writes its own PID (`$$`) — the PGID — to a private file that
|
|
63
|
+
* only this process wrote, so the identity is kernel-owned and cannot be
|
|
64
|
+
* forged by another container process.
|
|
65
|
+
*
|
|
66
|
+
* If `setsid -w` is unavailable in the image (e.g. BusyBox), we degrade
|
|
67
|
+
* gracefully: the command runs directly and we record its PID so kill() can
|
|
68
|
+
* still signal it (descendant coverage is then best-effort). The probe
|
|
69
|
+
* `setsid -w true` also covers images without setsid at all.
|
|
70
|
+
*
|
|
71
|
+
* Positional args: $1 = pgid file path, $2 = user command. The script text is a
|
|
72
|
+
* static constant; runtime values travel only as argv, never interpolated into
|
|
73
|
+
* the command string.
|
|
74
|
+
*/
|
|
75
|
+
const SPAWN_WRAPPER = `
|
|
76
|
+
umask 077
|
|
77
|
+
d="\${1%/*}"
|
|
78
|
+
mkdir -p "$d" 2>/dev/null
|
|
79
|
+
chmod 700 "$d" 2>/dev/null
|
|
80
|
+
if setsid -w true >/dev/null 2>&1; then
|
|
81
|
+
exec setsid -w sh -c 'echo $$ > "$1" || exit 126; sh -c "$2"; ret=$?; rm -f "$1" 2>/dev/null; exit $ret' sh "$1" "$2"
|
|
82
|
+
fi
|
|
83
|
+
echo $$ > "$1" || exit 126
|
|
84
|
+
sh -c "$2"
|
|
85
|
+
ret=$?
|
|
86
|
+
rm -f "$1" 2>/dev/null
|
|
87
|
+
exit $ret
|
|
88
|
+
`;
|
|
89
|
+
/**
|
|
90
|
+
* Kill script: read the recorded PGID and SIGKILL the whole process group.
|
|
91
|
+
* A negative PID targets the kernel-owned process group, so descendants that
|
|
92
|
+
* re-parented to PID 1 are still caught. We SIGSTOP the
|
|
93
|
+
* group first to freeze fork races, then SIGKILL. The file may not exist yet if
|
|
94
|
+
* kill races the leader's first write, so we briefly wait for it.
|
|
95
|
+
*
|
|
96
|
+
* Positional arg: $1 = pgid file path (static script; no interpolation).
|
|
97
|
+
*/
|
|
98
|
+
const KILL_SCRIPT = `
|
|
99
|
+
f="$1"
|
|
100
|
+
i=0
|
|
101
|
+
while [ ! -r "$f" ] && [ "$i" -lt 40 ]; do sleep 0.05; i=$((i + 1)); done
|
|
102
|
+
# If the PGID was never recorded (file absent/unreadable after the wait, or
|
|
103
|
+
# empty), we have no group to signal or verify — report failure rather than
|
|
104
|
+
# falsely claiming the tree was terminated.
|
|
105
|
+
[ -r "$f" ] || exit 1
|
|
106
|
+
pgid=$(cat "$f" 2>/dev/null)
|
|
107
|
+
rm -f "$f" 2>/dev/null
|
|
108
|
+
[ -n "$pgid" ] || exit 1
|
|
109
|
+
kill -STOP -"$pgid" 2>/dev/null
|
|
110
|
+
kill -KILL -"$pgid" 2>/dev/null
|
|
111
|
+
# Fallback for images without setsid: the leader is not a group leader, so also
|
|
112
|
+
# signal it directly.
|
|
113
|
+
kill -KILL "$pgid" 2>/dev/null
|
|
114
|
+
# Verify the group is actually gone before reporting success. kill -0 probes
|
|
115
|
+
# for the group's existence without sending a signal, and we poll while it
|
|
116
|
+
# still reports the group alive. When the probe finally fails we must inspect
|
|
117
|
+
# why: ESRCH ("no such process") means every member was reaped, so report
|
|
118
|
+
# success; EPERM or any other error means termination is unconfirmed (e.g. a
|
|
119
|
+
# member dropped privileges and became unsignalable), so exit nonzero and let
|
|
120
|
+
# kill() report failure instead of falsely claiming the tree was terminated.
|
|
121
|
+
j=0
|
|
122
|
+
while err=$(kill -0 -"$pgid" 2>&1); do
|
|
123
|
+
j=$((j + 1))
|
|
124
|
+
[ "$j" -ge 40 ] && exit 1
|
|
125
|
+
sleep 0.05
|
|
126
|
+
done
|
|
127
|
+
case "$err" in
|
|
128
|
+
*[Ss]uch\\ process*) exit 0 ;;
|
|
129
|
+
*) exit 1 ;;
|
|
130
|
+
esac
|
|
131
|
+
`;
|
|
132
|
+
/**
|
|
32
133
|
* Wraps a Docker exec instance to conform to Mastra's ProcessHandle.
|
|
33
134
|
* Not exported — internal to this module.
|
|
34
135
|
*
|
|
@@ -48,13 +149,16 @@ var DockerProcessHandle = class extends _mastra_core_workspace.ProcessHandle {
|
|
|
48
149
|
_waitPromise = null;
|
|
49
150
|
_stdinStream = null;
|
|
50
151
|
_execStream = null;
|
|
51
|
-
|
|
152
|
+
/** @internal Container path of the file holding this process group's PGID. */
|
|
153
|
+
_pgidFile;
|
|
154
|
+
constructor(exec, container, startTime, stdinStream, pgidFile, options) {
|
|
52
155
|
super(options);
|
|
53
156
|
this.pid = exec.id;
|
|
54
157
|
this._exec = exec;
|
|
55
158
|
this._container = container;
|
|
56
159
|
this._startTime = startTime;
|
|
57
160
|
this._stdinStream = stdinStream;
|
|
161
|
+
this._pgidFile = pgidFile;
|
|
58
162
|
}
|
|
59
163
|
get exitCode() {
|
|
60
164
|
return this._exitCode;
|
|
@@ -85,37 +189,28 @@ var DockerProcessHandle = class extends _mastra_core_workspace.ProcessHandle {
|
|
|
85
189
|
async kill() {
|
|
86
190
|
if (this._exitCode !== void 0) return false;
|
|
87
191
|
try {
|
|
88
|
-
|
|
89
|
-
if (!info.Running || !info.Pid) {
|
|
90
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
91
|
-
info = await this._inspectExec();
|
|
92
|
-
}
|
|
93
|
-
if (!info.Running) {
|
|
94
|
-
this._killed = true;
|
|
95
|
-
this._destroyStream();
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
const pid = info.Pid;
|
|
99
|
-
if (!pid) {
|
|
100
|
-
this._killed = true;
|
|
101
|
-
this._destroyStream();
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
await (await this._container.exec({
|
|
192
|
+
const killExec = await this._container.exec({
|
|
105
193
|
Cmd: [
|
|
106
194
|
"sh",
|
|
107
195
|
"-c",
|
|
108
|
-
|
|
196
|
+
KILL_SCRIPT,
|
|
197
|
+
"sh",
|
|
198
|
+
this._pgidFile
|
|
109
199
|
],
|
|
110
200
|
AttachStdout: false,
|
|
111
201
|
AttachStderr: false
|
|
112
|
-
})
|
|
202
|
+
});
|
|
203
|
+
await killExec.start({});
|
|
204
|
+
let killInfo = await killExec.inspect();
|
|
205
|
+
while (killInfo.Running) {
|
|
206
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
207
|
+
killInfo = await killExec.inspect();
|
|
208
|
+
}
|
|
209
|
+
if (killInfo.ExitCode !== 0) throw new Error(`kill helper exited with code ${killInfo.ExitCode}`);
|
|
113
210
|
this._killed = true;
|
|
114
211
|
this._destroyStream();
|
|
115
212
|
return true;
|
|
116
213
|
} catch (error) {
|
|
117
|
-
this._killed = true;
|
|
118
|
-
this._destroyStream();
|
|
119
214
|
const msg = error instanceof Error ? error.message.toLowerCase() : "";
|
|
120
215
|
if (!msg.includes("no such process") && !msg.includes("esrch")) console.warn(`[DockerProcessManager] kill(${this.pid}) failed unexpectedly:`, error);
|
|
121
216
|
return false;
|
|
@@ -169,11 +264,15 @@ var DockerProcessManager = class extends _mastra_core_workspace.SandboxProcessMa
|
|
|
169
264
|
}
|
|
170
265
|
async spawn(command, options = {}) {
|
|
171
266
|
const container = this.container;
|
|
267
|
+
const pgidFile = `${PROC_DIR}/${(0, crypto.randomUUID)()}`;
|
|
172
268
|
const envArray = Object.entries({ ...options.env }).filter((entry) => entry[1] !== void 0).map(([k, v]) => `${k}=${v}`);
|
|
173
269
|
const exec = await container.exec({
|
|
174
270
|
Cmd: [
|
|
175
271
|
"sh",
|
|
176
272
|
"-c",
|
|
273
|
+
SPAWN_WRAPPER,
|
|
274
|
+
"sh",
|
|
275
|
+
pgidFile,
|
|
177
276
|
command
|
|
178
277
|
],
|
|
179
278
|
AttachStdout: true,
|
|
@@ -188,7 +287,7 @@ var DockerProcessManager = class extends _mastra_core_workspace.SandboxProcessMa
|
|
|
188
287
|
stdin: true
|
|
189
288
|
});
|
|
190
289
|
const startTime = Date.now();
|
|
191
|
-
const handle = new DockerProcessHandle(exec, container, startTime, stream, options);
|
|
290
|
+
const handle = new DockerProcessHandle(exec, container, startTime, stream, pgidFile, options);
|
|
192
291
|
handle._setExecStream(stream);
|
|
193
292
|
const waitPromise = new Promise((resolve) => {
|
|
194
293
|
const buffer = [];
|
|
@@ -264,8 +363,15 @@ var DockerProcessManager = class extends _mastra_core_workspace.SandboxProcessMa
|
|
|
264
363
|
if (handle.exitCode === void 0) {
|
|
265
364
|
handle._killed = true;
|
|
266
365
|
handle._timedOut = true;
|
|
267
|
-
|
|
268
|
-
|
|
366
|
+
const forceClose = () => {
|
|
367
|
+
if (handle.exitCode === void 0) {
|
|
368
|
+
handle._killed = true;
|
|
369
|
+
handle._destroyStream();
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
handle.kill().then((killed) => {
|
|
373
|
+
if (!killed) forceClose();
|
|
374
|
+
}).catch(forceClose);
|
|
269
375
|
}
|
|
270
376
|
}, resolvedTimeout);
|
|
271
377
|
waitPromise.then(() => clearTimeout(timer));
|
|
@@ -358,12 +464,14 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
358
464
|
_cpuQuota;
|
|
359
465
|
_cpuPeriod;
|
|
360
466
|
_pidsLimit;
|
|
467
|
+
_init;
|
|
361
468
|
_readonlyRootfs;
|
|
362
469
|
_capDrop;
|
|
363
470
|
_capAdd;
|
|
364
471
|
_securityOpt;
|
|
365
472
|
_ulimits;
|
|
366
473
|
_tmpfs;
|
|
474
|
+
_mounts;
|
|
367
475
|
_labels;
|
|
368
476
|
_instructionsOverride;
|
|
369
477
|
_constructorOptions;
|
|
@@ -397,12 +505,14 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
397
505
|
this._cpuQuota = options.cpuQuota;
|
|
398
506
|
this._cpuPeriod = options.cpuPeriod;
|
|
399
507
|
this._pidsLimit = options.pidsLimit;
|
|
508
|
+
this._init = options.init ?? true;
|
|
400
509
|
this._readonlyRootfs = options.readonlyRootfs;
|
|
401
510
|
this._capDrop = options.capDrop;
|
|
402
511
|
this._capAdd = options.capAdd;
|
|
403
512
|
this._securityOpt = options.securityOpt;
|
|
404
513
|
this._ulimits = options.ulimits;
|
|
405
514
|
this._tmpfs = options.tmpfs;
|
|
515
|
+
this._mounts = options.mounts;
|
|
406
516
|
this.setWorkingDirectory(options.workingDirectory ?? options.workingDir ?? "/workspace");
|
|
407
517
|
this._labels = {
|
|
408
518
|
...options.labels,
|
|
@@ -484,12 +594,14 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
484
594
|
CpuQuota: this._cpuQuota,
|
|
485
595
|
CpuPeriod: this._cpuPeriod,
|
|
486
596
|
PidsLimit: this._pidsLimit,
|
|
597
|
+
Init: this._init,
|
|
487
598
|
ReadonlyRootfs: this._readonlyRootfs,
|
|
488
599
|
CapDrop: this._capDrop,
|
|
489
600
|
CapAdd: this._capAdd,
|
|
490
601
|
SecurityOpt: this._securityOpt,
|
|
491
602
|
Ulimits: this._ulimits?.map(toDockerUlimit),
|
|
492
|
-
Tmpfs: this._tmpfs
|
|
603
|
+
Tmpfs: this._tmpfs,
|
|
604
|
+
Mounts: this._mounts?.map(toDockerMount)
|
|
493
605
|
},
|
|
494
606
|
OpenStdin: true,
|
|
495
607
|
Tty: false
|
|
@@ -543,7 +655,8 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
543
655
|
["CapAdd", this._capAdd],
|
|
544
656
|
["SecurityOpt", this._securityOpt],
|
|
545
657
|
["Ulimits", this._ulimits],
|
|
546
|
-
["Tmpfs", this._tmpfs]
|
|
658
|
+
["Tmpfs", this._tmpfs],
|
|
659
|
+
["Mounts", this._mounts?.map(toDockerMount)]
|
|
547
660
|
];
|
|
548
661
|
if (this._privilegedWasSet || hostConfig?.Privileged === true) entries.unshift(["Privileged", this._privileged]);
|
|
549
662
|
return entries.filter((entry) => isPresentHostConfigValue(entry[1]));
|
|
@@ -819,6 +932,31 @@ function toDockerUlimit(ulimit) {
|
|
|
819
932
|
Hard: ulimit.hard
|
|
820
933
|
};
|
|
821
934
|
}
|
|
935
|
+
function toDockerMount(mount) {
|
|
936
|
+
const settings = {
|
|
937
|
+
Type: mount.type,
|
|
938
|
+
Target: mount.target,
|
|
939
|
+
Source: mount.type === "tmpfs" ? "" : mount.source
|
|
940
|
+
};
|
|
941
|
+
if (mount.readOnly !== void 0) settings.ReadOnly = mount.readOnly;
|
|
942
|
+
if (mount.type === "volume" && mount.volumeOptions) {
|
|
943
|
+
const { subpath, noCopy, labels } = mount.volumeOptions;
|
|
944
|
+
const volumeOptions = {};
|
|
945
|
+
if (subpath !== void 0) volumeOptions.Subpath = subpath;
|
|
946
|
+
if (noCopy !== void 0) volumeOptions.NoCopy = noCopy;
|
|
947
|
+
if (labels !== void 0) volumeOptions.Labels = labels;
|
|
948
|
+
if (Object.keys(volumeOptions).length > 0) settings.VolumeOptions = volumeOptions;
|
|
949
|
+
}
|
|
950
|
+
if (mount.type === "bind" && mount.bindOptions?.propagation !== void 0) settings.BindOptions = { Propagation: mount.bindOptions.propagation };
|
|
951
|
+
if (mount.type === "tmpfs" && mount.tmpfsOptions) {
|
|
952
|
+
const { sizeBytes, mode } = mount.tmpfsOptions;
|
|
953
|
+
const tmpfsOptions = {};
|
|
954
|
+
if (sizeBytes !== void 0) tmpfsOptions.SizeBytes = sizeBytes;
|
|
955
|
+
if (mode !== void 0) tmpfsOptions.Mode = mode;
|
|
956
|
+
if (Object.keys(tmpfsOptions).length > 0) settings.TmpfsOptions = tmpfsOptions;
|
|
957
|
+
}
|
|
958
|
+
return settings;
|
|
959
|
+
}
|
|
822
960
|
function toDockerSandboxOptionName(field) {
|
|
823
961
|
return {
|
|
824
962
|
Privileged: "privileged",
|
|
@@ -833,7 +971,8 @@ function toDockerSandboxOptionName(field) {
|
|
|
833
971
|
CapAdd: "capAdd",
|
|
834
972
|
SecurityOpt: "securityOpt",
|
|
835
973
|
Ulimits: "ulimits",
|
|
836
|
-
Tmpfs: "tmpfs"
|
|
974
|
+
Tmpfs: "tmpfs",
|
|
975
|
+
Mounts: "mounts"
|
|
837
976
|
}[field] ?? String(field);
|
|
838
977
|
}
|
|
839
978
|
//#endregion
|
|
@@ -944,6 +1083,87 @@ const dockerSandboxProvider = {
|
|
|
944
1083
|
type: "object",
|
|
945
1084
|
description: "tmpfs mount paths with options",
|
|
946
1085
|
additionalProperties: { type: "string" }
|
|
1086
|
+
},
|
|
1087
|
+
mounts: {
|
|
1088
|
+
type: "array",
|
|
1089
|
+
description: "Mounts mapped 1:1 onto Docker HostConfig.Mounts (supports volume subpath)",
|
|
1090
|
+
items: { oneOf: [
|
|
1091
|
+
{
|
|
1092
|
+
type: "object",
|
|
1093
|
+
required: [
|
|
1094
|
+
"type",
|
|
1095
|
+
"target",
|
|
1096
|
+
"source"
|
|
1097
|
+
],
|
|
1098
|
+
additionalProperties: false,
|
|
1099
|
+
properties: {
|
|
1100
|
+
type: { const: "volume" },
|
|
1101
|
+
target: { type: "string" },
|
|
1102
|
+
source: { type: "string" },
|
|
1103
|
+
readOnly: { type: "boolean" },
|
|
1104
|
+
volumeOptions: {
|
|
1105
|
+
type: "object",
|
|
1106
|
+
additionalProperties: false,
|
|
1107
|
+
properties: {
|
|
1108
|
+
subpath: { type: "string" },
|
|
1109
|
+
noCopy: { type: "boolean" },
|
|
1110
|
+
labels: {
|
|
1111
|
+
type: "object",
|
|
1112
|
+
additionalProperties: { type: "string" }
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
type: "object",
|
|
1120
|
+
required: [
|
|
1121
|
+
"type",
|
|
1122
|
+
"target",
|
|
1123
|
+
"source"
|
|
1124
|
+
],
|
|
1125
|
+
additionalProperties: false,
|
|
1126
|
+
properties: {
|
|
1127
|
+
type: { const: "bind" },
|
|
1128
|
+
target: { type: "string" },
|
|
1129
|
+
source: { type: "string" },
|
|
1130
|
+
readOnly: { type: "boolean" },
|
|
1131
|
+
bindOptions: {
|
|
1132
|
+
type: "object",
|
|
1133
|
+
additionalProperties: false,
|
|
1134
|
+
properties: { propagation: {
|
|
1135
|
+
type: "string",
|
|
1136
|
+
enum: [
|
|
1137
|
+
"private",
|
|
1138
|
+
"rprivate",
|
|
1139
|
+
"shared",
|
|
1140
|
+
"rshared",
|
|
1141
|
+
"slave",
|
|
1142
|
+
"rslave"
|
|
1143
|
+
]
|
|
1144
|
+
} }
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
},
|
|
1148
|
+
{
|
|
1149
|
+
type: "object",
|
|
1150
|
+
required: ["type", "target"],
|
|
1151
|
+
additionalProperties: false,
|
|
1152
|
+
properties: {
|
|
1153
|
+
type: { const: "tmpfs" },
|
|
1154
|
+
target: { type: "string" },
|
|
1155
|
+
readOnly: { type: "boolean" },
|
|
1156
|
+
tmpfsOptions: {
|
|
1157
|
+
type: "object",
|
|
1158
|
+
additionalProperties: false,
|
|
1159
|
+
properties: {
|
|
1160
|
+
sizeBytes: { type: "number" },
|
|
1161
|
+
mode: { type: "number" }
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
] }
|
|
947
1167
|
}
|
|
948
1168
|
}
|
|
949
1169
|
},
|