microsandbox-rb 0.17.1 → 0.17.3
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 +138 -8
- data/Cargo.lock +1 -1
- data/DESIGN.md +183 -36
- data/README.md +8 -7
- data/SECURITY.md +1 -1
- data/docs/errors.md +1 -0
- data/docs/execution.md +8 -1
- data/docs/lifecycle.md +33 -0
- data/docs/releasing.md +85 -34
- data/docs/runtime.md +88 -1
- data/docs/surface.md +16 -8
- data/ext/microsandbox/Cargo.toml +9 -5
- data/ext/microsandbox/src/agent.rs +26 -21
- data/ext/microsandbox/src/backend.rs +291 -34
- data/ext/microsandbox/src/error.rs +18 -0
- data/ext/microsandbox/src/exec.rs +39 -21
- data/ext/microsandbox/src/fs_stream.rs +30 -15
- data/ext/microsandbox/src/host_path.rs +4 -3
- data/ext/microsandbox/src/image.rs +2 -2
- data/ext/microsandbox/src/lib.rs +3 -2
- data/ext/microsandbox/src/runtime.rs +314 -47
- data/ext/microsandbox/src/sandbox.rs +225 -124
- data/ext/microsandbox/src/snapshot.rs +24 -15
- data/ext/microsandbox/src/ssh.rs +48 -39
- data/ext/microsandbox/src/stream.rs +19 -9
- data/ext/microsandbox/src/volume.rs +40 -32
- data/lib/microsandbox/errors.rb +8 -0
- data/lib/microsandbox/sandbox.rb +75 -4
- data/lib/microsandbox/version.rb +1 -1
- data/lib/microsandbox.rb +20 -1
- data/sig/microsandbox.rbs +25 -0
- metadata +1 -1
data/lib/microsandbox/errors.rb
CHANGED
|
@@ -102,6 +102,14 @@ module Microsandbox
|
|
|
102
102
|
define_error(:CloudHttpError, "cloud-http")
|
|
103
103
|
define_error(:UnsupportedError, "unsupported")
|
|
104
104
|
|
|
105
|
+
# Process model ------------------------------------------------------------
|
|
106
|
+
# A handle (a Sandbox, a SandboxHandle, an ExecHandle, a stream, an SSH or
|
|
107
|
+
# agent client, a VolumeFs, ...) was used in a process forked from the one
|
|
108
|
+
# that created it. Handles are bound to their creating process; a forked
|
|
109
|
+
# child opens its own (`Sandbox.get(name).connect`, `Volume.fs(name)`, ...).
|
|
110
|
+
# The message names both pids. Ruby-only: no other SDK has a counterpart.
|
|
111
|
+
define_error(:ForkedHandleError, "forked-handle")
|
|
112
|
+
|
|
105
113
|
# As of runtime v0.6.8 the core keys Unsupported errors by a structured
|
|
106
114
|
# (operation, reason) pair. The native layer renders both into the message
|
|
107
115
|
# ("sandbox.kill is not supported by this backend: ...") and also attaches
|
data/lib/microsandbox/sandbox.rb
CHANGED
|
@@ -496,7 +496,10 @@ module Microsandbox
|
|
|
496
496
|
def create(name, **kwargs, &block)
|
|
497
497
|
opts = build_create_opts(**kwargs)
|
|
498
498
|
Microsandbox.ensure_runtime!
|
|
499
|
-
|
|
499
|
+
# Everything Ruby-level happens BEFORE `acquire`: its interrupt mask must
|
|
500
|
+
# cover only the native call plus the wrap, never caller-supplied code.
|
|
501
|
+
sandbox_name = name.to_s
|
|
502
|
+
sandbox = acquire { Native::Sandbox.create(sandbox_name, opts) }
|
|
500
503
|
return sandbox unless block_given?
|
|
501
504
|
|
|
502
505
|
begin
|
|
@@ -553,7 +556,8 @@ module Microsandbox
|
|
|
553
556
|
def connect_or_create(name, **kwargs, &block)
|
|
554
557
|
opts = build_create_opts(**kwargs)
|
|
555
558
|
Microsandbox.ensure_runtime!
|
|
556
|
-
|
|
559
|
+
sandbox_name = name.to_s # outside the interrupt mask, like in {create}
|
|
560
|
+
sandbox = acquire { Native::Sandbox.connect_or_create(sandbox_name, opts) }
|
|
557
561
|
return sandbox unless block_given?
|
|
558
562
|
|
|
559
563
|
begin
|
|
@@ -588,6 +592,65 @@ module Microsandbox
|
|
|
588
592
|
PullSession.new(Native::Sandbox.create_with_progress(name.to_s, opts))
|
|
589
593
|
end
|
|
590
594
|
|
|
595
|
+
# @api private
|
|
596
|
+
# Wrap the native sandbox the block acquires, and stop it again if an
|
|
597
|
+
# interrupt cuts in before the caller has it.
|
|
598
|
+
#
|
|
599
|
+
# A native call cannot be interrupted while it runs, so a `Thread#raise`
|
|
600
|
+
# (what `Timeout.timeout` uses) or a `Thread#kill` that arrives during the
|
|
601
|
+
# block is delivered after it — at the next interrupt checkpoint, which is
|
|
602
|
+
# still in here, before the caller ever sees the object. Left alone the
|
|
603
|
+
# sandbox would be garbage and only GC would stop it, "eventually"; for
|
|
604
|
+
# `Timeout.timeout { Sandbox.create(...) }` that is not good enough. So:
|
|
605
|
+
#
|
|
606
|
+
# - the acquisition runs under `Thread.handle_interrupt(Object => :never)`,
|
|
607
|
+
# which holds a queued exception (or kill) back until the wrapper is
|
|
608
|
+
# assigned. The mask covers only the uninterruptible native call plus
|
|
609
|
+
# the wrap, so it changes no observable timing; runtime provisioning
|
|
610
|
+
# ({Microsandbox.ensure_runtime!}) and the block forms' `yield` stay
|
|
611
|
+
# outside it, with normal interrupt handling. For the same reason the
|
|
612
|
+
# block passed here must contain NOTHING but the native call: argument
|
|
613
|
+
# conversion (`name.to_s`, option Hashes) is the caller's own code and
|
|
614
|
+
# has to run before `acquire`, where it stays interruptible;
|
|
615
|
+
# - the `ensure` plus the `acquired` flag — not `rescue Exception; raise` —
|
|
616
|
+
# is what also covers `Thread#kill`, which no `rescue` can catch.
|
|
617
|
+
#
|
|
618
|
+
# A SIGINT (Ctrl-C on the main thread) is the exception: it is not queued
|
|
619
|
+
# like those two and cannot be masked. One that lands during the native
|
|
620
|
+
# call is raised the moment the call returns — the VM checks interrupts
|
|
621
|
+
# as it pops the native method's frame, before any Ruby code here can
|
|
622
|
+
# hold the result — so no assignment order would catch it; the sandbox
|
|
623
|
+
# is then torn down when the unreferenced object is garbage-collected.
|
|
624
|
+
#
|
|
625
|
+
# Ownership: {connect_or_create} can hand back a sandbox that was already
|
|
626
|
+
# running for someone else, and {start} restarts a persisted one. The
|
|
627
|
+
# cleanup therefore applies EXACTLY the predicate the block form of
|
|
628
|
+
# {connect_or_create} uses — {Sandbox#owns_lifecycle?} — in all three
|
|
629
|
+
# callers, {create} included: a sandbox this call does not own is never
|
|
630
|
+
# stopped here. Its reference is just let go (GC closes the handle, the
|
|
631
|
+
# sandbox keeps running).
|
|
632
|
+
# @yieldreturn [Native::Sandbox]
|
|
633
|
+
# @return [Sandbox]
|
|
634
|
+
def acquire
|
|
635
|
+
sandbox = nil
|
|
636
|
+
acquired = false
|
|
637
|
+
begin
|
|
638
|
+
Thread.handle_interrupt(Object => :never) do
|
|
639
|
+
sandbox = new(yield)
|
|
640
|
+
end
|
|
641
|
+
acquired = true
|
|
642
|
+
ensure
|
|
643
|
+
if sandbox && !acquired && sandbox.owns_lifecycle?
|
|
644
|
+
begin
|
|
645
|
+
sandbox.stop
|
|
646
|
+
rescue Microsandbox::Error
|
|
647
|
+
# best-effort cleanup; the interrupt is what the caller must see
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
sandbox
|
|
652
|
+
end
|
|
653
|
+
|
|
591
654
|
# @api private
|
|
592
655
|
# Shared keyword-option builder for {create}/{connect_or_create}/
|
|
593
656
|
# {create_with_progress}. Pure validation and normalization: the callers
|
|
@@ -705,7 +768,9 @@ module Microsandbox
|
|
|
705
768
|
# @return [Sandbox]
|
|
706
769
|
def start(name, detached: false)
|
|
707
770
|
Microsandbox.ensure_runtime!
|
|
708
|
-
|
|
771
|
+
sandbox_name = name.to_s # outside the interrupt mask, like in {create}
|
|
772
|
+
opts = {"detached" => detached}
|
|
773
|
+
acquire { Native::Sandbox.start(sandbox_name, opts) }
|
|
709
774
|
end
|
|
710
775
|
|
|
711
776
|
# Fetch a controllable handle for a sandbox by name (running or not).
|
|
@@ -1998,10 +2063,16 @@ module Microsandbox
|
|
|
1998
2063
|
# {Sandbox} is live — stop it when done. Memoized: the native result can be
|
|
1999
2064
|
# taken only once, so a later call returns the same {Sandbox}, or re-raises
|
|
2000
2065
|
# the same error if the create failed.
|
|
2066
|
+
#
|
|
2067
|
+
# Interrupt-safe like {Sandbox.create}: if a `Timeout.timeout` (or another
|
|
2068
|
+
# `Thread#raise`/`Thread#kill`) arrives while this is waiting, the sandbox
|
|
2069
|
+
# the session produced is stopped before the exception propagates rather
|
|
2070
|
+
# than left running with nothing referring to it. The result is consumed
|
|
2071
|
+
# by then, so a later call raises instead of returning a sandbox.
|
|
2001
2072
|
# @return [Sandbox]
|
|
2002
2073
|
def sandbox
|
|
2003
2074
|
raise @failure if @failure
|
|
2004
|
-
@sandbox ||= Sandbox.
|
|
2075
|
+
@sandbox ||= Sandbox.acquire { @native.result }
|
|
2005
2076
|
rescue Microsandbox::Error => e
|
|
2006
2077
|
@failure ||= e
|
|
2007
2078
|
raise
|
data/lib/microsandbox/version.rb
CHANGED
|
@@ -8,7 +8,7 @@ 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.17.
|
|
11
|
+
VERSION = "0.17.3"
|
|
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
|
data/lib/microsandbox.rb
CHANGED
|
@@ -240,6 +240,15 @@ module Microsandbox
|
|
|
240
240
|
# avoid calling {set_default_backend} inside the block (the restore on exit
|
|
241
241
|
# would overwrite that change). Mirrors the official SDKs' scoped-backend helper.
|
|
242
242
|
#
|
|
243
|
+
# The restore can fail in one situation: the previous backend belonged to a
|
|
244
|
+
# parent process (the block is left in a child forked while it was running,
|
|
245
|
+
# or it is a forked child's first use of the SDK) and cannot be chosen
|
|
246
|
+
# again there — it came from the environment or config file, which no
|
|
247
|
+
# longer resolve. The block's temporary selection then stays in effect. If
|
|
248
|
+
# the block itself raised, its exception is the one that propagates and the
|
|
249
|
+
# restore failure is only warned about; otherwise the restore failure is
|
|
250
|
+
# raised.
|
|
251
|
+
#
|
|
243
252
|
# @param kind ["local","cloud", Symbol]
|
|
244
253
|
# @param url [String, nil]
|
|
245
254
|
# @param api_key [String, nil]
|
|
@@ -248,10 +257,20 @@ module Microsandbox
|
|
|
248
257
|
# @return [Object] the block's return value
|
|
249
258
|
def with_backend(kind, url: nil, api_key: nil, profile: nil)
|
|
250
259
|
token = Native.push_default_backend(kind.to_s, url&.to_s, api_key&.to_s, profile&.to_s)
|
|
260
|
+
raised = false
|
|
251
261
|
begin
|
|
252
262
|
yield
|
|
263
|
+
rescue Exception # standard:disable Lint/RescueException -- only noted, re-raised at once
|
|
264
|
+
raised = true
|
|
265
|
+
raise
|
|
253
266
|
ensure
|
|
254
|
-
|
|
267
|
+
begin
|
|
268
|
+
Native.pop_default_backend(token)
|
|
269
|
+
rescue Microsandbox::Error => e
|
|
270
|
+
# Never let the restore failure replace what the block raised.
|
|
271
|
+
raise unless raised
|
|
272
|
+
warn "microsandbox: with_backend could not restore the previous backend: #{e.message}"
|
|
273
|
+
end
|
|
255
274
|
end
|
|
256
275
|
end
|
|
257
276
|
|
data/sig/microsandbox.rbs
CHANGED
|
@@ -64,6 +64,7 @@ module Microsandbox
|
|
|
64
64
|
class MetricsUnavailableError < Error end
|
|
65
65
|
class UnsupportedOperationError < Error end
|
|
66
66
|
class CloudHttpError < Error end
|
|
67
|
+
class ForkedHandleError < Error end
|
|
67
68
|
class UnsupportedError < Error
|
|
68
69
|
def operation: () -> String?
|
|
69
70
|
def hint: () -> String?
|
|
@@ -82,6 +83,7 @@ module Microsandbox
|
|
|
82
83
|
end
|
|
83
84
|
|
|
84
85
|
class FsEntry
|
|
86
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
85
87
|
def path: () -> String
|
|
86
88
|
def name: () -> String
|
|
87
89
|
def type: () -> Symbol
|
|
@@ -94,6 +96,7 @@ module Microsandbox
|
|
|
94
96
|
end
|
|
95
97
|
|
|
96
98
|
class FsMetadata
|
|
99
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
97
100
|
def type: () -> Symbol
|
|
98
101
|
def size: () -> Integer
|
|
99
102
|
def mode: () -> Integer
|
|
@@ -106,6 +109,7 @@ module Microsandbox
|
|
|
106
109
|
end
|
|
107
110
|
|
|
108
111
|
class FS
|
|
112
|
+
def initialize: (untyped native) -> void
|
|
109
113
|
def read: (String path) -> String
|
|
110
114
|
def read_text: (String path) -> String
|
|
111
115
|
def write: (String path, String data) -> nil
|
|
@@ -125,17 +129,20 @@ module Microsandbox
|
|
|
125
129
|
|
|
126
130
|
class FsReadStream
|
|
127
131
|
include Enumerable[String]
|
|
132
|
+
def initialize: (untyped native) -> void
|
|
128
133
|
def each: () { (String) -> void } -> self
|
|
129
134
|
| () -> Enumerator[String, self]
|
|
130
135
|
def read: () -> String
|
|
131
136
|
end
|
|
132
137
|
|
|
133
138
|
class FsWriteSink
|
|
139
|
+
def initialize: (untyped native) -> void
|
|
134
140
|
def write: (String data) -> self
|
|
135
141
|
def close: () -> nil
|
|
136
142
|
end
|
|
137
143
|
|
|
138
144
|
class Metrics
|
|
145
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
139
146
|
def cpu_percent: () -> Float
|
|
140
147
|
def vcpu_time_ns: () -> Integer
|
|
141
148
|
def memory_bytes: () -> Integer
|
|
@@ -154,6 +161,7 @@ module Microsandbox
|
|
|
154
161
|
end
|
|
155
162
|
|
|
156
163
|
class LogEntry
|
|
164
|
+
def initialize: (Hash[String, untyped] entry) -> void
|
|
157
165
|
def source: () -> Symbol
|
|
158
166
|
def session_id: () -> Integer?
|
|
159
167
|
def cursor: () -> String
|
|
@@ -163,6 +171,7 @@ module Microsandbox
|
|
|
163
171
|
end
|
|
164
172
|
|
|
165
173
|
class SandboxHandle
|
|
174
|
+
def initialize: (untyped native) -> void
|
|
166
175
|
def name: () -> String
|
|
167
176
|
def id: () -> String
|
|
168
177
|
def status: () -> Symbol
|
|
@@ -200,6 +209,7 @@ module Microsandbox
|
|
|
200
209
|
SandboxInfo: singleton(SandboxHandle)
|
|
201
210
|
|
|
202
211
|
class SandboxStopResult
|
|
212
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
203
213
|
def name: () -> String
|
|
204
214
|
def status: () -> Symbol
|
|
205
215
|
def exit_code: () -> Integer?
|
|
@@ -290,6 +300,7 @@ module Microsandbox
|
|
|
290
300
|
def self.list_with: (?labels: Hash[untyped, untyped], ?limit: Integer?, ?cursor: String?) -> SandboxPage
|
|
291
301
|
def self.remove: (String name) -> nil
|
|
292
302
|
|
|
303
|
+
def initialize: (untyped native) -> void
|
|
293
304
|
def name: () -> String
|
|
294
305
|
def id: () -> String
|
|
295
306
|
def exec: (String command, ?Array[String] args, ?cwd: String?, ?user: String?,
|
|
@@ -354,17 +365,20 @@ module Microsandbox
|
|
|
354
365
|
|
|
355
366
|
class LogStream
|
|
356
367
|
include Enumerable[LogEntry]
|
|
368
|
+
def initialize: (untyped native) -> void
|
|
357
369
|
def each: () { (LogEntry) -> void } -> self
|
|
358
370
|
| () -> Enumerator[LogEntry, self]
|
|
359
371
|
end
|
|
360
372
|
|
|
361
373
|
class MetricsStream
|
|
362
374
|
include Enumerable[Metrics]
|
|
375
|
+
def initialize: (untyped native) -> void
|
|
363
376
|
def each: () { (Metrics) -> void } -> self
|
|
364
377
|
| () -> Enumerator[Metrics, self]
|
|
365
378
|
end
|
|
366
379
|
|
|
367
380
|
class ExecEvent
|
|
381
|
+
def initialize: (Hash[String, untyped] event) -> void
|
|
368
382
|
def type: () -> Symbol
|
|
369
383
|
def pid: () -> Integer?
|
|
370
384
|
def code: () -> Integer?
|
|
@@ -379,18 +393,21 @@ module Microsandbox
|
|
|
379
393
|
end
|
|
380
394
|
|
|
381
395
|
class ExitStatus
|
|
396
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
382
397
|
def exit_code: () -> Integer?
|
|
383
398
|
def success?: () -> bool
|
|
384
399
|
def failure?: () -> bool
|
|
385
400
|
end
|
|
386
401
|
|
|
387
402
|
class ExecStdin
|
|
403
|
+
def initialize: (untyped native) -> void
|
|
388
404
|
def write: (String data) -> self
|
|
389
405
|
def close: () -> nil
|
|
390
406
|
end
|
|
391
407
|
|
|
392
408
|
class ExecHandle
|
|
393
409
|
include Enumerable[ExecEvent]
|
|
410
|
+
def initialize: (untyped native) -> void
|
|
394
411
|
def id: () -> String
|
|
395
412
|
def each: () { (ExecEvent) -> void } -> self
|
|
396
413
|
| () -> Enumerator[ExecEvent, self]
|
|
@@ -403,6 +420,7 @@ module Microsandbox
|
|
|
403
420
|
end
|
|
404
421
|
|
|
405
422
|
class ImageInfo
|
|
423
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
406
424
|
def reference: () -> String
|
|
407
425
|
def size_bytes: () -> Integer?
|
|
408
426
|
def manifest_digest: () -> String?
|
|
@@ -414,6 +432,7 @@ module Microsandbox
|
|
|
414
432
|
end
|
|
415
433
|
|
|
416
434
|
class ImageDetail
|
|
435
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
417
436
|
def handle: () -> ImageInfo
|
|
418
437
|
def config: () -> Hash[String, untyped]?
|
|
419
438
|
def layers: () -> Array[Hash[String, untyped]]
|
|
@@ -421,6 +440,7 @@ module Microsandbox
|
|
|
421
440
|
end
|
|
422
441
|
|
|
423
442
|
class ImagePruneReport
|
|
443
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
424
444
|
def image_refs_removed: () -> Integer
|
|
425
445
|
def manifests_removed: () -> Integer
|
|
426
446
|
def layers_removed: () -> Integer
|
|
@@ -441,6 +461,7 @@ module Microsandbox
|
|
|
441
461
|
end
|
|
442
462
|
|
|
443
463
|
class VolumeInfo
|
|
464
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
444
465
|
def name: () -> String
|
|
445
466
|
def path: () -> String?
|
|
446
467
|
def kind: () -> Symbol?
|
|
@@ -481,6 +502,7 @@ module Microsandbox
|
|
|
481
502
|
end
|
|
482
503
|
|
|
483
504
|
class SnapshotInfo
|
|
505
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
484
506
|
def digest: () -> String
|
|
485
507
|
def path: () -> String
|
|
486
508
|
def name: () -> String?
|
|
@@ -506,6 +528,7 @@ module Microsandbox
|
|
|
506
528
|
end
|
|
507
529
|
|
|
508
530
|
class SnapshotVerifyReport
|
|
531
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
509
532
|
def digest: () -> String
|
|
510
533
|
def path: () -> String
|
|
511
534
|
def status: () -> Symbol
|
|
@@ -569,6 +592,7 @@ module Microsandbox
|
|
|
569
592
|
end
|
|
570
593
|
|
|
571
594
|
class BackendInfo
|
|
595
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
572
596
|
def kind: () -> Symbol
|
|
573
597
|
def api_url: () -> String?
|
|
574
598
|
def source: () -> Symbol
|
|
@@ -643,6 +667,7 @@ module Microsandbox
|
|
|
643
667
|
|
|
644
668
|
class AgentStream
|
|
645
669
|
include Enumerable[AgentFrame]
|
|
670
|
+
def initialize: (untyped native, Integer id, Integer handle) -> void
|
|
646
671
|
def id: () -> Integer
|
|
647
672
|
def recv: () -> AgentFrame?
|
|
648
673
|
def each: () { (AgentFrame) -> void } -> self
|