microsandbox-rb 0.14.0 → 0.16.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.
@@ -20,6 +20,14 @@ module Microsandbox
20
20
  # @return [String]
21
21
  def name = @native.name
22
22
 
23
+ # The opaque, backend-assigned identity of the persisted sandbox this handle
24
+ # is bound to (runtime v0.6.16). Unlike {#name} — a reusable label — it is
25
+ # stable for the sandbox's lifetime and changes once the same name is
26
+ # removed and recreated; the convergent operations below compare against it
27
+ # before acting.
28
+ # @return [String]
29
+ def id = @native.id
30
+
23
31
  # @return [Symbol] :created, :starting, :running, :draining, :paused,
24
32
  # :stopped, or :crashed (a snapshot, captured when this handle was fetched)
25
33
  def status = @native.status.to_sym
@@ -95,6 +103,68 @@ module Microsandbox
95
103
  nil
96
104
  end
97
105
 
106
+ # Converge on a live sandbox for this handle (runtime v0.6.16): connect
107
+ # when it is already running, wait while it is starting, or start it when it
108
+ # is created/stopped/crashed. A draining or paused sandbox is rejected
109
+ # rather than raced.
110
+ # @param detached [Boolean] applies only when a start is actually required
111
+ # @raise [SandboxReplacedError] if the name now refers to a different sandbox
112
+ # @return [Sandbox] the live sandbox
113
+ def connect_or_start(detached: false)
114
+ # May boot a microVM (the start arm), so it needs a provisioned runtime
115
+ # just like {Sandbox.create}/{Sandbox.start}. Memoized after the first
116
+ # call, so the extra check costs nothing on the connect arm.
117
+ Microsandbox.ensure_runtime!
118
+ opts = detached ? {"detached" => true} : {}
119
+ Sandbox.new(@native.connect_or_start(opts))
120
+ end
121
+
122
+ # Block until *this exact* sandbox reaches +status+ (runtime v0.6.16).
123
+ #
124
+ # The wait is uninterruptible from Ruby: the native call blocks with the GVL
125
+ # released and no unblock function, so Ctrl-C (and any other signal) only
126
+ # lands once it returns. There is no built-in timeout either — the core
127
+ # polls every 100ms forever, by design, leaving the deadline to the caller.
128
+ # So wait only for a state this *backend* can actually reach (`:created` and
129
+ # `:paused` are cloud-side states, unreachable locally), and if you need a
130
+ # deadline, run this on a Thread of its own and enforce the timeout there
131
+ # (`Timeout.timeout` around it would not fire).
132
+ # @param status [Symbol, String] :created, :starting, :running, :draining,
133
+ # :paused, :stopped, or :crashed
134
+ # @raise [ArgumentError] on an unknown status name
135
+ # @raise [SandboxReplacedError] if the name now refers to a different sandbox
136
+ # @return [SandboxHandle] a fresh handle observed in that state
137
+ def wait_for_status(status)
138
+ SandboxHandle.new(@native.wait_for_status(Sandbox.send(:coerce_status, status)))
139
+ end
140
+
141
+ # Stop and start *this exact* sandbox, returning the new live sandbox
142
+ # (runtime v0.6.16). A created/stopped/crashed sandbox is started directly.
143
+ # @param force [Boolean] SIGKILL instead of requesting a graceful shutdown
144
+ # @param timeout [Numeric, nil] graceful-shutdown seconds before escalating
145
+ # (the core's default is 10)
146
+ # @param detached [Boolean] start the replacement detached
147
+ # @raise [SandboxReplacedError] if the name now refers to a different sandbox
148
+ # @return [Sandbox]
149
+ def restart(force: false, timeout: nil, detached: false)
150
+ Microsandbox.ensure_runtime!
151
+ opts = Sandbox.send(:build_restart_opts, force: force, timeout: timeout, detached: detached)
152
+ Sandbox.new(@native.restart(opts))
153
+ end
154
+
155
+ # Stop and remove *this exact* sandbox (runtime v0.6.16) — {#stop} plus
156
+ # {Sandbox.remove} in one convergent step, with an identity check that
157
+ # refuses to remove a same-name replacement.
158
+ # @param force [Boolean] SIGKILL instead of requesting a graceful shutdown
159
+ # @param timeout [Numeric, nil] graceful-shutdown seconds before escalating
160
+ # (the core's default is 10)
161
+ # @raise [SandboxReplacedError] if the name now refers to a different sandbox
162
+ # @return [nil]
163
+ def destroy(force: false, timeout: nil)
164
+ @native.destroy(Sandbox.send(:build_destroy_opts, force: force, timeout: timeout))
165
+ nil
166
+ end
167
+
98
168
  # Block until the sandbox is observed in a terminal (non-running) state.
