microsandbox-rb 0.12.0 → 0.13.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -0
- data/Cargo.lock +993 -552
- data/README.md +39 -6
- data/ext/microsandbox/Cargo.toml +4 -4
- data/ext/microsandbox/src/backend.rs +22 -0
- data/ext/microsandbox/src/error.rs +4 -0
- data/ext/microsandbox/src/sandbox.rs +198 -9
- data/ext/microsandbox/src/snapshot.rs +10 -3
- data/ext/microsandbox/src/volume.rs +9 -0
- data/lib/microsandbox/backend_info.rb +46 -0
- data/lib/microsandbox/errors.rb +4 -0
- data/lib/microsandbox/root_disk.rb +24 -0
- data/lib/microsandbox/sandbox.rb +181 -9
- data/lib/microsandbox/snapshot.rb +12 -8
- data/lib/microsandbox/version.rb +2 -2
- data/lib/microsandbox/volume.rb +16 -0
- data/lib/microsandbox.rb +16 -3
- data/sig/microsandbox.rbs +29 -2
- metadata +2 -1
data/lib/microsandbox/sandbox.rb
CHANGED
|
@@ -288,7 +288,14 @@ module Microsandbox
|
|
|
288
288
|
# @param hostname [String, nil] guest hostname
|
|
289
289
|
# @param labels [Hash, nil] metadata labels
|
|
290
290
|
# @param scripts [Hash, nil] named scripts to install
|
|
291
|
-
# @param entrypoint [Array<String>, nil] image
|
|
291
|
+
# @param entrypoint [Array<String>, nil] image ENTRYPOINT override. An
|
|
292
|
+
# explicit `[]` clears the image's ENTRYPOINT (so `exec_default` runs
|
|
293
|
+
# the CMD alone); `nil` (the default) inherits it.
|
|
294
|
+
# @param cmd [Array<String>, nil] image CMD override used by
|
|
295
|
+
# default-workload execution ({Sandbox#exec_default} et al., runtime
|
|
296
|
+
# v0.6.9). Durable configuration — it does **not** execute anything at
|
|
297
|
+
# create time (`create` boots the VM only). An explicit `[]` clears the
|
|
298
|
+
# image CMD; `nil` (the default) inherits it.
|
|
292
299
|
# @param ports [Hash, nil] host_port => guest_port TCP publications
|
|
293
300
|
# @param ports_udp [Hash, nil] host_port => guest_port UDP publications
|
|
294
301
|
# @param volumes [Hash, nil] guest_path => mount spec. Each value is a host
|
|
@@ -322,6 +329,17 @@ module Microsandbox
|
|
|
322
329
|
# @param ipv6_pool [String, nil] guest IPv6 address pool CIDR
|
|
323
330
|
# @param max_connections [Integer, nil] cap on concurrent proxied connections
|
|
324
331
|
# @param trust_host_cas [Boolean, nil] trust the host's CA bundle for upstream TLS
|
|
332
|
+
# @param rate_limiter [Hash, nil] per-sandbox egress/ingress token-bucket
|
|
333
|
+
# limits (runtime v0.6.9, local backend):
|
|
334
|
+
# `{ egress: { bandwidth: { size: 1_048_576, refill_time_ms: 1000,
|
|
335
|
+
# one_time_burst: 0 }, ops: { size: 1000, refill_time_ms: 1000 } },
|
|
336
|
+
# ingress: { ... } }`. `bandwidth` buckets meter bytes, `ops` buckets
|
|
337
|
+
# meter network frames; an omitted bucket or direction is unlimited.
|
|
338
|
+
# @param vsock [Hash, Array, nil] host sockets exposed on guest-to-host
|
|
339
|
+
# vsock ports (runtime v0.6.9): `{ "/host/api.sock" => 5000 }` (stream
|
|
340
|
+
# sockets), or an Array of
|
|
341
|
+
# `{ host_socket:, port:, socket_type: :stream|:dgram }` Hashes. Guests
|
|
342
|
+
# connect to host CID 2 on the given port; no in-guest proxy required.
|
|
325
343
|
# @param from_snapshot [String, nil] boot from a snapshot name or digest
|
|
326
344
|
# instead of an image (mutually exclusive with `image:`)
|
|
327
345
|
# @param fstype [String, nil] inner filesystem type (e.g. "ext4") when
|
|
@@ -343,9 +361,12 @@ module Microsandbox
|
|
|
343
361
|
# disk (runtime v0.6.7). An Integer is the managed ext4 upper's size cap
|
|
344
362
|
# in MiB (default kind, 4 GiB when unset); a Hash picks a kind via the
|
|
345
363
|
# {RootDisk} factory — `RootDisk.managed(8192)`, `RootDisk.tmpfs(2048)`
|
|
346
|
-
# (RAM-backed, pristine on every boot),
|
|
364
|
+
# (RAM-backed, pristine on every boot),
|
|
347
365
|
# `RootDisk.disk("./scratch.img", format: "raw", fstype: "ext4")`
|
|
348
|
-
# (user-supplied image attached writable)
|
|
366
|
+
# (user-supplied image attached writable), or
|
|
367
|
+
# `RootDisk.flat(8192, clone: :auto)` (a single complete ext4 root disk
|
|
368
|
+
# materialized from the OCI image, runtime v0.6.9 — skips the overlay
|
|
369
|
+
# stack; content-addressed and cached across sandboxes).
|
|
349
370
|
# @param oci_upper_size [Integer, nil] deprecated alias for
|
|
350
371
|
# `root_disk: <Integer>` (the managed kind); warns, and conflicts with
|
|
351
372
|
# `root_disk:`
|
|
@@ -418,9 +439,9 @@ module Microsandbox
|
|
|
418
439
|
def build_create_opts(image: nil, cpus: nil, max_cpus: nil, memory: nil, max_memory: nil,
|
|
419
440
|
env: nil, workdir: nil,
|
|
420
441
|
shell: nil, user: nil, hostname: nil, labels: nil, scripts: nil,
|
|
421
|
-
entrypoint: nil, ports: nil, ports_udp: nil, volumes: nil, network: nil,
|
|
442
|
+
entrypoint: nil, cmd: nil, ports: nil, ports_udp: nil, volumes: nil, network: nil,
|
|
422
443
|
dns: nil, tls: nil, ipv4_pool: nil, ipv6_pool: nil,
|
|
423
|
-
max_connections: nil, trust_host_cas: nil,
|
|
444
|
+
max_connections: nil, trust_host_cas: nil, rate_limiter: nil, vsock: nil,
|
|
424
445
|
patches: nil,
|
|
425
446
|
from_snapshot: nil, fstype: nil, init: nil, ephemeral: false,
|
|
426
447
|
log_level: nil, quiet_logs: false, security: nil,
|
|
@@ -465,7 +486,11 @@ module Microsandbox
|
|
|
465
486
|
opts["env"] = stringify(env) if env
|
|
466
487
|
opts["labels"] = stringify(labels) if labels
|
|
467
488
|
opts["scripts"] = stringify(scripts) if scripts
|
|
468
|
-
|
|
489
|
+
# entrypoint/cmd: an explicit empty Array clears the image's value
|
|
490
|
+
# (blocking the image-config merge), so presence is keyed on the kwarg
|
|
491
|
+
# itself — nil (the default) inherits from the image.
|
|
492
|
+
opts["entrypoint"] = Array(entrypoint).map(&:to_s) unless entrypoint.nil?
|
|
493
|
+
opts["cmd"] = Array(cmd).map(&:to_s) unless cmd.nil?
|
|
469
494
|
opts["ports"] = intify_ports(ports) if ports
|
|
470
495
|
opts["ports_udp"] = intify_ports(ports_udp) if ports_udp
|
|
471
496
|
opts["volumes"] = normalize_volumes(volumes) if volumes
|
|
@@ -477,6 +502,8 @@ module Microsandbox
|
|
|
477
502
|
opts["ipv6_pool"] = ipv6_pool.to_s if ipv6_pool
|
|
478
503
|
opts["max_connections"] = Integer(max_connections) if max_connections
|
|
479
504
|
set_bool(opts, "trust_host_cas", trust_host_cas)
|
|
505
|
+
opts["rate_limiter"] = normalize_rate_limiter(rate_limiter) if rate_limiter
|
|
506
|
+
opts["vsock"] = normalize_vsock(vsock) if vsock
|
|
480
507
|
opts["log_level"] = log_level.to_s if log_level
|
|
481
508
|
opts["quiet_logs"] = true if quiet_logs
|
|
482
509
|
opts["security"] = security.to_s if security
|
|
@@ -638,6 +665,7 @@ module Microsandbox
|
|
|
638
665
|
# always set (defaulting to "no_restart"); everything else is included only
|
|
639
666
|
# when provided so an unset option means "leave unchanged".
|
|
640
667
|
def build_modify_opts(cpus: nil, max_cpus: nil, memory: nil, max_memory: nil,
|
|
668
|
+
root_disk_size: nil,
|
|
641
669
|
env: nil, remove_env: nil, labels: nil, remove_labels: nil, workdir: nil,
|
|
642
670
|
secrets: nil, remove_secrets: nil, policy: nil, dry_run: false)
|
|
643
671
|
opts = {}
|
|
@@ -645,6 +673,7 @@ module Microsandbox
|
|
|
645
673
|
opts["max_cpus"] = Integer(max_cpus) if max_cpus
|
|
646
674
|
opts["memory"] = Integer(memory) if memory
|
|
647
675
|
opts["max_memory"] = Integer(max_memory) if max_memory
|
|
676
|
+
opts["root_disk_size"] = Integer(root_disk_size) if root_disk_size
|
|
648
677
|
opts["env"] = stringify(env) if env
|
|
649
678
|
opts["remove_env"] = Array(remove_env).map(&:to_s) if remove_env
|
|
650
679
|
opts["labels"] = stringify(labels) if labels
|
|
@@ -831,6 +860,71 @@ module Microsandbox
|
|
|
831
860
|
end
|
|
832
861
|
|
|
833
862
|
# Normalize the `dns:` config Hash for the native layer.
|
|
863
|
+
# Normalize the `rate_limiter:` Hash (v0.6.9) — per-direction
|
|
864
|
+
# bandwidth/ops token buckets — for the native layer. Mirrors the Python
|
|
865
|
+
# SDK's `NetworkRateLimiter`/`RateLimiter`/`TokenBucket` shapes as plain
|
|
866
|
+
# Hashes: `{egress: {bandwidth: {size:, refill_time_ms:, one_time_burst:},
|
|
867
|
+
# ops: {...}}, ingress: {...}}`.
|
|
868
|
+
def normalize_rate_limiter(rl)
|
|
869
|
+
raise ArgumentError, "rate_limiter: must be a Hash" unless rl.is_a?(Hash)
|
|
870
|
+
out = {}
|
|
871
|
+
%i[egress ingress].each do |dir|
|
|
872
|
+
spec = fetch_opt(rl, dir)
|
|
873
|
+
next if spec.nil?
|
|
874
|
+
unless spec.is_a?(Hash)
|
|
875
|
+
raise ArgumentError, "rate_limiter #{dir}: must be a Hash"
|
|
876
|
+
end
|
|
877
|
+
dout = {}
|
|
878
|
+
%i[bandwidth ops].each do |dim|
|
|
879
|
+
bucket = fetch_opt(spec, dim)
|
|
880
|
+
next if bucket.nil?
|
|
881
|
+
unless bucket.is_a?(Hash)
|
|
882
|
+
raise ArgumentError, "rate_limiter #{dir} #{dim}: must be a Hash"
|
|
883
|
+
end
|
|
884
|
+
size = fetch_opt(bucket, :size)
|
|
885
|
+
refill = fetch_opt(bucket, :refill_time_ms)
|
|
886
|
+
if size.nil? || refill.nil?
|
|
887
|
+
raise ArgumentError,
|
|
888
|
+
"rate_limiter #{dir} #{dim}: requires size: and refill_time_ms:"
|
|
889
|
+
end
|
|
890
|
+
bout = {"size" => Integer(size), "refill_time_ms" => Integer(refill)}
|
|
891
|
+
burst = fetch_opt(bucket, :one_time_burst)
|
|
892
|
+
bout["one_time_burst"] = Integer(burst) if burst
|
|
893
|
+
dout[dim.to_s] = bout
|
|
894
|
+
end
|
|
895
|
+
out[dir.to_s] = dout
|
|
896
|
+
end
|
|
897
|
+
out
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
# Normalize the `vsock:` create option (v0.6.9) into the native array of
|
|
901
|
+
# route Hashes. Accepts the Python SDK's two shapes: a Hash of
|
|
902
|
+
# `{ host_socket => port }` (stream sockets), or an Array of
|
|
903
|
+
# `{host_socket:, port:, socket_type: :stream|:dgram}` Hashes.
|
|
904
|
+
def normalize_vsock(vsock)
|
|
905
|
+
if vsock.is_a?(Hash)
|
|
906
|
+
vsock.map { |path, port| {"host_socket" => path.to_s, "port" => Integer(port)} }
|
|
907
|
+
elsif vsock.is_a?(Array)
|
|
908
|
+
vsock.map do |route|
|
|
909
|
+
unless route.is_a?(Hash)
|
|
910
|
+
raise ArgumentError, "vsock: array entries must be Hashes {host_socket:, port:, socket_type:}"
|
|
911
|
+
end
|
|
912
|
+
path = fetch_opt(route, :host_socket)
|
|
913
|
+
port = fetch_opt(route, :port)
|
|
914
|
+
if path.nil? || port.nil?
|
|
915
|
+
raise ArgumentError, "vsock route requires host_socket: and port:"
|
|
916
|
+
end
|
|
917
|
+
out = {"host_socket" => path.to_s, "port" => Integer(port)}
|
|
918
|
+
st = fetch_opt(route, :socket_type)
|
|
919
|
+
out["socket_type"] = st.to_s if st
|
|
920
|
+
out
|
|
921
|
+
end
|
|
922
|
+
else
|
|
923
|
+
raise ArgumentError,
|
|
924
|
+
"vsock: must be a Hash of {host_socket => port} or an Array of route Hashes"
|
|
925
|
+
end
|
|
926
|
+
end
|
|
927
|
+
|
|
834
928
|
def normalize_dns(dns)
|
|
835
929
|
raise ArgumentError, "dns: must be a Hash" unless dns.is_a?(Hash)
|
|
836
930
|
out = {}
|
|
@@ -928,9 +1022,29 @@ module Microsandbox
|
|
|
928
1022
|
h["format"] = spec["format"].to_s if spec["format"]
|
|
929
1023
|
h["fstype"] = spec["fstype"].to_s if spec["fstype"]
|
|
930
1024
|
h
|
|
1025
|
+
when "flat"
|
|
1026
|
+
%w[path format].each do |key|
|
|
1027
|
+
if spec[key]
|
|
1028
|
+
raise ArgumentError, "root_disk #{key}: is only valid for the disk kind"
|
|
1029
|
+
end
|
|
1030
|
+
end
|
|
1031
|
+
h = {"kind" => "flat"}
|
|
1032
|
+
if spec["size_mib"]
|
|
1033
|
+
h["size_mib"] = coerce_root_disk_size(spec["size_mib"], "root_disk size_mib:")
|
|
1034
|
+
end
|
|
1035
|
+
h["fstype"] = spec["fstype"].to_s if spec["fstype"]
|
|
1036
|
+
if spec["clone"]
|
|
1037
|
+
clone = spec["clone"].to_s
|
|
1038
|
+
unless %w[auto copy reflink].include?(clone)
|
|
1039
|
+
raise ArgumentError,
|
|
1040
|
+
"unknown root_disk clone strategy #{clone.inspect} (expected auto/copy/reflink)"
|
|
1041
|
+
end
|
|
1042
|
+
h["clone"] = clone
|
|
1043
|
+
end
|
|
1044
|
+
h
|
|
931
1045
|
else
|
|
932
1046
|
raise ArgumentError,
|
|
933
|
-
"unknown root_disk kind #{kind.inspect} (expected managed/tmpfs/disk)"
|
|
1047
|
+
"unknown root_disk kind #{kind.inspect} (expected managed/tmpfs/disk/flat)"
|
|
934
1048
|
end
|
|
935
1049
|
end
|
|
936
1050
|
|
|
@@ -1116,6 +1230,21 @@ module Microsandbox
|
|
|
1116
1230
|
exec_opts(cwd:, user:, env:, timeout:, tty:, stdin:, rlimits:)))
|
|
1117
1231
|
end
|
|
1118
1232
|
|
|
1233
|
+
# Run the image's resolved OCI `ENTRYPOINT` and `CMD` — the **default
|
|
1234
|
+
# workload** (runtime v0.6.9) — and collect output. {Sandbox.create} is
|
|
1235
|
+
# strictly boot-only, so this is how the image's own command gets
|
|
1236
|
+
# executed; override the durable CMD at create time via `cmd:`.
|
|
1237
|
+
#
|
|
1238
|
+
# Options match {#exec} minus the command itself.
|
|
1239
|
+
# @return [ExecOutput]
|
|
1240
|
+
# @raise [NoDefaultCommandError] when the image's effective entrypoint and
|
|
1241
|
+
# CMD resolve to no executable command
|
|
1242
|
+
def exec_default(cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil)
|
|
1243
|
+
ExecOutput.new(@native.exec_default(
|
|
1244
|
+
exec_opts(cwd:, user:, env:, timeout:, tty:, stdin:, rlimits:)
|
|
1245
|
+
))
|
|
1246
|
+
end
|
|
1247
|
+
|
|
1119
1248
|
# Run a command and stream its output as it arrives.
|
|
1120
1249
|
#
|
|
1121
1250
|
# Pass +stdin: :pipe+ to feed the process interactively: {ExecHandle#stdin}
|
|
@@ -1142,6 +1271,17 @@ module Microsandbox
|
|
|
1142
1271
|
exec_opts(cwd:, user:, env:, timeout:, tty:, stdin:, rlimits:, pipe_ok: true)))
|
|
1143
1272
|
end
|
|
1144
1273
|
|
|
1274
|
+
# Run the default workload (see {#exec_default}) and stream its output.
|
|
1275
|
+
# @note Like {#exec_stream}, +timeout:+ is accepted but **not applied** on
|
|
1276
|
+
# the streaming path.
|
|
1277
|
+
# @return [ExecHandle]
|
|
1278
|
+
# @raise [NoDefaultCommandError] when no executable default command resolves
|
|
1279
|
+
def exec_default_stream(cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil)
|
|
1280
|
+
ExecHandle.new(@native.exec_default_stream(
|
|
1281
|
+
exec_opts(cwd:, user:, env:, timeout:, tty:, stdin:, rlimits:, pipe_ok: true)
|
|
1282
|
+
))
|
|
1283
|
+
end
|
|
1284
|
+
|
|
1145
1285
|
# Attach an interactive terminal to a command in the sandbox.
|
|
1146
1286
|
#
|
|
1147
1287
|
# Puts the **host** terminal into raw mode and forwards keystrokes (and
|
|
@@ -1181,6 +1321,32 @@ module Microsandbox
|
|
|
1181
1321
|
@native.attach_shell
|
|
1182
1322
|
end
|
|
1183
1323
|
|
|
1324
|
+
# Attach an interactive terminal to the image's resolved OCI `ENTRYPOINT`
|
|
1325
|
+
# and `CMD` — the default workload (runtime v0.6.9). See {#attach} for the
|
|
1326
|
+
# host-TTY requirements and {#exec_default} for default-workload semantics.
|
|
1327
|
+
#
|
|
1328
|
+
# @param cwd [String, nil] working directory
|
|
1329
|
+
# @param user [String, nil] user to run as
|
|
1330
|
+
# @param env [Hash, nil] extra environment variables
|
|
1331
|
+
# @param detach_keys [String, nil] detach sequence (default "ctrl-]")
|
|
1332
|
+
# @param rlimits [Hash, nil] resource limits (see {#exec})
|
|
1333
|
+
# @return [Integer] the workload's exit code (or the code at detach)
|
|
1334
|
+
# @raise [NoDefaultCommandError] when no executable default command resolves
|
|
1335
|
+
def attach_default(cwd: nil, user: nil, env: nil, detach_keys: nil, rlimits: nil)
|
|
1336
|
+
opts = {}
|
|
1337
|
+
opts["cwd"] = cwd.to_s if cwd
|
|
1338
|
+
opts["user"] = user.to_s if user
|
|
1339
|
+
opts["env"] = env.each_with_object({}) { |(k, v), a| a[k.to_s] = v.to_s } if env
|
|
1340
|
+
opts["detach_keys"] = detach_keys.to_s if detach_keys
|
|
1341
|
+
if rlimits
|
|
1342
|
+
opts["rlimits"] = rlimits.map do |resource, limit|
|
|
1343
|
+
soft, hard = limit.is_a?(Array) ? [limit[0], limit[1]] : [limit, limit]
|
|
1344
|
+
[resource.to_s, Integer(soft), Integer(hard)]
|
|
1345
|
+
end
|
|
1346
|
+
end
|
|
1347
|
+
@native.attach_default(opts)
|
|
1348
|
+
end
|
|
1349
|
+
|
|
1184
1350
|
# Guest filesystem operations.
|
|
1185
1351
|
# @return [FS]
|
|
1186
1352
|
def fs
|
|
@@ -1241,6 +1407,11 @@ module Microsandbox
|
|
|
1241
1407
|
# @param max_cpus [Integer, nil] desired boot-time maximum vCPU ceiling
|
|
1242
1408
|
# @param memory [Integer, nil] desired effective guest memory in MiB
|
|
1243
1409
|
# @param max_memory [Integer, nil] desired boot-time maximum memory (MiB)
|
|
1410
|
+
# @param root_disk_size [Integer, nil] desired root-disk size in MiB
|
|
1411
|
+
# (runtime v0.6.9): grows the sandbox-owned managed upper or flat root
|
|
1412
|
+
# disk. Growth-only, applied while the sandbox is stopped —
|
|
1413
|
+
# restart/next-start semantics and backing-specific limits are enforced
|
|
1414
|
+
# by the runtime
|
|
1244
1415
|
# @param env [Hash, nil] environment variables to set for future execs
|
|
1245
1416
|
# @param remove_env [Array<String>, nil] environment variable names to remove
|
|
1246
1417
|
# @param labels [Hash, nil] labels to set
|
|
@@ -1406,8 +1577,9 @@ module Microsandbox
|
|
|
1406
1577
|
when :pipe
|
|
1407
1578
|
unless pipe_ok
|
|
1408
1579
|
raise ArgumentError,
|
|
1409
|
-
"stdin: :pipe is only valid for
|
|
1410
|
-
"
|
|
1580
|
+
"stdin: :pipe is only valid for the streaming variants (exec_stream/" \
|
|
1581
|
+
"shell_stream/exec_default_stream) — a blocking exec cannot expose a " \
|
|
1582
|
+
"writable stdin sink; pass a String to feed bytes"
|
|
1411
1583
|
end
|
|
1412
1584
|
opts["stdin_pipe"] = true
|
|
1413
1585
|
when Symbol
|
|
@@ -115,19 +115,21 @@ module Microsandbox
|
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
# The result of {Snapshot.verify}.
|
|
119
|
-
#
|
|
120
|
-
#
|
|
118
|
+
# The result of {Snapshot.verify}. Since runtime v0.6.9 payload integrity is
|
|
119
|
+
# recorded only when the snapshot was created with `record_integrity: true`:
|
|
120
|
+
# such snapshots report `:verified` (an integrity mismatch raises
|
|
121
|
+
# {SnapshotIntegrityError} instead of returning), snapshots without recorded
|
|
122
|
+
# integrity report `:not_recorded` with `algorithm`/`content_digest` nil.
|
|
121
123
|
class SnapshotVerifyReport
|
|
122
124
|
# @return [String] descriptor digest
|
|
123
125
|
attr_reader :digest
|
|
124
126
|
# @return [String] artifact directory path
|
|
125
127
|
attr_reader :path
|
|
126
|
-
# @return [Symbol] :verified
|
|
128
|
+
# @return [Symbol] :verified or :not_recorded
|
|
127
129
|
attr_reader :status
|
|
128
|
-
# @return [String] digest algorithm
|
|
130
|
+
# @return [String, nil] digest algorithm (nil when :not_recorded)
|
|
129
131
|
attr_reader :algorithm
|
|
130
|
-
# @return [String] matched content digest
|
|
132
|
+
# @return [String, nil] matched content digest (nil when :not_recorded)
|
|
131
133
|
attr_reader :content_digest
|
|
132
134
|
|
|
133
135
|
def initialize(data)
|
|
@@ -161,8 +163,10 @@ module Microsandbox
|
|
|
161
163
|
# is written at `dest_dir/<name>` (default: the snapshots dir)
|
|
162
164
|
# @param labels [Hash, nil] user labels
|
|
163
165
|
# @param force [Boolean] overwrite an existing artifact at the destination
|
|
164
|
-
# @param record_integrity [Boolean]
|
|
165
|
-
#
|
|
166
|
+
# @param record_integrity [Boolean] record persistent payload integrity
|
|
167
|
+
# (a Merkle content digest) in the artifact — opt-in since runtime
|
|
168
|
+
# v0.6.9 because hashing large allocated uppers is expensive; without
|
|
169
|
+
# it {Snapshot.verify} reports `:not_recorded`
|
|
166
170
|
# @param resumable [Boolean] request a resumable (memory+device) snapshot;
|
|
167
171
|
# raises {UnsupportedError} until VM pause/resume lands upstream
|
|
168
172
|
# @return [SnapshotInfo]
|
data/lib/microsandbox/version.rb
CHANGED
|
@@ -8,12 +8,12 @@ module Microsandbox
|
|
|
8
8
|
# Versioning section of the README for the full gem-to-runtime map. Must equal
|
|
9
9
|
# the native ext's Cargo crate version (`Native.version`), enforced by
|
|
10
10
|
# spec/unit/version_spec.rb.
|
|
11
|
-
VERSION = "0.
|
|
11
|
+
VERSION = "0.13.0"
|
|
12
12
|
|
|
13
13
|
# The upstream microsandbox runtime release this gem build embeds — the `tag`
|
|
14
14
|
# pinned on the `microsandbox`/`microsandbox-network` git deps in
|
|
15
15
|
# ext/microsandbox/Cargo.toml. Exposed at runtime as
|
|
16
16
|
# {Microsandbox.runtime_version}. spec/unit/version_spec.rb asserts it stays in
|
|
17
17
|
# sync with the Cargo tag so it can't silently drift out of date.
|
|
18
|
-
RUNTIME_VERSION = "v0.6.
|
|
18
|
+
RUNTIME_VERSION = "v0.6.9"
|
|
19
19
|
end
|
data/lib/microsandbox/volume.rb
CHANGED
|
@@ -13,6 +13,7 @@ module Microsandbox
|
|
|
13
13
|
@name = data["name"]
|
|
14
14
|
@path = data["path"]
|
|
15
15
|
@kind = data["kind"]
|
|
16
|
+
@default = data["default"]
|
|
16
17
|
@quota_mib = data["quota_mib"]
|
|
17
18
|
@used_bytes = data["used_bytes"]
|
|
18
19
|
@capacity_bytes = data["capacity_bytes"]
|
|
@@ -28,6 +29,13 @@ module Microsandbox
|
|
|
28
29
|
@kind&.to_sym
|
|
29
30
|
end
|
|
30
31
|
|
|
32
|
+
# Whether this is the backend's default volume (runtime v0.6.9, see
|
|
33
|
+
# {Volume.get_default}).
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
def default?
|
|
36
|
+
!!@default
|
|
37
|
+
end
|
|
38
|
+
|
|
31
39
|
# @return [Time, nil]
|
|
32
40
|
def created_at
|
|
33
41
|
@created_at_ms && Time.at(@created_at_ms / 1000.0)
|
|
@@ -154,6 +162,14 @@ module Microsandbox
|
|
|
154
162
|
VolumeInfo.new(Native::Volume.get(name.to_s))
|
|
155
163
|
end
|
|
156
164
|
|
|
165
|
+
# The backend's default volume (runtime v0.6.9). Cloud backend only —
|
|
166
|
+
# the full {VolumeInfo#fs} surface works against it; the local backend
|
|
167
|
+
# raises {UnsupportedError} to avoid accidental host access.
|
|
168
|
+
# @return [VolumeInfo]
|
|
169
|
+
def get_default
|
|
170
|
+
VolumeInfo.new(Native::Volume.get_default)
|
|
171
|
+
end
|
|
172
|
+
|
|
157
173
|
# All volumes.
|
|
158
174
|
# @return [Array<VolumeInfo>]
|
|
159
175
|
def list
|
data/lib/microsandbox.rb
CHANGED
|
@@ -17,6 +17,7 @@ rescue LoadError
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
require_relative "microsandbox/errors"
|
|
20
|
+
require_relative "microsandbox/backend_info"
|
|
20
21
|
require_relative "microsandbox/exec_output"
|
|
21
22
|
require_relative "microsandbox/exec_handle"
|
|
22
23
|
require_relative "microsandbox/fs"
|
|
@@ -172,9 +173,13 @@ module Microsandbox
|
|
|
172
173
|
|
|
173
174
|
# Install a process-wide default backend (v0.5.8 backend routing). Without a
|
|
174
175
|
# call to this, operations use a local libkrun backend; the env/profile
|
|
175
|
-
# ladder (`MSB_BACKEND
|
|
176
|
-
#
|
|
177
|
-
#
|
|
176
|
+
# ladder (`MSB_BACKEND` → `MSB_PROFILE` → `~/.microsandbox/config.json`) is
|
|
177
|
+
# resolved lazily on first use. Since runtime v0.6.9 a bare `MSB_API_KEY`
|
|
178
|
+
# no longer selects the cloud — cloud intent must be explicit via
|
|
179
|
+
# `MSB_BACKEND=cloud` (paired with `MSB_API_URL`/`MSB_API_KEY`), a cloud
|
|
180
|
+
# profile, or this method; invalid cloud config raises
|
|
181
|
+
# {InvalidConfigError} instead of falling back to local. Call once at
|
|
182
|
+
# startup, before any sandbox operations.
|
|
178
183
|
#
|
|
179
184
|
# @param kind ["local","cloud", Symbol] backend kind
|
|
180
185
|
# @param url [String, nil] cloud control-plane URL (cloud, unless `profile:`)
|
|
@@ -216,6 +221,14 @@ module Microsandbox
|
|
|
216
221
|
Native.default_backend_kind.to_sym
|
|
217
222
|
end
|
|
218
223
|
|
|
224
|
+
# Secret-safe description of the active default backend (runtime v0.6.9).
|
|
225
|
+
# Like {default_backend_kind}, the first call freezes ambient env/profile
|
|
226
|
+
# resolution for the process. The API key is never included.
|
|
227
|
+
# @return [BackendInfo]
|
|
228
|
+
def default_backend_info
|
|
229
|
+
BackendInfo.new(Native.default_backend_info)
|
|
230
|
+
end
|
|
231
|
+
|
|
219
232
|
# Latest resource-usage snapshot for every running sandbox, keyed by name.
|
|
220
233
|
# Mirrors the official `all_sandbox_metrics`/`allSandboxMetrics` helpers.
|
|
221
234
|
# @return [Hash{String => Metrics}]
|
data/sig/microsandbox.rbs
CHANGED
|
@@ -27,6 +27,7 @@ module Microsandbox
|
|
|
27
27
|
def self.set_default_backend: (String | Symbol kind, ?url: String?, ?api_key: String?, ?profile: String?) -> void
|
|
28
28
|
def self.with_backend: [T] (String | Symbol kind, ?url: String?, ?api_key: String?, ?profile: String?) { () -> T } -> T
|
|
29
29
|
def self.default_backend_kind: () -> Symbol
|
|
30
|
+
def self.default_backend_info: () -> BackendInfo
|
|
30
31
|
|
|
31
32
|
class Error < StandardError
|
|
32
33
|
def self.code: () -> String
|
|
@@ -40,6 +41,7 @@ module Microsandbox
|
|
|
40
41
|
class SandboxStillRunningError < Error end
|
|
41
42
|
class ExecTimeoutError < Error end
|
|
42
43
|
class ExecFailedError < Error end
|
|
44
|
+
class NoDefaultCommandError < Error end
|
|
43
45
|
class FilesystemError < Error end
|
|
44
46
|
class PathNotFoundError < Error end
|
|
45
47
|
class VolumeNotFoundError < Error end
|
|
@@ -180,6 +182,7 @@ module Microsandbox
|
|
|
180
182
|
def ping: () -> PingResult
|
|
181
183
|
def touch: () -> TouchResult
|
|
182
184
|
def modify: (?cpus: Integer?, ?max_cpus: Integer?, ?memory: Integer?, ?max_memory: Integer?,
|
|
185
|
+
?root_disk_size: Integer?,
|
|
183
186
|
?env: Hash[untyped, untyped]?, ?remove_env: Array[String]?,
|
|
184
187
|
?labels: Hash[untyped, untyped]?, ?remove_labels: Array[String]?,
|
|
185
188
|
?workdir: String?, ?secrets: Hash[untyped, untyped]?,
|
|
@@ -245,11 +248,14 @@ module Microsandbox
|
|
|
245
248
|
?env: Hash[untyped, untyped]?, ?workdir: String?, ?shell: String?,
|
|
246
249
|
?user: String?, ?hostname: String?, ?labels: Hash[untyped, untyped]?,
|
|
247
250
|
?scripts: Hash[untyped, untyped]?, ?entrypoint: Array[String]?,
|
|
251
|
+
?cmd: Array[String]?,
|
|
248
252
|
?ports: Hash[untyped, untyped]?, ?ports_udp: Hash[untyped, untyped]?,
|
|
249
253
|
?volumes: Hash[untyped, untyped]?, ?network: untyped?,
|
|
250
254
|
?dns: Hash[untyped, untyped]?, ?tls: Hash[untyped, untyped]?,
|
|
251
255
|
?ipv4_pool: String?, ?ipv6_pool: String?,
|
|
252
256
|
?max_connections: Integer?, ?trust_host_cas: bool?,
|
|
257
|
+
?rate_limiter: Hash[untyped, untyped]?,
|
|
258
|
+
?vsock: (Hash[untyped, untyped] | Array[Hash[untyped, untyped]])?,
|
|
253
259
|
?patches: Array[Hash[untyped, untyped]]?, ?from_snapshot: String?,
|
|
254
260
|
?fstype: String?, ?init: (String | Symbol | Hash[untyped, untyped])?, ?ephemeral: bool,
|
|
255
261
|
?log_level: (String | Symbol)?, ?quiet_logs: bool, ?security: (String | Symbol)?,
|
|
@@ -282,16 +288,25 @@ module Microsandbox
|
|
|
282
288
|
?rlimits: Hash[untyped, untyped]?) -> ExecHandle
|
|
283
289
|
def shell_stream: (String script, ?cwd: String?, ?user: String?, ?env: Hash[untyped, untyped]?,
|
|
284
290
|
?timeout: Numeric?, ?tty: bool, ?stdin: (String | :pipe | :null)?, ?rlimits: Hash[untyped, untyped]?) -> ExecHandle
|
|
291
|
+
def exec_default: (?cwd: String?, ?user: String?, ?env: Hash[untyped, untyped]?,
|
|
292
|
+
?timeout: Numeric?, ?tty: bool, ?stdin: (String | :null)?,
|
|
293
|
+
?rlimits: Hash[untyped, untyped]?) -> ExecOutput
|
|
294
|
+
def exec_default_stream: (?cwd: String?, ?user: String?, ?env: Hash[untyped, untyped]?,
|
|
295
|
+
?timeout: Numeric?, ?tty: bool, ?stdin: (String | :pipe | :null)?,
|
|
296
|
+
?rlimits: Hash[untyped, untyped]?) -> ExecHandle
|
|
285
297
|
def attach: (String command, ?Array[String] args, ?cwd: String?, ?user: String?,
|
|
286
298
|
?env: Hash[untyped, untyped]?, ?detach_keys: String?,
|
|
287
299
|
?rlimits: Hash[untyped, untyped]?) -> Integer
|
|
288
300
|
def attach_shell: () -> Integer
|
|
301
|
+
def attach_default: (?cwd: String?, ?user: String?, ?env: Hash[untyped, untyped]?,
|
|
302
|
+
?detach_keys: String?, ?rlimits: Hash[untyped, untyped]?) -> Integer
|
|
289
303
|
def fs: () -> FS
|
|
290
304
|
def ssh: () -> SshOps
|
|
291
305
|
def metrics: () -> Metrics
|
|
292
306
|
def ping: () -> PingResult
|
|
293
307
|
def touch: () -> TouchResult
|
|
294
308
|
def modify: (?cpus: Integer?, ?max_cpus: Integer?, ?memory: Integer?, ?max_memory: Integer?,
|
|
309
|
+
?root_disk_size: Integer?,
|
|
295
310
|
?env: Hash[untyped, untyped]?, ?remove_env: Array[String]?,
|
|
296
311
|
?labels: Hash[untyped, untyped]?, ?remove_labels: Array[String]?,
|
|
297
312
|
?workdir: String?, ?secrets: Hash[untyped, untyped]?,
|
|
@@ -412,6 +427,7 @@ module Microsandbox
|
|
|
412
427
|
def name: () -> String
|
|
413
428
|
def path: () -> String?
|
|
414
429
|
def kind: () -> Symbol?
|
|
430
|
+
def default?: () -> bool
|
|
415
431
|
def quota_mib: () -> Integer?
|
|
416
432
|
def used_bytes: () -> Integer?
|
|
417
433
|
def capacity_bytes: () -> Integer?
|
|
@@ -441,6 +457,7 @@ module Microsandbox
|
|
|
441
457
|
def self.create: (String name, ?kind: String, ?size_mib: Integer?, ?quota_mib: Integer?,
|
|
442
458
|
?labels: Hash[untyped, untyped]?) -> VolumeInfo
|
|
443
459
|
def self.get: (String name) -> VolumeInfo
|
|
460
|
+
def self.get_default: () -> VolumeInfo
|
|
444
461
|
def self.list: () -> Array[VolumeInfo]
|
|
445
462
|
def self.remove: (String name) -> nil
|
|
446
463
|
def self.fs: (String name) -> VolumeFs
|
|
@@ -475,8 +492,8 @@ module Microsandbox
|
|
|
475
492
|
def digest: () -> String
|
|
476
493
|
def path: () -> String
|
|
477
494
|
def status: () -> Symbol
|
|
478
|
-
def algorithm: () -> String
|
|
479
|
-
def content_digest: () -> String
|
|
495
|
+
def algorithm: () -> String?
|
|
496
|
+
def content_digest: () -> String?
|
|
480
497
|
def verified?: () -> bool
|
|
481
498
|
end
|
|
482
499
|
|
|
@@ -531,6 +548,16 @@ module Microsandbox
|
|
|
531
548
|
def self.managed: (?Integer? size_mib) -> Hash[String, untyped]
|
|
532
549
|
def self.tmpfs: (?Integer? size_mib) -> Hash[String, untyped]
|
|
533
550
|
def self.disk: (String path, ?format: (String | Symbol)?, ?fstype: String?) -> Hash[String, untyped]
|
|
551
|
+
def self.flat: (?Integer? size_mib, ?fstype: String?, ?clone: (String | Symbol)?) -> Hash[String, untyped]
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
class BackendInfo
|
|
555
|
+
def kind: () -> Symbol
|
|
556
|
+
def api_url: () -> String?
|
|
557
|
+
def source: () -> Symbol
|
|
558
|
+
def profile: () -> String?
|
|
559
|
+
def local?: () -> bool
|
|
560
|
+
def cloud?: () -> bool
|
|
534
561
|
end
|
|
535
562
|
|
|
536
563
|
class NetworkPolicy
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: microsandbox-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ya-luotao
|
|
@@ -61,6 +61,7 @@ files:
|
|
|
61
61
|
- ext/microsandbox/src/volume.rs
|
|
62
62
|
- lib/microsandbox.rb
|
|
63
63
|
- lib/microsandbox/agent.rb
|
|
64
|
+
- lib/microsandbox/backend_info.rb
|
|
64
65
|
- lib/microsandbox/errors.rb
|
|
65
66
|
- lib/microsandbox/exec_handle.rb
|
|
66
67
|
- lib/microsandbox/exec_output.rb
|