Class: Microsandbox::Sandbox

Inherits:
Object
  • Object
show all
Defined in:
lib/microsandbox/sandbox.rb

Overview

A running sandbox (microVM) — the primary entry point of the SDK.

Examples:

Block form (auto-stops on exit)

Microsandbox::Sandbox.create("hello", image: "python") do |sb|
  out = sb.exec("python", ["-c", "print('hi')"])
  puts out.stdout
end

Manual lifecycle

sb = Microsandbox::Sandbox.create("hello", image: "python")
begin
  sb.shell("echo hi")
ensure
  sb.stop
end

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(native) ⇒ Sandbox

Returns a new instance of Sandbox.



307
308
309
# File 'lib/microsandbox/sandbox.rb', line 307

def initialize(native)
  @native = native
end

Class Method Details

.create(name, 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, from_snapshot: nil, 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, detached: false, replace: false, replace_with_timeout: nil) {|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.

Parameters:

  • name (String)

    sandbox name (max 128 UTF-8 bytes)

  • image (String, nil) (defaults to: nil)

    OCI image reference (e.g. “python”)

  • cpus (Integer, nil) (defaults to: nil)

    number of vCPUs

  • memory (Integer, nil) (defaults to: nil)

    memory in MiB

  • env (Hash, nil) (defaults to: nil)

    environment variables

  • workdir (String, nil) (defaults to: nil)

    working directory inside the guest

  • shell (String, nil) (defaults to: nil)

    default shell (for #shell)

  • user (String, nil) (defaults to: nil)

    default user

  • hostname (String, nil) (defaults to: nil)

    guest hostname

  • labels (Hash, nil) (defaults to: nil)

    metadata labels

  • scripts (Hash, nil) (defaults to: nil)

    named scripts to install

  • entrypoint (Array<String>, nil) (defaults to: nil)

    image entrypoint override

  • ports (Hash, nil) (defaults to: nil)

    host_port => guest_port TCP publications

  • ports_udp (Hash, nil) (defaults to: nil)

    host_port => guest_port UDP publications

  • network ("public_only", "none", "allow_all", "non_local", nil) (defaults to: nil)

    network policy preset (default public_only)

  • log_level ("error", "warn", "info", "debug", "trace", nil) (defaults to: nil)

    guest log verbosity

  • quiet_logs (Boolean) (defaults to: false)

    suppress sandbox process logs

  • security ("default", "restricted", nil) (defaults to: nil)

    exec security profile

  • oci_upper_size (Integer, nil) (defaults to: nil)

    writable upper-layer size cap, in MiB

  • max_duration (Integer, nil) (defaults to: nil)

    hard wall-clock lifetime, in seconds

  • idle_timeout (Integer, nil) (defaults to: nil)

    stop after this many idle seconds

  • rlimits (Hash, nil) (defaults to: nil)

    resource limits: { resource => limit } or { resource => [soft, hard] } (e.g. { nofile: 65_535 })

  • pull_policy ("always", "if-missing", "never", nil) (defaults to: nil)

    image pull behavior

  • registry_auth (Hash, nil) (defaults to: nil)

    credentials for a private/authenticated registry: { username:, password: } (the password may be a token). Without this the core’s default resolution chain still applies (OS keyring, global config, ‘~/.docker/config.json`).

  • registry_insecure (Boolean) (defaults to: false)

    reach the registry over plain HTTP instead of HTTPS (for local/self-hosted registries)

  • registry_ca_certs (String, Array<String>, nil) (defaults to: nil)

    extra PEM-encoded CA root certificate(s) to trust (for a registry with a private CA)

  • secrets (Array<Hash>, nil) (defaults to: nil)

    placeholder-protected secrets, each { env:, value:, host: } — the value is substituted by the TLS proxy only for the allowed host (auto-enables TLS interception)

  • detached (Boolean) (defaults to: false)

    keep running after this process exits

  • replace (Boolean) (defaults to: false)

    replace an existing sandbox with the same name

  • replace_with_timeout (Numeric, nil) (defaults to: nil)

    replace, waiting up to N seconds

Yield Parameters:

Returns:

  • (Sandbox, Object)

    the sandbox, or the block’s return value



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/microsandbox/sandbox.rb', line 138

def create(name,
           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,
           from_snapshot: nil, 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,
           detached: false, replace: false, replace_with_timeout: nil)
  Microsandbox.ensure_runtime!
  opts = {}
  opts["image"] = image.to_s if image
  opts["from_snapshot"] = from_snapshot.to_s if from_snapshot
  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["network"] = network.to_s if network
  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["detached"] = true if detached
  if replace_with_timeout
    opts["replace_with_timeout"] = Float(replace_with_timeout)
  elsif replace
    opts["replace"] = true
  end

  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

.get(name) ⇒ SandboxInfo

Fetch metadata for a sandbox by name.

Returns:



205
206
207
# File 'lib/microsandbox/sandbox.rb', line 205

def get(name)
  SandboxInfo.new(Native::Sandbox.get(name.to_s))
end

.listArray<SandboxInfo>

List all sandboxes.

Returns:



211
212
213
# File 'lib/microsandbox/sandbox.rb', line 211

def list
  Native::Sandbox.list.map { |info| SandboxInfo.new(info) }
end

.list_with(labels: {}) ⇒ Array<SandboxInfo>

List sandboxes carrying all of the given labels (AND-matched).

Parameters:

  • labels (Hash) (defaults to: {})

    required key => value labels

Returns:



218
219
220
221
# File 'lib/microsandbox/sandbox.rb', line 218

def list_with(labels: {})
  opts = { "labels" => stringify(labels) }
  Native::Sandbox.list_with(opts).map { |info| SandboxInfo.new(info) }
end

.remove(name) ⇒ nil

Remove a (stopped) sandbox by name.

Returns:

  • (nil)


225
226
227
228
# File 'lib/microsandbox/sandbox.rb', line 225

def remove(name)
  Native::Sandbox.remove(name.to_s)
  nil
end

.start(name, detached: false) ⇒ Sandbox

Restart a previously-defined sandbox by name.

Returns:



198
199
200
201
# File 'lib/microsandbox/sandbox.rb', line 198

def start(name, detached: false)
  Microsandbox.ensure_runtime!
  new(Native::Sandbox.start(name.to_s, { "detached" => detached }))
end

Instance Method Details

#detachnil

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).

Returns:

  • (nil)


464
465
466
467
# File 'lib/microsandbox/sandbox.rb', line 464

def detach
  @native.detach
  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.

Parameters:

  • command (String)

    the executable

  • args (Array<String>) (defaults to: [])

    arguments

  • cwd (String, nil) (defaults to: nil)

    working directory

  • user (String, nil) (defaults to: nil)

    user to run as

  • env (Hash, nil) (defaults to: nil)

    extra environment variables

  • timeout (Numeric, nil) (defaults to: nil)

    kill after N seconds

  • tty (Boolean) (defaults to: false)

    allocate a pseudo-terminal

  • stdin (String, nil) (defaults to: nil)

    data to feed to stdin

Returns:



327
328
329
330
# File 'lib/microsandbox/sandbox.rb', line 327

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.

Returns:

See Also:



342
343
344
345
# File 'lib/microsandbox/sandbox.rb', line 342

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:)))
end

#fsFS

Guest filesystem operations.

Returns:



356
357
358
# File 'lib/microsandbox/sandbox.rb', line 356

def fs
  @fs ||= FS.new(@native)
end

#inspectObject



469
470
471
# File 'lib/microsandbox/sandbox.rb', line 469

def inspect
  "#<Microsandbox::Sandbox name=#{name.inspect}>"
end

#kill(timeout: nil) ⇒ nil

Force-kill the sandbox (SIGKILL).

Parameters:

  • timeout (Numeric, nil) (defaults to: nil)

    seconds to wait

Returns:

  • (nil)


422
423
424
425
# File 'lib/microsandbox/sandbox.rb', line 422

def kill(timeout: nil)
  @native.kill(timeout && Float(timeout))
  nil
end

#log_stream(sources: nil, since_ms: nil, from_cursor: nil, until_ms: nil, follow: false) ⇒ LogStream

Stream captured logs as they appear.

Parameters:

  • sources (Array<String,Symbol>, nil) (defaults to: nil)

    filter by source (“stdout”/“stderr”/“output”/“system”)

  • since_ms (Numeric, nil) (defaults to: nil)

    start at the first entry at/after this Unix ms

  • from_cursor (String, nil) (defaults to: nil)

    resume exactly after a prior LogEntry#cursor (mutually exclusive with since_ms; takes precedence if both given)

  • until_ms (Numeric, nil) (defaults to: nil)

    stop before any entry at/after this Unix ms

  • follow (Boolean) (defaults to: false)

    keep the stream open for new entries past current EOF

Returns:



401
402
403
404
405
406
407
408
409
# File 'lib/microsandbox/sandbox.rb', line 401

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.

Parameters:

  • tail (Integer, nil) (defaults to: nil)

    only the last N entries

  • since_ms (Numeric, nil) (defaults to: nil)

    only entries at/after this Unix ms

  • until_ms (Numeric, nil) (defaults to: nil)

    only entries before this Unix ms

  • sources (Array<String,Symbol>, nil) (defaults to: nil)

    filter by source (“stdout”/“stderr”/“output”/“system”/“all”)

Returns:



374
375
376
377
378
379
380
381
# File 'lib/microsandbox/sandbox.rb', line 374

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

#metricsMetrics

Latest resource-usage snapshot.

Returns:



362
363
364
# File 'lib/microsandbox/sandbox.rb', line 362

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.

Parameters:

  • interval (Numeric) (defaults to: 1.0)

    seconds between snapshots

Returns:



387
388
389
# File 'lib/microsandbox/sandbox.rb', line 387

def metrics_stream(interval: 1.0)
  MetricsStream.new(@native.metrics_stream(Float(interval)))
end

#nameString

Returns the sandbox name.

Returns:

  • (String)

    the sandbox name



312
313
314
# File 'lib/microsandbox/sandbox.rb', line 312

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).

Returns:

  • (Boolean)

    whether this handle owns the sandbox process lifecycle (i.e. stopping it or dropping the handle terminates the sandbox)



457
458
459
# File 'lib/microsandbox/sandbox.rb', line 457

def owns_lifecycle?
  @native.owns_lifecycle
end

#request_drainnil

Request a graceful drain and return immediately, without waiting.

Returns:

  • (nil)


444
445
446
447
# File 'lib/microsandbox/sandbox.rb', line 444

def request_drain
  @native.request_drain
  nil
end

#request_killnil

Send the force-kill request and return immediately, without waiting.

Returns:

  • (nil)


437
438
439
440
# File 'lib/microsandbox/sandbox.rb', line 437

def request_kill
  @native.request_kill
  nil
end

#request_stopnil

Send the graceful-shutdown request and return immediately, without waiting for the sandbox to terminate. Pair with #wait_until_stopped.

Returns:

  • (nil)


430
431
432
433
# File 'lib/microsandbox/sandbox.rb', line 430

def request_stop
  @native.request_stop
  nil
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.

Returns:



334
335
336
337
# File 'lib/microsandbox/sandbox.rb', line 334

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

Run a shell script and stream its output as it arrives.

Returns:



349
350
351
352
# File 'lib/microsandbox/sandbox.rb', line 349

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:)))
end

#stop(timeout: nil) ⇒ nil

Gracefully stop the sandbox (and wait for it to terminate).

Parameters:

  • timeout (Numeric, nil) (defaults to: nil)

    seconds to wait before SIGKILL

Returns:

  • (nil)


414
415
416
417
# File 'lib/microsandbox/sandbox.rb', line 414

def stop(timeout: nil)
  @native.stop(timeout && Float(timeout))
  nil
end

#wait_until_stoppedSandboxStopResult

Block until the sandbox is observed in a terminal (non-running) state.

Returns:



451
452
453
# File 'lib/microsandbox/sandbox.rb', line 451

def wait_until_stopped
  SandboxStopResult.new(@native.wait_until_stopped)
end