99
169
  # @return [SandboxStopResult]
100
170
  def wait_until_stopped
@@ -265,6 +335,11 @@ module Microsandbox
265
335
  # to gate the `fstype:`-vs-OCI check; keep in sync on a runtime-tag bump.
266
336
  DISK_IMAGE_EXTENSIONS = %w[raw qcow2 vmdk].freeze
267
337
 
338
+ # The seven lifecycle states a sandbox can be observed in — the spelling
339
+ # {Sandbox#status} / {SandboxHandle#status} return, and the accepted
340
+ # arguments of {Sandbox#wait_for_status} / {SandboxHandle#wait_for_status}.
341
+ STATUSES = %i[created starting running draining paused stopped crashed].freeze
342
+
268
343
  class << self
269
344
  # Create and boot a sandbox.
270
345
  #
@@ -309,7 +384,11 @@ module Microsandbox
309
384
  # (4 GiB as of `v0.5.10`); the core rejects it on tmpfs/disk/named (for a
310
385
  # named volume, set its quota via {Volume.create}). Bind/named mounts
311
386
  # also accept `follow_root_symlinks: true` to opt out of the default-on
312
- # mount-root symlink protection (runtime v0.6.7).
387
+ # mount-root symlink protection (runtime v0.6.7), and `uid:`/`gid:`
388
+ # (runtime v0.6.15) to pin the guest owner presented for host files that
389
+ # carry no per-file stat override — both must be given together, each an
390
+ # Integer in 0..4294967295, and they conflict with
391
+ # `stat_virtualization: :off`.
313
392
  # @param network [Array, String, Symbol, NetworkPolicy, Hash, nil] network
314
393
  # policy. Composable profiles (`[:public]` (the default), `[:public,
315
394
  # :private]`, `:host`, …), a terminal preset (:none, :allow_all), a
@@ -414,6 +493,62 @@ module Microsandbox
414
493
  end
415
494
  end
416
495
 
496
+ # Converge on a live sandbox called +name+ (runtime v0.6.16, upstream
497
+ # #1462): connect to the persisted sandbox with that name — starting it
498
+ # when it is created/stopped/crashed, waiting when it is already starting
499
+ # — or create it when none exists. Concurrent callers converge on the
500
+ # winning identity instead of one of them losing to a name clash.
501
+ #
502
+ # The keyword options are exactly {create}'s and are used **only when a
503
+ # create actually happens**: an existing sandbox keeps its persisted
504
+ # configuration, so passing `memory:` here will not resize one that is
505
+ # already there. `replace:`/`replace_with_timeout:` are accepted for
506
+ # kwargs parity with {create} but are rejected by the core — replacing a
507
+ # sandbox is the opposite of converging on it — and raise
508
+ # {InvalidConfigError}.
509
+ #
510
+ # The block form yields the sandbox and then stops it **only when this
511
+ # call owns its lifecycle** ({Sandbox#owns_lifecycle?}). That is a
512
+ # deliberate divergence from {create}, whose block form always stops:
513
+ # `connect_or_create` may hand back a sandbox this process did not start,
514
+ # and tearing down someone else's long-lived service on the way out of a
515
+ # block is never what the caller meant. Concretely:
516
+ #
517
+ # - created here (the common case) — yielded, then stopped, exactly like
518
+ # {create};
519
+ # - *connected* to a sandbox that was already running — left running;
520
+ # - created here with `detached: true` — left running, which is what
521
+ # `detached:` asks for (this is where the divergence from {create} bites:
522
+ # `create(..., detached: true) { }` still stops on block exit).
523
+ #
524
+ # Either way, stop it yourself with {Sandbox#stop}/{Sandbox#destroy} when
525
+ # you do want it gone.
526
+ #
527
+ # The teardown is safe against name reuse from either side: it skips the
528
+ # stop entirely for a sandbox this call did not start, and the stop it does
529
+ # issue is scoped to this sandbox's {Sandbox#id} (runtime v0.6.16), so a
530
+ # name removed and recreated while the block ran raises inside the ensure
531
+ # — swallowed as a best-effort teardown failure — rather than taking the
532
+ # replacement down.
533
+ # @param name [String]
534
+ # @yieldparam sandbox [Sandbox]
535
+ # @return [Sandbox, Object] the live sandbox, or the block's return value
536
+ def connect_or_create(name, **kwargs, &block)
537
+ opts = build_create_opts(**kwargs)
538
+ sandbox = new(Native::Sandbox.connect_or_create(name.to_s, opts))
539
+ return sandbox unless block_given?
540
+
541
+ begin
542
+ yield sandbox
543
+ ensure
544
+ begin
545
+ sandbox.stop if sandbox.owns_lifecycle?
546
+ rescue Microsandbox::Error
547
+ # best-effort cleanup; ignore stop failures during teardown
548
+ end
549
+ end
550
+ end
551
+
417
552
  # Create a sandbox while streaming image-pull progress. Accepts the same
