Class: Microsandbox::Sandbox
- Inherits:
-
Object
- Object
- Microsandbox::Sandbox
- Defined in:
- lib/microsandbox/sandbox.rb
Overview
A running sandbox (microVM) — the primary entry point of the SDK.
Constant Summary collapse
- DISK_IMAGE_EXTENSIONS =
Recognized disk-image rootfs extensions, mirroring the upstream
DiskImageFormat::from_extension/FromStrset. Used by disk_image_rootfs? to gate thefstype:-vs-OCI check; keep in sync on a runtime-tag bump. %w[raw qcow2 vmdk].freeze
Class Method Summary collapse
-
.build_create_opts(image: nil, cpus: nil, memory: nil, env: nil, workdir: nil, shell: nil, user: nil, hostname: nil, labels: nil, scripts: nil, entrypoint: nil, ports: nil, ports_udp: nil, volumes: nil, network: nil, dns: nil, tls: nil, ipv4_pool: nil, ipv6_pool: nil, max_connections: nil, trust_host_cas: nil, patches: nil, from_snapshot: nil, fstype: nil, init: nil, ephemeral: false, log_level: nil, quiet_logs: false, security: nil, oci_upper_size: nil, max_duration: nil, idle_timeout: nil, rlimits: nil, pull_policy: nil, registry_auth: nil, registry_insecure: false, registry_ca_certs: nil, secrets: nil, on_secret_violation: nil, detached: false, replace: false, replace_with_timeout: nil) ⇒ Object
private
Shared keyword-option builder for Sandbox.create/Sandbox.create_with_progress.
-
.create(name, **kwargs) {|sandbox| ... } ⇒ Sandbox, Object
Create and boot a sandbox.
-
.create_with_progress(name, **kwargs) ⇒ PullSession
Create a sandbox while streaming image-pull progress.
-
.get(name) ⇒ SandboxHandle
Fetch a controllable handle for a sandbox by name (running or not).
-
.list ⇒ Array<SandboxHandle>
List all sandboxes as controllable handles.
-
.list_with(labels: {}) ⇒ Array<SandboxHandle>
List sandboxes carrying all of the given labels (AND-matched).
-
.remove(name) ⇒ nil
Remove a (stopped) sandbox by name.
-
.start(name, detached: false) ⇒ Sandbox
Restart a previously-defined sandbox by name.
Instance Method Summary collapse
-
#attach(command, args = [], cwd: nil, user: nil, env: nil, detach_keys: nil, rlimits: nil) ⇒ Integer
Attach an interactive terminal to a command in the sandbox.
-
#attach_shell ⇒ Integer
Attach an interactive terminal running the sandbox's default shell.
-
#detach ⇒ nil
Detach this handle: disarm the stop-on-drop safety net so the sandbox keeps running after this handle is gone (and after this process exits).
-
#drain ⇒ nil
Trigger a graceful drain (SIGUSR1).
-
#exec(command, args = [], cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ⇒ ExecOutput
Run a command (no shell interpretation) and collect its output.
-
#exec_stream(command, args = [], cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ⇒ ExecHandle
Run a command and stream its output as it arrives.
-
#fs ⇒ FS
Guest filesystem operations.
-
#initialize(native) ⇒ Sandbox
constructor
A new instance of Sandbox.
- #inspect ⇒ Object
-
#kill ⇒ nil
Force-kill the sandbox (SIGKILL).
-
#log_stream(sources: nil, since_ms: nil, from_cursor: nil, until_ms: nil, follow: false) ⇒ LogStream
Stream captured logs as they appear.
-
#logs(tail: nil, since_ms: nil, until_ms: nil, sources: nil) ⇒ Array<LogEntry>
Read captured logs.
-
#metrics ⇒ Metrics
Latest resource-usage snapshot.
-
#metrics_stream(interval: 1.0) ⇒ MetricsStream
Stream resource-usage snapshots, one per interval tick, until the sandbox stops.
-
#name ⇒ String
The sandbox name.
-
#owns_lifecycle? ⇒ Boolean
Whether this handle owns the sandbox process lifecycle (i.e. stopping it or dropping the handle terminates the sandbox).
-
#shell(script, cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ⇒ ExecOutput
Run a shell script (pipes, redirects, etc. allowed) and collect output.
-
#shell_stream(script, cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ⇒ ExecHandle
Run a shell script and stream its output as it arrives.
-
#ssh ⇒ SshOps
SSH access to the sandbox — open a native in-process SSH client or prepare a reusable server endpoint.
-
#status ⇒ Symbol
The live status, fetched from the backend (a round-trip per call).
-
#stop ⇒ nil
Gracefully stop the sandbox (SIGTERM→SIGKILL escalation, 10s default) and wait for it to terminate.
-
#stop_and_wait ⇒ ExitStatus
Gracefully stop, then wait for the process to exit.
-
#wait ⇒ ExitStatus
Wait for the sandbox process to exit.
Constructor Details
#initialize(native) ⇒ Sandbox
Returns a new instance of Sandbox.
812 813 814 |
# File 'lib/microsandbox/sandbox.rb', line 812 def initialize(native) @native = native end |
Class Method Details
.build_create_opts(image: nil, cpus: nil, memory: nil, env: nil, workdir: nil, shell: nil, user: nil, hostname: nil, labels: nil, scripts: nil, entrypoint: nil, ports: nil, ports_udp: nil, volumes: nil, network: nil, dns: nil, tls: nil, ipv4_pool: nil, ipv6_pool: nil, max_connections: nil, trust_host_cas: nil, patches: nil, from_snapshot: nil, fstype: nil, init: nil, ephemeral: false, log_level: nil, quiet_logs: false, security: nil, oci_upper_size: nil, max_duration: nil, idle_timeout: nil, rlimits: nil, pull_policy: nil, registry_auth: nil, registry_insecure: false, registry_ca_certs: nil, secrets: nil, on_secret_violation: nil, detached: false, replace: false, replace_with_timeout: nil) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Shared keyword-option builder for create/create_with_progress.
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 |
# File 'lib/microsandbox/sandbox.rb', line 337 def build_create_opts(image: nil, cpus: nil, memory: nil, env: nil, workdir: nil, shell: nil, user: nil, hostname: nil, labels: nil, scripts: nil, entrypoint: nil, ports: nil, ports_udp: nil, volumes: nil, network: nil, dns: nil, tls: nil, ipv4_pool: nil, ipv6_pool: nil, max_connections: nil, trust_host_cas: nil, patches: nil, from_snapshot: nil, fstype: nil, init: nil, ephemeral: false, log_level: nil, quiet_logs: false, security: nil, oci_upper_size: nil, max_duration: nil, idle_timeout: nil, rlimits: nil, pull_policy: nil, registry_auth: nil, registry_insecure: false, registry_ca_certs: nil, secrets: nil, on_secret_violation: nil, detached: false, replace: false, replace_with_timeout: nil) # A sandbox boots from exactly one rootfs source. The core would reject a # contradictory pair, but only after a runtime round-trip; fail fast and # clearly here (the Python SDK validates this the same way). if image && from_snapshot raise ArgumentError, "provide either image: or from_snapshot:, not both" end Microsandbox.ensure_runtime! # `fstype:` names the inner filesystem of a disk-image rootfs, so it only # applies when `image:` is a disk-image path (a local path ending in # .raw/.qcow2/.vmdk). Routing an OCI ref (e.g. "python") through the # disk-image builder would make the core treat it as a host disk path and # fail at boot, so reject the combination up front instead of forwarding a # value the native layer can't honour. if fstype && !disk_image_rootfs?(image) raise ArgumentError, "fstype: only applies to a disk-image rootfs; image: must be a local " \ "path ending in .raw, .qcow2, or .vmdk (got #{image.inspect}). " \ "OCI references auto-detect their filesystem — drop fstype:." end opts = {} opts["image"] = image.to_s if image opts["from_snapshot"] = from_snapshot.to_s if from_snapshot opts["fstype"] = fstype.to_s if fstype opts["cpus"] = Integer(cpus) if cpus opts["memory"] = Integer(memory) if memory opts["workdir"] = workdir.to_s if workdir opts["shell"] = shell.to_s if shell opts["user"] = user.to_s if user opts["hostname"] = hostname.to_s if hostname opts["env"] = stringify(env) if env opts["labels"] = stringify(labels) if labels opts["scripts"] = stringify(scripts) if scripts opts["entrypoint"] = Array(entrypoint).map(&:to_s) if entrypoint opts["ports"] = intify_ports(ports) if ports opts["ports_udp"] = intify_ports(ports_udp) if ports_udp opts["volumes"] = normalize_volumes(volumes) if volumes opts["patches"] = normalize_patches(patches) if patches apply_network_opts(opts, network) unless network.nil? opts["dns"] = normalize_dns(dns) if dns opts["tls"] = normalize_tls(tls) if tls opts["ipv4_pool"] = ipv4_pool.to_s if ipv4_pool opts["ipv6_pool"] = ipv6_pool.to_s if ipv6_pool opts["max_connections"] = Integer(max_connections) if max_connections set_bool(opts, "trust_host_cas", trust_host_cas) opts["log_level"] = log_level.to_s if log_level opts["quiet_logs"] = true if quiet_logs opts["security"] = security.to_s if security opts["oci_upper_size"] = Integer(oci_upper_size) if oci_upper_size opts["max_duration"] = Integer(max_duration) if max_duration opts["idle_timeout"] = Integer(idle_timeout) if idle_timeout opts["rlimits"] = normalize_rlimits(rlimits) if rlimits opts["pull_policy"] = pull_policy.to_s if pull_policy apply_registry_opts(opts, registry_auth, registry_insecure, registry_ca_certs) opts["secrets"] = normalize_secrets(secrets) if secrets opts["on_secret_violation"] = normalize_violation(on_secret_violation) if on_secret_violation opts["init"] = normalize_init(init) unless init.nil? opts["ephemeral"] = true if ephemeral opts["detached"] = true if detached if replace_with_timeout opts["replace_with_timeout"] = coerce_duration(replace_with_timeout, "replace_with_timeout") elsif replace opts["replace"] = true end opts end |
.create(name, **kwargs) {|sandbox| ... } ⇒ Sandbox, Object
Create and boot a sandbox.
When a block is given the sandbox is yielded and stopped automatically when the block returns (the block's value is returned); otherwise the live Microsandbox::Sandbox is returned and you are responsible for calling #stop.
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/microsandbox/sandbox.rb', line 299 def create(name, **kwargs, &block) opts = build_create_opts(**kwargs) sandbox = new(Native::Sandbox.create(name.to_s, opts)) return sandbox unless block_given? begin yield sandbox ensure begin sandbox.stop rescue Microsandbox::Error # best-effort cleanup; ignore stop failures during teardown end end end |
.create_with_progress(name, **kwargs) ⇒ PullSession
Create a sandbox while streaming image-pull progress. Accepts the same
options as create; returns a PullSession — iterate it (an
Enumerable of progress-event Hashes, each with a "kind"), then call
PullSession#sandbox for the booted Microsandbox::Sandbox. Mirrors the Python
create_with_progress / Node createWithPullProgress.
321 322 323 324 325 326 327 328 329 330 331 332 333 |
# File 'lib/microsandbox/sandbox.rb', line 321 def create_with_progress(name, **kwargs) # Unlike {create}, this has no block form: the booted sandbox is reached # via {PullSession#sandbox} (after iterating progress) and stopped by the # caller. A block would be silently dropped — and the sandbox leaked — so # reject it loudly rather than let a `create`-style block call misfire. if block_given? raise ArgumentError, "create_with_progress takes no block; iterate the returned PullSession " \ "for progress, then call #sandbox and stop it when done" end opts = build_create_opts(**kwargs) PullSession.new(Native::Sandbox.create_with_progress(name.to_s, opts)) end |
.get(name) ⇒ SandboxHandle
Fetch a controllable handle for a sandbox by name (running or not).
425 426 427 |
# File 'lib/microsandbox/sandbox.rb', line 425 def get(name) SandboxHandle.new(Native::Sandbox.get(name.to_s)) end |
.list ⇒ Array<SandboxHandle>
List all sandboxes as controllable handles.
431 432 433 |
# File 'lib/microsandbox/sandbox.rb', line 431 def list Native::Sandbox.list.map { |h| SandboxHandle.new(h) } end |
.list_with(labels: {}) ⇒ Array<SandboxHandle>
List sandboxes carrying all of the given labels (AND-matched).
438 439 440 441 |
# File 'lib/microsandbox/sandbox.rb', line 438 def list_with(labels: {}) opts = {"labels" => stringify(labels)} Native::Sandbox.list_with(opts).map { |h| SandboxHandle.new(h) } end |
.remove(name) ⇒ nil
Remove a (stopped) sandbox by name.
445 446 447 448 |
# File 'lib/microsandbox/sandbox.rb', line 445 def remove(name) Native::Sandbox.remove(name.to_s) nil end |
.start(name, detached: false) ⇒ Sandbox
Restart a previously-defined sandbox by name.
418 419 420 421 |
# File 'lib/microsandbox/sandbox.rb', line 418 def start(name, detached: false) Microsandbox.ensure_runtime! new(Native::Sandbox.start(name.to_s, {"detached" => detached})) end |
Instance Method Details
#attach(command, args = [], cwd: nil, user: nil, env: nil, detach_keys: nil, rlimits: nil) ⇒ Integer
Attach an interactive terminal to a command in the sandbox.
Puts the host terminal into raw mode and forwards keystrokes (and
SIGWINCH resizes) to the guest until the command exits or the detach
sequence is typed. Requires a real TTY on stdin/stdout, so it is for CLI
use, not library/automation code (use #exec/#exec_stream there). Blocks
until the session ends. Mirrors the official SDKs' attach.
893 894 895 896 897 898 899 900 901 902 903 904 905 906 |
# File 'lib/microsandbox/sandbox.rb', line 893 def attach(command, args = [], cwd: nil, user: nil, env: nil, detach_keys: nil, rlimits: nil) opts = {} opts["cwd"] = cwd.to_s if cwd opts["user"] = user.to_s if user opts["env"] = env.each_with_object({}) { |(k, v), a| a[k.to_s] = v.to_s } if env opts["detach_keys"] = detach_keys.to_s if detach_keys if rlimits opts["rlimits"] = rlimits.map do |resource, limit| soft, hard = limit.is_a?(Array) ? [limit[0], limit[1]] : [limit, limit] [resource.to_s, Integer(soft), Integer(hard)] end end @native.attach(command.to_s, Array(args).map(&:to_s), opts) end |
#attach_shell ⇒ Integer
Attach an interactive terminal running the sandbox's default shell. See #attach for the host-TTY requirements.
911 912 913 |
# File 'lib/microsandbox/sandbox.rb', line 911 def attach_shell @native.attach_shell end |
#detach ⇒ nil
Detach this handle: disarm the stop-on-drop safety net so the sandbox keeps running after this handle is gone (and after this process exits).
1048 1049 1050 1051 |
# File 'lib/microsandbox/sandbox.rb', line 1048 def detach @native.detach nil end |
#drain ⇒ nil
Trigger a graceful drain (SIGUSR1).
1021 1022 1023 1024 |
# File 'lib/microsandbox/sandbox.rb', line 1021 def drain @native.drain nil end |
#exec(command, args = [], cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ⇒ ExecOutput
Run a command (no shell interpretation) and collect its output.
838 839 840 841 |
# File 'lib/microsandbox/sandbox.rb', line 838 def exec(command, args = [], cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ExecOutput.new(@native.exec(command.to_s, Array(args).map(&:to_s), exec_opts(cwd:, user:, env:, timeout:, tty:, stdin:, rlimits:))) end |
#exec_stream(command, args = [], cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ⇒ ExecHandle
Run a command and stream its output as it arrives.
Pass stdin: :pipe to feed the process interactively: ExecHandle#stdin
then returns a writable sink; close it to send EOF (a process like cat
that reads until EOF will otherwise block forever).
862 863 864 865 |
# File 'lib/microsandbox/sandbox.rb', line 862 def exec_stream(command, args = [], cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ExecHandle.new(@native.exec_stream(command.to_s, Array(args).map(&:to_s), exec_opts(cwd:, user:, env:, timeout:, tty:, stdin:, rlimits:, pipe_ok: true))) end |
#fs ⇒ FS
Guest filesystem operations.
917 918 919 |
# File 'lib/microsandbox/sandbox.rb', line 917 def fs @fs ||= FS.new(@native) end |
#inspect ⇒ Object
1053 1054 1055 |
# File 'lib/microsandbox/sandbox.rb', line 1053 def inspect "#<Microsandbox::Sandbox name=#{name.inspect}>" end |
#kill ⇒ nil
Force-kill the sandbox (SIGKILL).
1014 1015 1016 1017 |
# File 'lib/microsandbox/sandbox.rb', line 1014 def kill @native.kill nil end |
#log_stream(sources: nil, since_ms: nil, from_cursor: nil, until_ms: nil, follow: false) ⇒ LogStream
Stream captured logs as they appear.
987 988 989 990 991 992 993 994 995 |
# File 'lib/microsandbox/sandbox.rb', line 987 def log_stream(sources: nil, since_ms: nil, from_cursor: nil, until_ms: nil, follow: false) opts = {} opts["sources"] = Array(sources).map(&:to_s) if sources opts["since_ms"] = Float(since_ms) if since_ms opts["from_cursor"] = from_cursor.to_s if from_cursor opts["until_ms"] = Float(until_ms) if until_ms opts["follow"] = true if follow LogStream.new(@native.log_stream(opts)) end |
#logs(tail: nil, since_ms: nil, until_ms: nil, sources: nil) ⇒ Array<LogEntry>
Read captured logs.
952 953 954 955 956 957 958 959 |
# File 'lib/microsandbox/sandbox.rb', line 952 def logs(tail: nil, since_ms: nil, until_ms: nil, sources: nil) opts = {} opts["tail"] = Integer(tail) if tail opts["since_ms"] = Float(since_ms) if since_ms opts["until_ms"] = Float(until_ms) if until_ms opts["sources"] = Array(sources).map(&:to_s) if sources @native.logs(opts).map { |entry| LogEntry.new(entry) } end |
#metrics ⇒ Metrics
Latest resource-usage snapshot.
Raises a Error ("sandbox N has no live metrics slot") when
called in the brief window right after create returns, before the
runtime has registered the sandbox's metrics slot. On the v0.6.1 runtime
the spawn handshake no longer blocks create until the first sample is
written, so the slot goes live a beat after boot (within a few hundred
milliseconds); retry for that window rather than treating the first failure
as fatal.
940 941 942 |
# File 'lib/microsandbox/sandbox.rb', line 940 def metrics Metrics.new(@native.metrics) end |
#metrics_stream(interval: 1.0) ⇒ MetricsStream
Stream resource-usage snapshots, one per interval tick, until the sandbox stops. Requires metrics to be enabled for the sandbox.
The first tick fires immediately, so opening the stream right after create can hit the same metrics-slot startup window as #metrics and yield a transient "no live metrics slot" error on that first tick. Because the stream is single-pass (a drained or errored stream is spent), make sure the slot is live before opening it — e.g. retry #metrics until it succeeds (the slot goes live within a few hundred milliseconds of boot), then call #metrics_stream.
973 974 975 |
# File 'lib/microsandbox/sandbox.rb', line 973 def metrics_stream(interval: 1.0) MetricsStream.new(@native.metrics_stream(coerce_duration(interval, "interval"))) end |
#name ⇒ String
Returns the sandbox name.
817 818 819 |
# File 'lib/microsandbox/sandbox.rb', line 817 def name @native.name end |
#owns_lifecycle? ⇒ Boolean
Returns whether this handle owns the sandbox process lifecycle (i.e. stopping it or dropping the handle terminates the sandbox).
1041 1042 1043 |
# File 'lib/microsandbox/sandbox.rb', line 1041 def owns_lifecycle? @native.owns_lifecycle end |
#shell(script, cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ⇒ ExecOutput
Run a shell script (pipes, redirects, etc. allowed) and collect output.
845 846 847 848 |
# File 'lib/microsandbox/sandbox.rb', line 845 def shell(script, cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ExecOutput.new(@native.shell(script.to_s, exec_opts(cwd:, user:, env:, timeout:, tty:, stdin:, rlimits:))) end |
#shell_stream(script, cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ⇒ ExecHandle
Like #exec_stream, timeout: is accepted but not applied on
the streaming path.
Run a shell script and stream its output as it arrives.
871 872 873 874 |
# File 'lib/microsandbox/sandbox.rb', line 871 def shell_stream(script, cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ExecHandle.new(@native.shell_stream(script.to_s, exec_opts(cwd:, user:, env:, timeout:, tty:, stdin:, rlimits:, pipe_ok: true))) end |
#ssh ⇒ SshOps
SSH access to the sandbox — open a native in-process SSH client or prepare a reusable server endpoint.
926 927 928 |
# File 'lib/microsandbox/sandbox.rb', line 926 def ssh SshOps.new(@native) end |
#status ⇒ Symbol
The live status, fetched from the backend (a round-trip per call).
1035 1036 1037 |
# File 'lib/microsandbox/sandbox.rb', line 1035 def status @native.status.to_sym end |
#stop ⇒ nil
Gracefully stop the sandbox (SIGTERM→SIGKILL escalation, 10s default) and
wait for it to terminate. For a custom timeout or fire-and-return
request_* control, fetch a Microsandbox::SandboxHandle via get.
1001 1002 1003 1004 |
# File 'lib/microsandbox/sandbox.rb', line 1001 def stop @native.stop nil end |
#stop_and_wait ⇒ ExitStatus
Gracefully stop, then wait for the process to exit.
1008 1009 1010 |
# File 'lib/microsandbox/sandbox.rb', line 1008 def stop_and_wait ExitStatus.new(@native.stop_and_wait) end |
#wait ⇒ ExitStatus
Wait for the sandbox process to exit.
1028 1029 1030 |
# File 'lib/microsandbox/sandbox.rb', line 1028 def wait ExitStatus.new(@native.wait) end |