418
553
  # options as {create}; returns a {PullSession} — iterate it (an
419
554
  # {Enumerable} of progress-event Hashes, each with a "kind"), then call
@@ -590,6 +725,40 @@ module Microsandbox
590
725
  seconds
591
726
  end
592
727
 
728
+ # Validate a `wait_for_status` target and lower it to the wire name.
729
+ # Accepts a Symbol or String in the {STATUSES} spelling; anything else is
730
+ # an ArgumentError (an unreachable typo would otherwise block forever —
731
+ # `wait_for_status` has no built-in timeout).
732
+ def coerce_status(status)
733
+ name = status.to_s
734
+ unless STATUSES.include?(name.to_sym)
735
+ raise ArgumentError,
736
+ "unknown sandbox status #{status.inspect} " \
737
+ "(expected one of: #{STATUSES.join(", ")})"
738
+ end
739
+ name
740
+ end
741
+
742
+ # Shared option builder for `restart`, on both {Sandbox} and
743
+ # {SandboxHandle}. An omitted `timeout:` keeps the core's ten-second
744
+ # graceful-shutdown default.
745
+ def build_restart_opts(force:, timeout:, detached:)
746
+ opts = {}
747
+ opts["force"] = true if force
748
+ opts["detached"] = true if detached
749
+ opts["timeout"] = coerce_duration(timeout, "timeout") if timeout
750
+ opts
751
+ end
752
+
753
+ # Shared option builder for `destroy`, on both {Sandbox} and
754
+ # {SandboxHandle}.
755
+ def build_destroy_opts(force:, timeout:)
756
+ opts = {}
757
+ opts["force"] = true if force
758
+ opts["timeout"] = coerce_duration(timeout, "timeout") if timeout
759
+ opts
760
+ end
761
+
593
762
  def stringify(hash)
594
763
  hash.each_with_object({}) { |(k, v), acc| acc[k.to_s] = v.to_s }
595
764
  end
@@ -987,7 +1156,8 @@ module Microsandbox
987
1156
  # { tmpfs: true, size_mib: 64 } # memory-backed
988
1157
  # { disk: "/img.raw", format: "raw", fstype: "ext4" } # disk-image mount
989
1158
  # Any mount may also carry stat_virtualization: (:strict/:relaxed/:off) and
990
- # host_permissions: (:private/:mirror); a bind mount may carry quota_mib:.
1159
+ # host_permissions: (:private/:mirror); a bind/named mount may carry
1160
+ # uid:/gid: (the fallback guest owner); a bind mount may carry quota_mib:.
991
1161
  # Coerce the `root_disk:` argument — an Integer (managed size in MiB), a
992
1162
  # {RootDisk} factory Hash, or an equivalent hand-written Hash — into the
993
1163
  # wire shape, validating kind/field combinations up front (mirrors the
@@ -1115,6 +1285,8 @@ module Microsandbox
1115
1285
  # `follow_root_symlinks:` opts out of the default-on mount-root symlink
1116
1286
  # protection (runtime v0.6.7; bind/named only — the core silently ignores
1117
1287
  # it on tmpfs/disk mounts, so reject those here instead).
1288
+ # `uid:`/`gid:` pin the fallback guest owner (runtime v0.6.15) — see
1289
+ # {apply_mount_owner}.
1118
1290
  def apply_mount_flags(mount, spec)
1119
1291
  mount["readonly"] = true if spec[:ro] || spec["ro"] || spec[:readonly] || spec["readonly"]
1120
1292
  mount["noexec"] = true if spec[:noexec] || spec["noexec"]
@@ -1134,6 +1306,58 @@ module Microsandbox
1134
1306
  end
1135
1307
  mount["follow_root_symlinks"] = !!follow
1136
1308
  end
1309
+ apply_mount_owner(mount, spec)
1310
+ end
1311
+
1312
+ # Apply a volume spec Hash's fallback mount ownership (runtime v0.6.15,
1313
+ # upstream #1451). Host files carrying no per-file stat override surface in
1314
+ # the guest as `uid:`/`gid:` instead of the runtime's fallback owner; the
1315
+ # pair travels on the wire as `override_uid`/`override_gid` (the core's
1316
+ # `MountBuilder#owner`), mirroring the Python SDK's concise `uid:`/`gid:`
1317
+ # public names over the same wire fields.
1318
+ #
1319
+ # Validation mirrors the Python SDK exactly: the two must be given
1320
+ # together, each must be a plain Integer in the u32 range, they need stat
1321
+ # virtualization (so `stat_virtualization: :off` conflicts), and they only
1322
+ # apply to bind/named mounts. The one Python rule with no Ruby counterpart
1323
+ # is "not supported for disk-backed named volumes": Python knows the
1324
+ # volume kind because its `Volume.named` carries it, while a Ruby
1325
+ # `{ named: "vol" }` spec only references an existing volume by name — the
1326
+ # core rejects that combination at create() instead.
1327
+ def apply_mount_owner(mount, spec)
1328
+ uid = spec.fetch(:uid, spec["uid"])
1329
+ gid = spec.fetch(:gid, spec["gid"])
1330
+ return if uid.nil? && gid.nil?
1331
+
1332
+ if uid.nil? || gid.nil?
1333
+ raise ArgumentError, "mount uid: and gid: must be set together"
1334
+ end
1335
+ unless %w[bind named].include?(mount["kind"])
1336
+ raise ArgumentError,
1337
+ "uid:/gid: (mount owner) only applies to bind/named mounts " \
1338
+ "(got a #{mount["kind"]} mount), like stat_virtualization:/host_permissions:"
1339
+ end
1340
+ if mount["stat_virtualization"] == "off"
1341
+ raise ArgumentError,
1342
+ "uid:/gid: (mount owner) cannot be combined with stat_virtualization: :off — " \
1343
+ "`off` exposes literal host metadata, leaving no overlay to rewrite the owner in"
1344
+ end
1345
+ mount["override_uid"] = coerce_mount_owner_id(uid, "uid:")
1346
+ mount["override_gid"] = coerce_mount_owner_id(gid, "gid:")
1347
+ end
1348
+
1349
+ # Range-check one mount owner ID. Deliberately stricter than the repo's
1350
+ # usual `Integer(value)` coercion: `Integer("1000")` and a truncating
1351
+ # `Integer(1000.7)` would both silently accept input that never named a
1352
+ # real owner. `is_a?(Integer)` also rejects `true`/`false`, matching the
1353
+ # Python SDK's `_mount_owner_id` (`type(value) is not int`, which excludes
1354
+ # `bool`).
1355
+ def coerce_mount_owner_id(value, label)
1356
+ unless value.is_a?(Integer) && value >= 0 && value <= 0xFFFF_FFFF
1357
+ raise ArgumentError,
1358
+ "#{label} must be an Integer between 0 and 4294967295 (got #{value.inspect})"
1359
+ end
1360
+ value
1137
1361
  end
1138
1362
 
1139
1363
  # Translate the pre-0.7.0 `options:` array form (e.g. options: %w[ro noexec])
@@ -1201,6 +1425,16 @@ module Microsandbox
1201
1425
  @native.name
1202
1426
  end
1203
1427
 
1428
+ # The opaque, backend-assigned identity of the persisted sandbox (runtime
1429
+ # v0.6.16). Unlike {#name} — a reusable label — this is stable for the
1430
+ # sandbox's lifetime and changes once the same name is removed and
1431
+ # recreated, which is exactly what {#wait_for_status}/{#restart}/{#destroy}
1432
+ # check before acting.
1433
+ # @return [String]
1434
+ def id
1435
+ @native.id
1436
+ end
1437
+
1204
1438
  # Run a command (no shell interpretation) and collect its output.
1205
1439
  #
1206
1440
  # @param command [String] the executable
@@ -1488,6 +1722,13 @@ module Microsandbox
1488
1722
  # Gracefully stop the sandbox (SIGTERM→SIGKILL escalation, 10s default) and
1489
1723
  # wait for it to terminate. For a custom timeout or fire-and-return
1490
1724
  # `request_*` control, fetch a {SandboxHandle} via {Sandbox.get}.
1725
+ #
1726
+ # Scoped to *this exact* sandbox: as of runtime v0.6.16 the stop carries this
1727
+ # object's {#id}, so if the name has since been removed and recreated the
1728
+ # call raises {SandboxReplacedError} (or {SandboxNotFoundError} when nothing
1729
+ # holds the name any more) instead of terminating whatever sandbox now
1730
+ # answers to it. Same for {#kill}, {#drain} and {#stop_and_wait}.
1731
+ # @raise [SandboxReplacedError] if the name now refers to a different sandbox
1491
1732
  # @return [nil]
1492
1733
  def stop
1493
1734
  @native.stop
@@ -1520,6 +1761,59 @@ module Microsandbox
1520
1761
  ExitStatus.new(@native.wait)
1521
1762
  end
1522
1763
 
1764
+ # Block until *this exact* sandbox reaches +status+ (runtime v0.6.16). If
1765
+ # the name has since been recreated as a different sandbox, this raises
1766
+ # {SandboxReplacedError} rather than silently redirecting the wait to the
1767
+ # replacement.
1768
+ #
1769
+ # The wait is uninterruptible from Ruby: the native call blocks with the GVL
1770
+ # released and no unblock function, so Ctrl-C (and any other signal) only
1771
+ # lands once it returns. There is no built-in timeout either — the core
1772
+ # polls every 100ms forever, by design, leaving the deadline to the caller.
1773
+ # So wait only for a state this *backend* can actually reach (`:created` and
1774
+ # `:paused` are cloud-side states, unreachable locally), and if you need a
1775
+ # deadline, run this on a Thread of its own and enforce the timeout there
1776
+ # (`Timeout.timeout` around it would not fire).
1777
+ # @param status [Symbol, String] :created, :starting, :running, :draining,
1778
+ # :paused, :stopped, or :crashed
1779
+ # @raise [ArgumentError] on an unknown status name
1780
+ # @return [SandboxHandle] a fresh handle observed in that state (mirrors the
1781
+ # official SDKs, which return a handle from both `Sandbox` and
1782
+ # `SandboxHandle`)
1783
+ def wait_for_status(status)
1784
+ SandboxHandle.new(@native.wait_for_status(self.class.send(:coerce_status, status)))
1785
+ end
1786
+
1787
+ # Stop and start *this exact* sandbox, returning the new live sandbox
1788
+ # (runtime v0.6.16). A created/stopped/crashed sandbox is started directly.
1789
+ # This handle is spent afterwards — use the returned one.
1790
+ # @param force [Boolean] SIGKILL instead of requesting a graceful shutdown
1791
+ # @param timeout [Numeric, nil] graceful-shutdown seconds before escalating
1792
+ # (the core's default is 10)
1793
+ # @param detached [Boolean] start the replacement detached
1794
+ # @raise [SandboxReplacedError] if the name now refers to a different sandbox
1795
+ # @return [Sandbox]
1796
+ def restart(force: false, timeout: nil, detached: false)
1797
+ # Boots a microVM again, so it needs a provisioned runtime — the sandbox
1798
+ # this object came from may have been created in a different process.
1799
+ Microsandbox.ensure_runtime!
1800
+ opts = self.class.send(:build_restart_opts, force: force, timeout: timeout, detached: detached)
1801
+ self.class.new(@native.restart(opts))
1802
+ end
1803
+
1804
+ # Stop and remove *this exact* sandbox (runtime v0.6.16) — the convergent
1805
+ # equivalent of {#stop} followed by {Sandbox.remove}, with an identity check
1806
+ # that refuses to remove a same-name replacement.
1807
+ # @param force [Boolean] SIGKILL instead of requesting a graceful shutdown
1808
+ # @param timeout [Numeric, nil] graceful-shutdown seconds before escalating
1809
+ # (the core's default is 10)
1810
+ # @raise [SandboxReplacedError] if the name now refers to a different sandbox
1811
+ # @return [nil]
1812
+ def destroy(force: false, timeout: nil)
1813
+ @native.destroy(self.class.send(:build_destroy_opts, force: force, timeout: timeout))
1814
+ nil
1815
+ end
1816
+
1523
1817
  # The live status, fetched from the backend (a round-trip per call).
1524
1818
  # @return [Symbol] :created, :starting, :running, :draining, :paused,
1525
1819
  # :stopped, or :crashed
@@ -231,11 +231,17 @@ module Microsandbox
231
231
  # @param user [String] guest user to authenticate as (default "root")
232
232
  # @param term [String, nil] TERM value for the session
233
233
  # @param sftp [Boolean] enable the SFTP subsystem (default true)
234
+ # @param inactivity_timeout [Numeric, nil] per-session inactivity timeout in
235
+ # seconds; `nil` inherits the global config (default 600s), `0` disables it
234
236
  # @yieldparam client [SshClient]
235
237
  # @return [SshClient, Object]
236
- def open_client(user: "root", term: nil, sftp: true)
238
+ def open_client(user: "root", term: nil, sftp: true, inactivity_timeout: nil)
237
239
  opts = {"user" => user.to_s, "sftp" => sftp ? true : false}
238
240
  opts["term"] = term.to_s if term
241
+ unless inactivity_timeout.nil?
242
+ opts["inactivity_timeout"] =
243
+ Sandbox.send(:coerce_duration, inactivity_timeout, "inactivity_timeout")
244
+ end
239
245
  client = SshClient.new(@native.ssh_open_client(opts))
240
246
  return client unless block_given?
241
247
 
@@ -251,12 +257,19 @@ module Microsandbox
251
257
  # @param authorized_keys_path [String, nil] authorized_keys file path
252
258
  # @param user [String, nil] guest user connections run as
253
259
  # @param sftp [Boolean] enable the SFTP subsystem (default true)
260
+ # @param inactivity_timeout [Numeric, nil] per-session inactivity timeout in
261
+ # seconds; `nil` inherits the global config (default 600s), `0` disables it
254
262
  # @return [SshServer]
255
- def prepare_server(host_key_path: nil, authorized_keys_path: nil, user: nil, sftp: true)
263
+ def prepare_server(host_key_path: nil, authorized_keys_path: nil, user: nil, sftp: true,
264
+ inactivity_timeout: nil)
256
265
  opts = {"sftp" => sftp ? true : false}
257
266
  opts["host_key_path"] = host_key_path.to_s if host_key_path
258
267
  opts["authorized_keys_path"] = authorized_keys_path.to_s if authorized_keys_path
259
268
  opts["user"] = user.to_s if user
269
+ unless inactivity_timeout.nil?
270
+ opts["inactivity_timeout"] =
271
+ Sandbox.send(:coerce_duration, inactivity_timeout, "inactivity_timeout")
272
+ end
260
273
  SshServer.new(@native.ssh_prepare_server(opts))
261
274
  end
262
275
  end
@@ -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.14.0"
11
+ VERSION = "0.16.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.9"
18
+ RUNTIME_VERSION = "v0.6.16"
19
19
  end
data/sig/microsandbox.rbs CHANGED
@@ -39,6 +39,7 @@ module Microsandbox
39
39
  class SandboxNotRunningError < Error end
40
40
  class SandboxAlreadyExistsError < Error end
41
41
  class SandboxStillRunningError < Error end
42
+ class SandboxReplacedError < Error end
42
43
  class ExecTimeoutError < Error end
43
44
  class ExecFailedError < Error end
44
45
  class NoDefaultCommandError < Error end
@@ -163,6 +164,7 @@ module Microsandbox
163
164
 
164
165
  class SandboxHandle
165
166
  def name: () -> String
167
+ def id: () -> String
166
168
  def status: () -> Symbol
167
169
  def running?: () -> bool
168
170
  def stopped?: () -> bool
@@ -176,6 +178,10 @@ module Microsandbox
176
178
  def request_kill: () -> nil
177
179
  def request_drain: () -> nil
178
180
  def wait_until_stopped: () -> SandboxStopResult
181
+ def connect_or_start: (?detached: bool) -> Sandbox
182
+ def wait_for_status: (Symbol | String status) -> SandboxHandle
183
+ def restart: (?force: bool, ?timeout: Numeric?, ?detached: bool) -> Sandbox
184
+ def destroy: (?force: bool, ?timeout: Numeric?) -> nil
179
185
  def config_json: () -> String
180
186
  def config: () -> Hash[String, untyped]
181
187
  def snapshot: (String name) -> SnapshotInfo
@@ -243,6 +249,8 @@ module Microsandbox
243
249
  end
244
250
 
245
251
  class Sandbox
252
+ STATUSES: Array[Symbol]
253
+
246
254
  def self.create: (String name, ?image: String?, ?cpus: Integer?, ?max_cpus: Integer?,
247
255
  ?memory: Integer?, ?max_memory: Integer?,
248
256
  ?env: Hash[untyped, untyped]?, ?workdir: String?, ?shell: String?,
@@ -269,6 +277,10 @@ module Microsandbox
269
277
  ?detached: bool,
270
278
  ?replace: bool, ?replace_with_timeout: Numeric?)
271
279
  ?{ (Sandbox) -> untyped } -> untyped
280
+ # Accepts the same keyword options as {create} (used only when a create is
281
+ # actually necessary); `replace:`/`replace_with_timeout:` are rejected by
282
+ # the core.
283
+ def self.connect_or_create: (String name, **untyped) ?{ (Sandbox) -> untyped } -> untyped
272
284
  # Accepts the same keyword options as {create}.
273
285
  def self.create_with_progress: (String name, **untyped) -> PullSession
274
286
  def self.start: (String name, ?detached: bool) -> Sandbox
@@ -278,6 +290,7 @@ module Microsandbox
278
290
  def self.remove: (String name) -> nil
279
291
 
280
292
  def name: () -> String
293
+ def id: () -> String
281
294
  def exec: (String command, ?Array[String] args, ?cwd: String?, ?user: String?,
282
295
  ?env: Hash[untyped, untyped]?, ?timeout: Numeric?, ?tty: bool, ?stdin: (String | :null)?,
283
296
  ?rlimits: Hash[untyped, untyped]?) -> ExecOutput
@@ -322,6 +335,9 @@ module Microsandbox
322
335
  def kill: () -> nil
323
336
  def drain: () -> nil
324
337
  def wait: () -> ExitStatus
338
+ def wait_for_status: (Symbol | String status) -> SandboxHandle
339
+ def restart: (?force: bool, ?timeout: Numeric?, ?detached: bool) -> Sandbox
340
+ def destroy: (?force: bool, ?timeout: Numeric?) -> nil
325
341
  def status: () -> Symbol
326
342
  def owns_lifecycle?: () -> bool
327
343
  def detach: () -> nil
@@ -653,8 +669,9 @@ module Microsandbox
653
669
 
654
670
  class SshOps
655
671
  def initialize: (untyped native) -> void
656
- def open_client: (?user: String, ?term: String?, ?sftp: bool) ?{ (SshClient) -> untyped } -> untyped
672
+ def open_client: (?user: String, ?term: String?, ?sftp: bool,
673
+ ?inactivity_timeout: Numeric?) ?{ (SshClient) -> untyped } -> untyped
657
674
  def prepare_server: (?host_key_path: String?, ?authorized_keys_path: String?,
658
- ?user: String?, ?sftp: bool) -> SshServer
675
+ ?user: String?, ?sftp: bool, ?inactivity_timeout: Numeric?) -> SshServer
659
676
  end
660
677
  end
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.14.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ya-luotao