microsandbox-rb 0.17.0 → 0.17.2
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 +229 -8
- data/Cargo.lock +45 -46
- data/DESIGN.md +90 -72
- data/README.md +178 -669
- data/SECURITY.md +65 -0
- data/docs/configuration.md +154 -0
- data/docs/errors.md +74 -0
- data/docs/execution.md +108 -0
- data/docs/filesystem.md +45 -0
- data/docs/images.md +65 -0
- data/docs/lifecycle.md +141 -0
- data/docs/observability.md +99 -0
- data/docs/releasing.md +117 -0
- data/docs/runtime.md +154 -0
- data/docs/surface.md +75 -0
- data/ext/microsandbox/Cargo.toml +9 -5
- data/ext/microsandbox/src/agent.rs +13 -11
- data/ext/microsandbox/src/backend.rs +1 -1
- data/ext/microsandbox/src/exec.rs +12 -8
- data/ext/microsandbox/src/fs_stream.rs +19 -7
- data/ext/microsandbox/src/host_path.rs +108 -0
- data/ext/microsandbox/src/image.rs +2 -2
- data/ext/microsandbox/src/lib.rs +3 -2
- data/ext/microsandbox/src/runtime.rs +189 -13
- data/ext/microsandbox/src/sandbox.rs +149 -102
- data/ext/microsandbox/src/snapshot.rs +17 -11
- data/ext/microsandbox/src/ssh.rs +22 -16
- data/ext/microsandbox/src/stream.rs +6 -2
- data/ext/microsandbox/src/volume.rs +50 -17
- data/lib/microsandbox/fs.rb +4 -8
- data/lib/microsandbox/image.rb +2 -2
- data/lib/microsandbox/log_entry.rb +1 -1
- data/lib/microsandbox/metrics.rb +1 -1
- data/lib/microsandbox/outbound_proxy.rb +8 -0
- data/lib/microsandbox/sandbox.rb +128 -13
- data/lib/microsandbox/snapshot.rb +1 -1
- data/lib/microsandbox/ssh.rb +2 -10
- data/lib/microsandbox/streams.rb +8 -5
- data/lib/microsandbox/version.rb +1 -1
- data/lib/microsandbox/volume.rb +31 -14
- data/lib/microsandbox-rb.rb +7 -0
- data/lib/microsandbox.rb +88 -31
- data/sig/microsandbox.rbs +24 -0
- metadata +14 -1
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
//! as a plain Ruby `Hash`/`Array`/`String` and shaped into value objects by the
|
|
8
8
|
//! Ruby layer.
|
|
9
9
|
|
|
10
|
+
use std::path::PathBuf;
|
|
10
11
|
use std::sync::Arc;
|
|
11
12
|
use std::time::Duration;
|
|
12
13
|
|
|
@@ -39,6 +40,7 @@ use tokio::task::JoinHandle;
|
|
|
39
40
|
use crate::conv;
|
|
40
41
|
use crate::error;
|
|
41
42
|
use crate::exec::ExecHandle;
|
|
43
|
+
use crate::host_path::{self, HostPaths};
|
|
42
44
|
use crate::runtime::{block_on, ruby};
|
|
43
45
|
use crate::stream::{LogStream, MetricsStream};
|
|
44
46
|
|
|
@@ -75,6 +77,9 @@ impl Sandbox {
|
|
|
75
77
|
/// Shared by `create` (blocking) and `create_with_progress` (streaming pull).
|
|
76
78
|
fn build_builder(name: String, opts: RHash) -> Result<SandboxBuilder, Error> {
|
|
77
79
|
let mut b = microsandbox::Sandbox::builder(name);
|
|
80
|
+
// Relative host paths are anchored to the current directory here, once,
|
|
81
|
+
// before the core persists them (see host_path.rs).
|
|
82
|
+
let host_paths = HostPaths::for_default_backend();
|
|
78
83
|
|
|
79
84
|
if let Some(v) = conv::opt_string(opts, "image")? {
|
|
80
85
|
if let Some(fstype) = conv::opt_string(opts, "fstype")? {
|
|
@@ -83,13 +88,17 @@ impl Sandbox {
|
|
|
83
88
|
// image_with(disk().fstype()). (A bare `image` string otherwise
|
|
84
89
|
// auto-detects OCI vs disk by extension.) Errors in disk()/fstype()
|
|
85
90
|
// are captured on the builder and surface at create().
|
|
86
|
-
|
|
91
|
+
let path = host_paths.pin(v, "root disk image path")?;
|
|
92
|
+
b = b.image_with(move |i| i.disk(path).fstype(fstype));
|
|
87
93
|
} else {
|
|
88
|
-
b =
|
|
94
|
+
b = match host_paths.pin_image(v)? {
|
|
95
|
+
host_path::Image::Text(text) => b.image(text),
|
|
96
|
+
host_path::Image::Path(path) => b.image(path),
|
|
97
|
+
};
|
|
89
98
|
}
|
|
90
99
|
}
|
|
91
100
|
if let Some(v) = conv::opt_string(opts, "from_snapshot")? {
|
|
92
|
-
b = b.from_snapshot(v);
|
|
101
|
+
b = b.from_snapshot(host_paths.pin_snapshot(v)?);
|
|
93
102
|
}
|
|
94
103
|
if let Some(v) = conv::opt_u8(opts, "cpus")? {
|
|
95
104
|
b = b.cpus(v);
|
|
@@ -208,27 +217,35 @@ impl Sandbox {
|
|
|
208
217
|
// the per-mount opt-out. Valid for bind/named-directory mounts —
|
|
209
218
|
// the core rejects it elsewhere at build().
|
|
210
219
|
let follow_root_symlinks = conv::opt::<bool>(m, "follow_root_symlinks")?;
|
|
211
|
-
// bind/named/disk require a source; tmpfs must not have one.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
220
|
+
// bind/named/disk require a source; tmpfs must not have one. A
|
|
221
|
+
// bind or disk source is a host path; a named source is a volume
|
|
222
|
+
// name and is never resolved.
|
|
223
|
+
let backing = match (kind.as_str(), source) {
|
|
224
|
+
("bind", Some(host)) => {
|
|
225
|
+
MountBacking::Bind(host_paths.pin(host, "bind mount host path")?)
|
|
226
|
+
}
|
|
227
|
+
("disk", Some(host)) => {
|
|
228
|
+
MountBacking::Disk(host_paths.pin(host, "disk mount host path")?)
|
|
229
|
+
}
|
|
230
|
+
("named", Some(name)) => MountBacking::Named(name),
|
|
231
|
+
("bind" | "named" | "disk", None) => {
|
|
215
232
|
return Err(error::base_error(format!(
|
|
216
233
|
"volume mount kind {kind:?} requires a source"
|
|
217
234
|
)))
|
|
218
235
|
}
|
|
219
|
-
"tmpfs" =>
|
|
220
|
-
other => {
|
|
236
|
+
("tmpfs", _) => MountBacking::Tmpfs,
|
|
237
|
+
(other, _) => {
|
|
221
238
|
return Err(error::base_error(format!(
|
|
222
239
|
"unknown volume mount kind {other:?} (expected bind/named/tmpfs/disk)"
|
|
223
240
|
)))
|
|
224
241
|
}
|
|
225
|
-
}
|
|
242
|
+
};
|
|
226
243
|
b = b.volume(guest, move |mut mb| {
|
|
227
|
-
mb = match
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
244
|
+
mb = match backing {
|
|
245
|
+
MountBacking::Named(name) => mb.named(name),
|
|
246
|
+
MountBacking::Tmpfs => mb.tmpfs(),
|
|
247
|
+
MountBacking::Disk(host) => mb.disk(host),
|
|
248
|
+
MountBacking::Bind(host) => mb.bind(host),
|
|
232
249
|
};
|
|
233
250
|
if let Some(n) = size_mib {
|
|
234
251
|
mb = mb.size(n);
|
|
@@ -272,7 +289,7 @@ impl Sandbox {
|
|
|
272
289
|
// patches: rootfs modifications applied before boot. The Ruby layer
|
|
273
290
|
// normalizes each `Microsandbox::Patch.*` into a string-keyed Hash with
|
|
274
291
|
// a `kind` discriminator; mirrors the Python binding's `apply_patch`.
|
|
275
|
-
for patch in parse_patches(opts)? {
|
|
292
|
+
for patch in parse_patches(opts, host_paths)? {
|
|
276
293
|
b = b.add_patch(patch);
|
|
277
294
|
}
|
|
278
295
|
if let Some(net) = conv::opt_string(opts, "network")? {
|
|
@@ -326,7 +343,7 @@ impl Sandbox {
|
|
|
326
343
|
// layer maps the deprecated `oci_upper_size:` kwarg to the managed kind
|
|
327
344
|
// before it reaches the wire.
|
|
328
345
|
if let Some(v) = conv::opt::<Value>(opts, "root_disk")? {
|
|
329
|
-
let spec = parse_root_disk(v)?;
|
|
346
|
+
let spec = parse_root_disk(v, host_paths)?;
|
|
330
347
|
b = b.root_disk_with(move |d| spec.apply(d));
|
|
331
348
|
}
|
|
332
349
|
// Registry connection settings, for private / non-default registries:
|
|
@@ -374,7 +391,7 @@ impl Sandbox {
|
|
|
374
391
|
.map(parse_dns)
|
|
375
392
|
.transpose()?;
|
|
376
393
|
let tls = conv::opt::<RHash>(opts, "tls")?
|
|
377
|
-
.map(parse_tls)
|
|
394
|
+
.map(|t| parse_tls(t, host_paths))
|
|
378
395
|
.transpose()?;
|
|
379
396
|
let ipv4_pool = conv::opt_string(opts, "ipv4_pool")?
|
|
380
397
|
.map(|s| {
|
|
@@ -529,7 +546,7 @@ impl Sandbox {
|
|
|
529
546
|
/// Create and boot a sandbox. `opts` is a string-keyed options Hash.
|
|
530
547
|
fn create(name: String, opts: RHash) -> Result<Sandbox, Error> {
|
|
531
548
|
let b = Self::build_builder(name, opts)?;
|
|
532
|
-
let inner = block_on(b.create())
|
|
549
|
+
let inner = block_on(b.create())?.map_err(error::to_ruby)?;
|
|
533
550
|
Ok(Sandbox::from_inner(inner))
|
|
534
551
|
}
|
|
535
552
|
|
|
@@ -542,7 +559,7 @@ impl Sandbox {
|
|
|
542
559
|
// `create_with_pull_progress` spawns a tokio task, so it must run inside
|
|
543
560
|
// the runtime context even though the call itself is synchronous.
|
|
544
561
|
let (handle, join) =
|
|
545
|
-
block_on(async move { b.create_with_pull_progress() })
|
|
562
|
+
block_on(async move { b.create_with_pull_progress() })?.map_err(error::to_ruby)?;
|
|
546
563
|
Ok(PullSession::new(handle, join))
|
|
547
564
|
}
|
|
548
565
|
|
|
@@ -552,7 +569,7 @@ impl Sandbox {
|
|
|
552
569
|
/// the combination with `replace`.
|
|
553
570
|
fn connect_or_create(name: String, opts: RHash) -> Result<Sandbox, Error> {
|
|
554
571
|
let b = Self::build_builder(name, opts)?;
|
|
555
|
-
let inner = block_on(b.connect_or_create())
|
|
572
|
+
let inner = block_on(b.connect_or_create())?.map_err(error::to_ruby)?;
|
|
556
573
|
Ok(Sandbox::from_inner(inner))
|
|
557
574
|
}
|
|
558
575
|
|
|
@@ -560,9 +577,9 @@ impl Sandbox {
|
|
|
560
577
|
fn start(name: String, opts: RHash) -> Result<Sandbox, Error> {
|
|
561
578
|
let detached = conv::opt_bool(opts, "detached")?;
|
|
562
579
|
let inner = if detached {
|
|
563
|
-
block_on(microsandbox::Sandbox::start_detached(&name))
|
|
580
|
+
block_on(microsandbox::Sandbox::start_detached(&name))?.map_err(error::to_ruby)?
|
|
564
581
|
} else {
|
|
565
|
-
block_on(microsandbox::Sandbox::start(&name))
|
|
582
|
+
block_on(microsandbox::Sandbox::start(&name))?.map_err(error::to_ruby)?
|
|
566
583
|
};
|
|
567
584
|
Ok(Sandbox::from_inner(inner))
|
|
568
585
|
}
|
|
@@ -570,7 +587,7 @@ impl Sandbox {
|
|
|
570
587
|
/// A controllable handle for a sandbox by name (running or not). Carries
|
|
571
588
|
/// metadata accessors and the full lifecycle surface (see `SbHandle`).
|
|
572
589
|
fn get(name: String) -> Result<SbHandle, Error> {
|
|
573
|
-
let handle = block_on(microsandbox::Sandbox::get(&name))
|
|
590
|
+
let handle = block_on(microsandbox::Sandbox::get(&name))?.map_err(error::to_ruby)?;
|
|
574
591
|
Ok(SbHandle::from_inner(handle))
|
|
575
592
|
}
|
|
576
593
|
|
|
@@ -590,7 +607,7 @@ impl Sandbox {
|
|
|
590
607
|
/// First page of sandboxes (default page size), as a page Hash of
|
|
591
608
|
/// controllable handles.
|
|
592
609
|
fn list() -> Result<RHash, Error> {
|
|
593
|
-
let page = block_on(microsandbox::Sandbox::list())
|
|
610
|
+
let page = block_on(microsandbox::Sandbox::list())?.map_err(error::to_ruby)?;
|
|
594
611
|
Self::page_to_hash(page)
|
|
595
612
|
}
|
|
596
613
|
|
|
@@ -609,14 +626,14 @@ impl Sandbox {
|
|
|
609
626
|
b = b.cursor(cursor);
|
|
610
627
|
}
|
|
611
628
|
b.labels(labels)
|
|
612
|
-
}))
|
|
629
|
+
}))?
|
|
613
630
|
.map_err(error::to_ruby)?;
|
|
614
631
|
Self::page_to_hash(page)
|
|
615
632
|
}
|
|
616
633
|
|
|
617
634
|
/// Remove a (stopped) sandbox by name.
|
|
618
635
|
fn remove(name: String) -> Result<(), Error> {
|
|
619
|
-
block_on(microsandbox::Sandbox::remove(&name))
|
|
636
|
+
block_on(microsandbox::Sandbox::remove(&name))?.map_err(error::to_ruby)
|
|
620
637
|
}
|
|
621
638
|
|
|
622
639
|
//----------------------------------------------------------------------
|
|
@@ -638,7 +655,7 @@ impl Sandbox {
|
|
|
638
655
|
/// string-keyed Hash (cwd, user, env, timeout, tty, stdin).
|
|
639
656
|
fn exec(&self, cmd: String, args: Vec<String>, opts: RHash) -> Result<RHash, Error> {
|
|
640
657
|
let parsed = ExecOpts::parse(args, opts)?;
|
|
641
|
-
let output = block_on(self.inner.exec_with(cmd, move |b| parsed.apply(b)))
|
|
658
|
+
let output = block_on(self.inner.exec_with(cmd, move |b| parsed.apply(b)))?
|
|
642
659
|
.map_err(error::to_ruby)?;
|
|
643
660
|
exec_output_to_hash(output)
|
|
644
661
|
}
|
|
@@ -646,7 +663,7 @@ impl Sandbox {
|
|
|
646
663
|
/// Run a shell script (pipes/redirects allowed).
|
|
647
664
|
fn shell(&self, script: String, opts: RHash) -> Result<RHash, Error> {
|
|
648
665
|
let parsed = ExecOpts::parse(Vec::new(), opts)?;
|
|
649
|
-
let output = block_on(self.inner.shell_with(script, move |b| parsed.apply(b)))
|
|
666
|
+
let output = block_on(self.inner.shell_with(script, move |b| parsed.apply(b)))?
|
|
650
667
|
.map_err(error::to_ruby)?;
|
|
651
668
|
exec_output_to_hash(output)
|
|
652
669
|
}
|
|
@@ -659,7 +676,7 @@ impl Sandbox {
|
|
|
659
676
|
opts: RHash,
|
|
660
677
|
) -> Result<ExecHandle, Error> {
|
|
661
678
|
let parsed = ExecOpts::parse(args, opts)?;
|
|
662
|
-
let handle = block_on(self.inner.exec_stream_with(cmd, move |b| parsed.apply(b)))
|
|
679
|
+
let handle = block_on(self.inner.exec_stream_with(cmd, move |b| parsed.apply(b)))?
|
|
663
680
|
.map_err(error::to_ruby)?;
|
|
664
681
|
Ok(ExecHandle::from_core(handle))
|
|
665
682
|
}
|
|
@@ -671,7 +688,7 @@ impl Sandbox {
|
|
|
671
688
|
/// NoDefaultCommandError when the image resolves no executable command.
|
|
672
689
|
fn exec_default(&self, opts: RHash) -> Result<RHash, Error> {
|
|
673
690
|
let parsed = ExecOpts::parse(Vec::new(), opts)?;
|
|
674
|
-
let output = block_on(self.inner.exec_default_with(move |b| parsed.apply(b)))
|
|
691
|
+
let output = block_on(self.inner.exec_default_with(move |b| parsed.apply(b)))?
|
|
675
692
|
.map_err(error::to_ruby)?;
|
|
676
693
|
exec_output_to_hash(output)
|
|
677
694
|
}
|
|
@@ -682,7 +699,7 @@ impl Sandbox {
|
|
|
682
699
|
let handle = block_on(
|
|
683
700
|
self.inner
|
|
684
701
|
.exec_default_stream_with(move |b| parsed.apply(b)),
|
|
685
|
-
)
|
|
702
|
+
)?
|
|
686
703
|
.map_err(error::to_ruby)?;
|
|
687
704
|
Ok(ExecHandle::from_core(handle))
|
|
688
705
|
}
|
|
@@ -693,7 +710,7 @@ impl Sandbox {
|
|
|
693
710
|
let handle = block_on(
|
|
694
711
|
self.inner
|
|
695
712
|
.shell_stream_with(script, move |b| parsed.apply(b)),
|
|
696
|
-
)
|
|
713
|
+
)?
|
|
697
714
|
.map_err(error::to_ruby)?;
|
|
698
715
|
Ok(ExecHandle::from_core(handle))
|
|
699
716
|
}
|
|
@@ -714,29 +731,29 @@ impl Sandbox {
|
|
|
714
731
|
/// Fine-grained control — a custom timeout or fire-and-return `request_*` —
|
|
715
732
|
/// lives on `SandboxHandle`, obtained via `Sandbox.get`.
|
|
716
733
|
fn stop(&self) -> Result<(), Error> {
|
|
717
|
-
block_on(self.inner.stop())
|
|
734
|
+
block_on(self.inner.stop())?.map_err(error::to_ruby)
|
|
718
735
|
}
|
|
719
736
|
|
|
720
737
|
/// Graceful stop, then wait for the process to exit. Returns an exit-status
|
|
721
738
|
/// Hash (`exit_code`, `success`). Local backend only.
|
|
722
739
|
fn stop_and_wait(&self) -> Result<RHash, Error> {
|
|
723
|
-
let status = block_on(self.inner.stop_and_wait())
|
|
740
|
+
let status = block_on(self.inner.stop_and_wait())?.map_err(error::to_ruby)?;
|
|
724
741
|
Ok(exit_status_to_hash(status))
|
|
725
742
|
}
|
|
726
743
|
|
|
727
744
|
/// Force kill (SIGKILL).
|
|
728
745
|
fn kill(&self) -> Result<(), Error> {
|
|
729
|
-
block_on(self.inner.kill())
|
|
746
|
+
block_on(self.inner.kill())?.map_err(error::to_ruby)
|
|
730
747
|
}
|
|
731
748
|
|
|
732
749
|
/// Trigger a graceful drain (SIGUSR1 on local).
|
|
733
750
|
fn drain(&self) -> Result<(), Error> {
|
|
734
|
-
block_on(self.inner.drain())
|
|
751
|
+
block_on(self.inner.drain())?.map_err(error::to_ruby)
|
|
735
752
|
}
|
|
736
753
|
|
|
737
754
|
/// Wait for the process to exit. Returns an exit-status Hash. Local only.
|
|
738
755
|
fn wait(&self) -> Result<RHash, Error> {
|
|
739
|
-
let status = block_on(self.inner.wait())
|
|
756
|
+
let status = block_on(self.inner.wait())?.map_err(error::to_ruby)?;
|
|
740
757
|
Ok(exit_status_to_hash(status))
|
|
741
758
|
}
|
|
742
759
|
|
|
@@ -745,7 +762,7 @@ impl Sandbox {
|
|
|
745
762
|
/// `SandboxReplacedError` instead of silently redirecting the wait.
|
|
746
763
|
fn wait_for_status(&self, status: String) -> Result<SbHandle, Error> {
|
|
747
764
|
let status = sandbox_status_from_str(&status)?;
|
|
748
|
-
let handle = block_on(self.inner.wait_for_status(status))
|
|
765
|
+
let handle = block_on(self.inner.wait_for_status(status))?.map_err(error::to_ruby)?;
|
|
749
766
|
Ok(SbHandle::from_inner(handle))
|
|
750
767
|
}
|
|
751
768
|
|
|
@@ -753,19 +770,19 @@ impl Sandbox {
|
|
|
753
770
|
/// `opts` carries `force`/`timeout`/`detached`.
|
|
754
771
|
fn restart(&self, opts: RHash) -> Result<Sandbox, Error> {
|
|
755
772
|
let options = restart_options(opts)?;
|
|
756
|
-
let inner = block_on(self.inner.restart_with(options))
|
|
773
|
+
let inner = block_on(self.inner.restart_with(options))?.map_err(error::to_ruby)?;
|
|
757
774
|
Ok(Sandbox::from_inner(inner))
|
|
758
775
|
}
|
|
759
776
|
|
|
760
777
|
/// Stop and remove this exact sandbox. `opts` carries `force`/`timeout`.
|
|
761
778
|
fn destroy(&self, opts: RHash) -> Result<(), Error> {
|
|
762
779
|
let options = destroy_options(opts)?;
|
|
763
|
-
block_on(self.inner.destroy_with(options))
|
|
780
|
+
block_on(self.inner.destroy_with(options))?.map_err(error::to_ruby)
|
|
764
781
|
}
|
|
765
782
|
|
|
766
783
|
/// Live status fetched from the backend (a round-trip per call).
|
|
767
784
|
fn status(&self) -> Result<String, Error> {
|
|
768
|
-
let status = block_on(self.inner.status())
|
|
785
|
+
let status = block_on(self.inner.status())?.map_err(error::to_ruby)?;
|
|
769
786
|
Ok(sandbox_status_str(status).to_string())
|
|
770
787
|
}
|
|
771
788
|
|
|
@@ -781,13 +798,13 @@ impl Sandbox {
|
|
|
781
798
|
/// detaching a clone disarms the shared handle just the same.
|
|
782
799
|
fn detach(&self) -> Result<(), Error> {
|
|
783
800
|
let inner = self.inner.clone();
|
|
784
|
-
block_on(inner.detach())
|
|
801
|
+
block_on(inner.detach())?;
|
|
785
802
|
Ok(())
|
|
786
803
|
}
|
|
787
804
|
|
|
788
805
|
/// Latest metrics snapshot as a Hash.
|
|
789
806
|
fn metrics(&self) -> Result<RHash, Error> {
|
|
790
|
-
let m = block_on(self.inner.metrics())
|
|
807
|
+
let m = block_on(self.inner.metrics())?.map_err(error::to_ruby)?;
|
|
791
808
|
Ok(metrics_to_hash(&m))
|
|
792
809
|
}
|
|
793
810
|
|
|
@@ -799,14 +816,14 @@ impl Sandbox {
|
|
|
799
816
|
/// `{ name, latency_secs }` (round-trip latency as f64 seconds; the Ruby
|
|
800
817
|
/// layer exposes both seconds and milliseconds).
|
|
801
818
|
fn ping(&self) -> Result<RHash, Error> {
|
|
802
|
-
let result = block_on(self.inner.ping())
|
|
819
|
+
let result = block_on(self.inner.ping())?.map_err(error::to_ruby)?;
|
|
803
820
|
Ok(ping_result_to_hash(&result))
|
|
804
821
|
}
|
|
805
822
|
|
|
806
823
|
/// Explicitly refresh the idle-activity timer. Returns
|
|
807
824
|
/// `{ name, activity_seq }`.
|
|
808
825
|
fn touch(&self) -> Result<RHash, Error> {
|
|
809
|
-
let result = block_on(self.inner.touch())
|
|
826
|
+
let result = block_on(self.inner.touch())?.map_err(error::to_ruby)?;
|
|
810
827
|
Ok(touch_result_to_hash(&result))
|
|
811
828
|
}
|
|
812
829
|
|
|
@@ -823,7 +840,7 @@ impl Sandbox {
|
|
|
823
840
|
/// until_ms, sources (Array of "stdout"/"stderr"/"output"/"system"/"all").
|
|
824
841
|
fn logs(&self, opts: RHash) -> Result<RArray, Error> {
|
|
825
842
|
let log_opts = parse_log_options(opts)?;
|
|
826
|
-
let entries = block_on(self.inner.logs(&log_opts))
|
|
843
|
+
let entries = block_on(self.inner.logs(&log_opts))?.map_err(error::to_ruby)?;
|
|
827
844
|
rhash_array(entries.iter().map(log_entry_to_hash))
|
|
828
845
|
}
|
|
829
846
|
|
|
@@ -843,7 +860,7 @@ impl Sandbox {
|
|
|
843
860
|
// which panics ("no reactor running") unless constructed inside the
|
|
844
861
|
// runtime context — so build it under `block_on`. (`log_stream` is async
|
|
845
862
|
// and already runs inside `block_on`, so it needs no such wrapper.)
|
|
846
|
-
let stream = block_on(async { self.inner.metrics_stream(dur) })
|
|
863
|
+
let stream = block_on(async { self.inner.metrics_stream(dur) })?;
|
|
847
864
|
Ok(MetricsStream::from_stream(stream))
|
|
848
865
|
}
|
|
849
866
|
|
|
@@ -851,7 +868,7 @@ impl Sandbox {
|
|
|
851
868
|
/// from_cursor, until_ms, follow. Returns a LogStream.
|
|
852
869
|
fn log_stream(&self, opts: RHash) -> Result<LogStream, Error> {
|
|
853
870
|
let log_opts = parse_log_stream_options(opts)?;
|
|
854
|
-
let stream = block_on(self.inner.log_stream(&log_opts))
|
|
871
|
+
let stream = block_on(self.inner.log_stream(&log_opts))?.map_err(error::to_ruby)?;
|
|
855
872
|
Ok(LogStream::from_stream(stream))
|
|
856
873
|
}
|
|
857
874
|
|
|
@@ -861,13 +878,13 @@ impl Sandbox {
|
|
|
861
878
|
|
|
862
879
|
fn fs_read(&self, path: String) -> Result<RString, Error> {
|
|
863
880
|
let fs = self.inner.fs();
|
|
864
|
-
let bytes = block_on(fs.read(&path))
|
|
881
|
+
let bytes = block_on(fs.read(&path))?.map_err(error::to_ruby)?;
|
|
865
882
|
Ok(ruby().str_from_slice(bytes.as_ref()))
|
|
866
883
|
}
|
|
867
884
|
|
|
868
885
|
fn fs_read_text(&self, path: String) -> Result<String, Error> {
|
|
869
886
|
let fs = self.inner.fs();
|
|
870
|
-
block_on(fs.read_to_string(&path))
|
|
887
|
+
block_on(fs.read_to_string(&path))?.map_err(error::to_ruby)
|
|
871
888
|
}
|
|
872
889
|
|
|
873
890
|
fn fs_write(&self, path: String, data: RString) -> Result<(), Error> {
|
|
@@ -875,72 +892,72 @@ impl Sandbox {
|
|
|
875
892
|
// could be moved/freed by GC.compact once block_on releases it.
|
|
876
893
|
let bytes = unsafe { data.as_slice() }.to_vec();
|
|
877
894
|
let fs = self.inner.fs();
|
|
878
|
-
block_on(fs.write(&path, &bytes))
|
|
895
|
+
block_on(fs.write(&path, &bytes))?.map_err(error::to_ruby)
|
|
879
896
|
}
|
|
880
897
|
|
|
881
898
|
fn fs_list(&self, path: String) -> Result<RArray, Error> {
|
|
882
899
|
let fs = self.inner.fs();
|
|
883
|
-
let entries = block_on(fs.list(&path))
|
|
900
|
+
let entries = block_on(fs.list(&path))?.map_err(error::to_ruby)?;
|
|
884
901
|
rhash_array(entries.iter().map(fs_entry_to_hash))
|
|
885
902
|
}
|
|
886
903
|
|
|
887
904
|
fn fs_mkdir(&self, path: String) -> Result<(), Error> {
|
|
888
905
|
let fs = self.inner.fs();
|
|
889
|
-
block_on(fs.mkdir(&path))
|
|
906
|
+
block_on(fs.mkdir(&path))?.map_err(error::to_ruby)
|
|
890
907
|
}
|
|
891
908
|
|
|
892
909
|
fn fs_remove(&self, path: String) -> Result<(), Error> {
|
|
893
910
|
let fs = self.inner.fs();
|
|
894
|
-
block_on(fs.remove(&path))
|
|
911
|
+
block_on(fs.remove(&path))?.map_err(error::to_ruby)
|
|
895
912
|
}
|
|
896
913
|
|
|
897
914
|
fn fs_remove_dir(&self, path: String) -> Result<(), Error> {
|
|
898
915
|
let fs = self.inner.fs();
|
|
899
|
-
block_on(fs.remove_dir(&path))
|
|
916
|
+
block_on(fs.remove_dir(&path))?.map_err(error::to_ruby)
|
|
900
917
|
}
|
|
901
918
|
|
|
902
919
|
fn fs_copy(&self, src: String, dst: String) -> Result<(), Error> {
|
|
903
920
|
let fs = self.inner.fs();
|
|
904
|
-
block_on(fs.copy(&src, &dst))
|
|
921
|
+
block_on(fs.copy(&src, &dst))?.map_err(error::to_ruby)
|
|
905
922
|
}
|
|
906
923
|
|
|
907
924
|
fn fs_rename(&self, src: String, dst: String) -> Result<(), Error> {
|
|
908
925
|
let fs = self.inner.fs();
|
|
909
|
-
block_on(fs.rename(&src, &dst))
|
|
926
|
+
block_on(fs.rename(&src, &dst))?.map_err(error::to_ruby)
|
|
910
927
|
}
|
|
911
928
|
|
|
912
929
|
fn fs_exists(&self, path: String) -> Result<bool, Error> {
|
|
913
930
|
let fs = self.inner.fs();
|
|
914
|
-
block_on(fs.exists(&path))
|
|
931
|
+
block_on(fs.exists(&path))?.map_err(error::to_ruby)
|
|
915
932
|
}
|
|
916
933
|
|
|
917
934
|
fn fs_stat(&self, path: String) -> Result<RHash, Error> {
|
|
918
935
|
let fs = self.inner.fs();
|
|
919
|
-
let meta = block_on(fs.stat(&path))
|
|
936
|
+
let meta = block_on(fs.stat(&path))?.map_err(error::to_ruby)?;
|
|
920
937
|
Ok(fs_metadata_to_hash(&meta))
|
|
921
938
|
}
|
|
922
939
|
|
|
923
940
|
fn fs_copy_from_host(&self, host_path: String, guest_path: String) -> Result<(), Error> {
|
|
924
941
|
let fs = self.inner.fs();
|
|
925
|
-
block_on(fs.copy_from_host(&host_path, &guest_path))
|
|
942
|
+
block_on(fs.copy_from_host(&host_path, &guest_path))?.map_err(error::to_ruby)
|
|
926
943
|
}
|
|
927
944
|
|
|
928
945
|
fn fs_copy_to_host(&self, guest_path: String, host_path: String) -> Result<(), Error> {
|
|
929
946
|
let fs = self.inner.fs();
|
|
930
|
-
block_on(fs.copy_to_host(&guest_path, &host_path))
|
|
947
|
+
block_on(fs.copy_to_host(&guest_path, &host_path))?.map_err(error::to_ruby)
|
|
931
948
|
}
|
|
932
949
|
|
|
933
950
|
/// Open a streaming reader over a guest file (for files too large to buffer).
|
|
934
951
|
fn fs_read_stream(&self, path: String) -> Result<crate::fs_stream::FsReadStreamHandle, Error> {
|
|
935
952
|
let fs = self.inner.fs();
|
|
936
|
-
let stream = block_on(fs.read_stream(&path))
|
|
953
|
+
let stream = block_on(fs.read_stream(&path))?.map_err(error::to_ruby)?;
|
|
937
954
|
Ok(crate::fs_stream::FsReadStreamHandle::new(stream))
|
|
938
955
|
}
|
|
939
956
|
|
|
940
957
|
/// Open a streaming writer to a guest file.
|
|
941
958
|
fn fs_write_stream(&self, path: String) -> Result<crate::fs_stream::FsWriteSinkHandle, Error> {
|
|
942
959
|
let fs = self.inner.fs();
|
|
943
|
-
let sink = block_on(fs.write_stream(&path))
|
|
960
|
+
let sink = block_on(fs.write_stream(&path))?.map_err(error::to_ruby)?;
|
|
944
961
|
Ok(crate::fs_stream::FsWriteSinkHandle::new(sink))
|
|
945
962
|
}
|
|
946
963
|
|
|
@@ -969,7 +986,7 @@ impl Sandbox {
|
|
|
969
986
|
b = b.inactivity_timeout(t);
|
|
970
987
|
}
|
|
971
988
|
b.sftp(sftp)
|
|
972
|
-
}))
|
|
989
|
+
}))?
|
|
973
990
|
.map_err(error::to_ruby)?;
|
|
974
991
|
Ok(crate::ssh::SshClient::from_core(client))
|
|
975
992
|
}
|
|
@@ -1000,7 +1017,7 @@ impl Sandbox {
|
|
|
1000
1017
|
b = b.inactivity_timeout(t);
|
|
1001
1018
|
}
|
|
1002
1019
|
b.sftp(sftp)
|
|
1003
|
-
}))
|
|
1020
|
+
}))?
|
|
1004
1021
|
.map_err(error::to_ruby)?;
|
|
1005
1022
|
Ok(crate::ssh::SshServer::from_core(server))
|
|
1006
1023
|
}
|
|
@@ -1015,12 +1032,12 @@ impl Sandbox {
|
|
|
1015
1032
|
/// cwd, user, env, detach_keys, rlimits.
|
|
1016
1033
|
fn attach(&self, cmd: String, args: Vec<String>, opts: RHash) -> Result<i32, Error> {
|
|
1017
1034
|
let parsed = AttachOpts::parse(args, opts)?;
|
|
1018
|
-
block_on(self.inner.attach_with(cmd, move |b| parsed.apply(b)))
|
|
1035
|
+
block_on(self.inner.attach_with(cmd, move |b| parsed.apply(b)))?.map_err(error::to_ruby)
|
|
1019
1036
|
}
|
|
1020
1037
|
|
|
1021
1038
|
/// Attach an interactive terminal running the sandbox's default shell.
|
|
1022
1039
|
fn attach_shell(&self) -> Result<i32, Error> {
|
|
1023
|
-
block_on(self.inner.attach_shell())
|
|
1040
|
+
block_on(self.inner.attach_shell())?.map_err(error::to_ruby)
|
|
1024
1041
|
}
|
|
1025
1042
|
|
|
1026
1043
|
/// Attach an interactive terminal to the image's resolved OCI ENTRYPOINT
|
|
@@ -1028,7 +1045,7 @@ impl Sandbox {
|
|
|
1028
1045
|
/// cwd, user, env, detach_keys, rlimits.
|
|
1029
1046
|
fn attach_default(&self, opts: RHash) -> Result<i32, Error> {
|
|
1030
1047
|
let parsed = AttachOpts::parse(Vec::new(), opts)?;
|
|
1031
|
-
block_on(self.inner.attach_default_with(move |b| parsed.apply(b)))
|
|
1048
|
+
block_on(self.inner.attach_default_with(move |b| parsed.apply(b)))?.map_err(error::to_ruby)
|
|
1032
1049
|
}
|
|
1033
1050
|
}
|
|
1034
1051
|
|
|
@@ -1217,7 +1234,7 @@ fn patch_bytes(h: RHash, key: &str) -> Result<Vec<u8>, Error> {
|
|
|
1217
1234
|
/// Parse the `patches` option into core `Patch` operations. The Ruby layer
|
|
1218
1235
|
/// normalizes each `Microsandbox::Patch.*` into a string-keyed Hash carrying a
|
|
1219
1236
|
/// `kind` discriminator plus the variant-specific fields.
|
|
1220
|
-
fn parse_patches(opts: RHash) -> Result<Vec<Patch>, Error> {
|
|
1237
|
+
fn parse_patches(opts: RHash, host_paths: HostPaths) -> Result<Vec<Patch>, Error> {
|
|
1221
1238
|
let mut out = Vec::new();
|
|
1222
1239
|
for h in conv::opt_hash_vec(opts, "patches")? {
|
|
1223
1240
|
let kind = patch_str(h, "kind")?;
|
|
@@ -1241,13 +1258,13 @@ fn parse_patches(opts: RHash) -> Result<Vec<Patch>, Error> {
|
|
|
1241
1258
|
content: patch_str(h, "content")?,
|
|
1242
1259
|
},
|
|
1243
1260
|
"copy_file" => Patch::CopyFile {
|
|
1244
|
-
src: patch_str(h, "src")
|
|
1261
|
+
src: host_paths.pin(patch_str(h, "src")?, "patch source path")?,
|
|
1245
1262
|
dst: patch_str(h, "dst")?,
|
|
1246
1263
|
mode,
|
|
1247
1264
|
replace,
|
|
1248
1265
|
},
|
|
1249
1266
|
"copy_dir" => Patch::CopyDir {
|
|
1250
|
-
src: patch_str(h, "src")
|
|
1267
|
+
src: host_paths.pin(patch_str(h, "src")?, "patch source path")?,
|
|
1251
1268
|
dst: patch_str(h, "dst")?,
|
|
1252
1269
|
replace,
|
|
1253
1270
|
},
|
|
@@ -1584,20 +1601,25 @@ struct TlsSpec {
|
|
|
1584
1601
|
verify_upstream: Option<bool>,
|
|
1585
1602
|
intercepted_ports: Option<Vec<u16>>,
|
|
1586
1603
|
block_quic: Option<bool>,
|
|
1587
|
-
upstream_ca_cert: Option<
|
|
1588
|
-
intercept_ca_cert: Option<
|
|
1589
|
-
intercept_ca_key: Option<
|
|
1604
|
+
upstream_ca_cert: Option<PathBuf>,
|
|
1605
|
+
intercept_ca_cert: Option<PathBuf>,
|
|
1606
|
+
intercept_ca_key: Option<PathBuf>,
|
|
1590
1607
|
}
|
|
1591
1608
|
|
|
1592
|
-
fn parse_tls(t: RHash) -> Result<TlsSpec, Error> {
|
|
1609
|
+
fn parse_tls(t: RHash, host_paths: HostPaths) -> Result<TlsSpec, Error> {
|
|
1610
|
+
let pin = |key: &str, label: &str| {
|
|
1611
|
+
conv::opt_string(t, key)?
|
|
1612
|
+
.map(|path| host_paths.pin(path, label))
|
|
1613
|
+
.transpose()
|
|
1614
|
+
};
|
|
1593
1615
|
Ok(TlsSpec {
|
|
1594
1616
|
bypass: conv::opt_string_vec(t, "bypass")?,
|
|
1595
1617
|
verify_upstream: conv::opt::<bool>(t, "verify_upstream")?,
|
|
1596
1618
|
intercepted_ports: conv::opt::<Vec<u16>>(t, "intercepted_ports")?,
|
|
1597
1619
|
block_quic: conv::opt::<bool>(t, "block_quic")?,
|
|
1598
|
-
upstream_ca_cert:
|
|
1599
|
-
intercept_ca_cert:
|
|
1600
|
-
intercept_ca_key:
|
|
1620
|
+
upstream_ca_cert: pin("upstream_ca_cert", "upstream CA certificate path")?,
|
|
1621
|
+
intercept_ca_cert: pin("intercept_ca_cert", "interception CA certificate path")?,
|
|
1622
|
+
intercept_ca_key: pin("intercept_ca_key", "interception CA key path")?,
|
|
1601
1623
|
})
|
|
1602
1624
|
}
|
|
1603
1625
|
|
|
@@ -1711,6 +1733,14 @@ fn network_profile_from_str(s: &str) -> Result<NetworkProfile, Error> {
|
|
|
1711
1733
|
}
|
|
1712
1734
|
}
|
|
1713
1735
|
|
|
1736
|
+
/// What backs a volume mount, with any host path already pinned.
|
|
1737
|
+
enum MountBacking {
|
|
1738
|
+
Bind(PathBuf),
|
|
1739
|
+
Disk(PathBuf),
|
|
1740
|
+
Named(String),
|
|
1741
|
+
Tmpfs,
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1714
1744
|
/// Parsed `root_disk` create option. Integer shorthand = managed upper of N
|
|
1715
1745
|
/// MiB (mirrors Python's `root_disk=8192` and the CLI bare-size form); Hash =
|
|
1716
1746
|
/// explicit kind. Kind/field cross-validation (size on a disk image, etc.)
|
|
@@ -1726,7 +1756,7 @@ struct RootDiskSpec {
|
|
|
1726
1756
|
enum RootDiskKindSpec {
|
|
1727
1757
|
Managed,
|
|
1728
1758
|
Tmpfs,
|
|
1729
|
-
Disk(
|
|
1759
|
+
Disk(PathBuf),
|
|
1730
1760
|
Flat,
|
|
1731
1761
|
}
|
|
1732
1762
|
|
|
@@ -1754,7 +1784,7 @@ impl RootDiskSpec {
|
|
|
1754
1784
|
}
|
|
1755
1785
|
}
|
|
1756
1786
|
|
|
1757
|
-
fn parse_root_disk(v: Value) -> Result<RootDiskSpec, Error> {
|
|
1787
|
+
fn parse_root_disk(v: Value, host_paths: HostPaths) -> Result<RootDiskSpec, Error> {
|
|
1758
1788
|
if let Ok(mib) = u32::try_convert(v) {
|
|
1759
1789
|
return Ok(RootDiskSpec {
|
|
1760
1790
|
kind: RootDiskKindSpec::Managed,
|
|
@@ -1777,7 +1807,7 @@ fn parse_root_disk(v: Value) -> Result<RootDiskSpec, Error> {
|
|
|
1777
1807
|
let Some(path) = conv::opt_string(h, "path")? else {
|
|
1778
1808
|
return Err(error::base_error("root_disk disk kind requires path:"));
|
|
1779
1809
|
};
|
|
1780
|
-
RootDiskKindSpec::Disk(path)
|
|
1810
|
+
RootDiskKindSpec::Disk(host_paths.pin(path, "root disk image path")?)
|
|
1781
1811
|
}
|
|
1782
1812
|
Some("flat") => RootDiskKindSpec::Flat,
|
|
1783
1813
|
Some(other) => {
|
|
@@ -2343,7 +2373,7 @@ fn run_modify(builder: SandboxModificationBuilder, opts: RHash) -> Result<String
|
|
|
2343
2373
|
} else {
|
|
2344
2374
|
builder.apply().await
|
|
2345
2375
|
}
|
|
2346
|
-
})
|
|
2376
|
+
})?
|
|
2347
2377
|
.map_err(error::to_ruby)?;
|
|
2348
2378
|
serde_json::to_string(&plan)
|
|
2349
2379
|
.map_err(|e| error::base_error(format!("failed to serialize modification plan: {e}")))
|
|
@@ -2571,7 +2601,7 @@ impl PullSession {
|
|
|
2571
2601
|
/// Next progress event as a Hash, or nil when the pull is finished.
|
|
2572
2602
|
fn recv(&self) -> Result<Option<RHash>, Error> {
|
|
2573
2603
|
let progress = Arc::clone(&self.progress);
|
|
2574
|
-
let event = block_on(async move { progress.lock().await.recv().await })
|
|
2604
|
+
let event = block_on(async move { progress.lock().await.recv().await })?;
|
|
2575
2605
|
Ok(event.map(|p| pull_progress_to_hash(&p)))
|
|
2576
2606
|
}
|
|
2577
2607
|
|
|
@@ -2579,9 +2609,20 @@ impl PullSession {
|
|
|
2579
2609
|
/// create task). Consumes the join handle, so it is callable only once.
|
|
2580
2610
|
fn result(&self) -> Result<Sandbox, Error> {
|
|
2581
2611
|
let join = Arc::clone(&self.join);
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2612
|
+
// Take the join handle AND await it in ONE `block_on`. `block_on`
|
|
2613
|
+
// returns `Err` when an interrupt is delivered before it starts; as two
|
|
2614
|
+
// calls, an interrupt between them would drop the taken `JoinHandle`
|
|
2615
|
+
// (which detaches the create task rather than cancelling it) and with
|
|
2616
|
+
// it the only way to reach the sandbox that task boots.
|
|
2617
|
+
let joined = block_on(async move {
|
|
2618
|
+
let taken = join.lock().await.take();
|
|
2619
|
+
match taken {
|
|
2620
|
+
Some(handle) => Some(handle.await),
|
|
2621
|
+
None => None,
|
|
2622
|
+
}
|
|
2623
|
+
})?;
|
|
2624
|
+
let inner = joined
|
|
2625
|
+
.ok_or_else(|| error::base_error("pull session result already taken"))?
|
|
2585
2626
|
.map_err(|e| error::base_error(format!("sandbox creation task failed: {e}")))?
|
|
2586
2627
|
.map_err(error::to_ruby)?;
|
|
2587
2628
|
Ok(Sandbox::from_inner(inner))
|
|
@@ -2633,37 +2674,37 @@ impl SbHandle {
|
|
|
2633
2674
|
|
|
2634
2675
|
/// Graceful stop (SIGTERM→SIGKILL escalation, 10s default) and wait.
|
|
2635
2676
|
fn stop(&self) -> Result<(), Error> {
|
|
2636
|
-
block_on(self.inner.stop())
|
|
2677
|
+
block_on(self.inner.stop())?.map_err(error::to_ruby)
|
|
2637
2678
|
}
|
|
2638
2679
|
|
|
2639
2680
|
/// Graceful stop with a custom escalation timeout (seconds).
|
|
2640
2681
|
fn stop_with_timeout(&self, secs: f64) -> Result<(), Error> {
|
|
2641
|
-
block_on(self.inner.stop_with_timeout(secs_to_duration(secs)?))
|
|
2682
|
+
block_on(self.inner.stop_with_timeout(secs_to_duration(secs)?))?.map_err(error::to_ruby)
|
|
2642
2683
|
}
|
|
2643
2684
|
|
|
2644
2685
|
/// Force kill (SIGKILL) and wait.
|
|
2645
2686
|
fn kill(&self) -> Result<(), Error> {
|
|
2646
|
-
block_on(self.inner.kill())
|
|
2687
|
+
block_on(self.inner.kill())?.map_err(error::to_ruby)
|
|
2647
2688
|
}
|
|
2648
2689
|
|
|
2649
2690
|
/// Force kill, waiting up to `secs` for the process to disappear.
|
|
2650
2691
|
fn kill_with_timeout(&self, secs: f64) -> Result<(), Error> {
|
|
2651
|
-
block_on(self.inner.kill_with_timeout(secs_to_duration(secs)?))
|
|
2692
|
+
block_on(self.inner.kill_with_timeout(secs_to_duration(secs)?))?.map_err(error::to_ruby)
|
|
2652
2693
|
}
|
|
2653
2694
|
|
|
2654
2695
|
/// Send the graceful-shutdown request and return without waiting.
|
|
2655
2696
|
fn request_stop(&self) -> Result<(), Error> {
|
|
2656
|
-
block_on(self.inner.request_stop())
|
|
2697
|
+
block_on(self.inner.request_stop())?.map_err(error::to_ruby)
|
|
2657
2698
|
}
|
|
2658
2699
|
|
|
2659
2700
|
/// Send the force-kill request and return without waiting.
|
|
2660
2701
|
fn request_kill(&self) -> Result<(), Error> {
|
|
2661
|
-
block_on(self.inner.request_kill())
|
|
2702
|
+
block_on(self.inner.request_kill())?.map_err(error::to_ruby)
|
|
2662
2703
|
}
|
|
2663
2704
|
|
|
2664
2705
|
/// Send the drain request (SIGUSR1) and return without waiting.
|
|
2665
2706
|
fn request_drain(&self) -> Result<(), Error> {
|
|
2666
|
-
block_on(self.inner.request_drain())
|
|
2707
|
+
block_on(self.inner.request_drain())?.map_err(error::to_ruby)
|
|
2667
2708
|
}
|
|
2668
2709
|
|
|
2669
2710
|
/// Connect when this exact sandbox is running, wait while it is starting,
|
|
@@ -2677,7 +2718,7 @@ impl SbHandle {
|
|
|
2677
2718
|
} else {
|
|
2678
2719
|
self.inner.connect_or_start().await
|
|
2679
2720
|
}
|
|
2680
|
-
})
|
|
2721
|
+
})?
|
|
2681
2722
|
.map_err(error::to_ruby)?;
|
|
2682
2723
|
Ok(Sandbox::from_inner(inner))
|
|
2683
2724
|
}
|
|
@@ -2687,7 +2728,7 @@ impl SbHandle {
|
|
|
2687
2728
|
/// `SandboxReplacedError` instead of silently redirecting the wait.
|
|
2688
2729
|
fn wait_for_status(&self, status: String) -> Result<SbHandle, Error> {
|
|
2689
2730
|
let status = sandbox_status_from_str(&status)?;
|
|
2690
|
-
let handle = block_on(self.inner.wait_for_status(status))
|
|
2731
|
+
let handle = block_on(self.inner.wait_for_status(status))?.map_err(error::to_ruby)?;
|
|
2691
2732
|
Ok(SbHandle::from_inner(handle))
|
|
2692
2733
|
}
|
|
2693
2734
|
|
|
@@ -2695,20 +2736,20 @@ impl SbHandle {
|
|
|
2695
2736
|
/// `opts` carries `force`/`timeout`/`detached`.
|
|
2696
2737
|
fn restart(&self, opts: RHash) -> Result<Sandbox, Error> {
|
|
2697
2738
|
let options = restart_options(opts)?;
|
|
2698
|
-
let inner = block_on(self.inner.restart_with(options))
|
|
2739
|
+
let inner = block_on(self.inner.restart_with(options))?.map_err(error::to_ruby)?;
|
|
2699
2740
|
Ok(Sandbox::from_inner(inner))
|
|
2700
2741
|
}
|
|
2701
2742
|
|
|
2702
2743
|
/// Stop and remove this exact sandbox. `opts` carries `force`/`timeout`.
|
|
2703
2744
|
fn destroy(&self, opts: RHash) -> Result<(), Error> {
|
|
2704
2745
|
let options = destroy_options(opts)?;
|
|
2705
|
-
block_on(self.inner.destroy_with(options))
|
|
2746
|
+
block_on(self.inner.destroy_with(options))?.map_err(error::to_ruby)
|
|
2706
2747
|
}
|
|
2707
2748
|
|
|
2708
2749
|
/// Block until the sandbox reaches a terminal state; returns a stop-result
|
|
2709
2750
|
/// Hash (name, status, exit_code, signal, observed_at_ms, source).
|
|
2710
2751
|
fn wait_until_stopped(&self) -> Result<RHash, Error> {
|
|
2711
|
-
let result = block_on(self.inner.wait_until_stopped())
|
|
2752
|
+
let result = block_on(self.inner.wait_until_stopped())?.map_err(error::to_ruby)?;
|
|
2712
2753
|
Ok(stop_result_to_hash(&result))
|
|
2713
2754
|
}
|
|
2714
2755
|
|
|
@@ -2725,7 +2766,7 @@ impl SbHandle {
|
|
|
2725
2766
|
/// (`snapshot_to` was removed upstream in v0.6.7 — use `Snapshot.create`
|
|
2726
2767
|
/// with `dest_dir:` for explicit placement.)
|
|
2727
2768
|
fn snapshot(&self, name: String) -> Result<RHash, Error> {
|
|
2728
|
-
let snap = block_on(self.inner.snapshot(&name))
|
|
2769
|
+
let snap = block_on(self.inner.snapshot(&name))?.map_err(error::to_ruby)?;
|
|
2729
2770
|
Ok(crate::snapshot::snapshot_to_hash(&snap))
|
|
2730
2771
|
}
|
|
2731
2772
|
|
|
@@ -2733,14 +2774,14 @@ impl SbHandle {
|
|
|
2733
2774
|
/// sandbox raises `SandboxNotRunningError` (the handle does not start it
|
|
2734
2775
|
/// implicitly). Returns `{ name, latency_secs }`.
|
|
2735
2776
|
fn ping(&self) -> Result<RHash, Error> {
|
|
2736
|
-
let result = block_on(self.inner.ping())
|
|
2777
|
+
let result = block_on(self.inner.ping())?.map_err(error::to_ruby)?;
|
|
2737
2778
|
Ok(ping_result_to_hash(&result))
|
|
2738
2779
|
}
|
|
2739
2780
|
|
|
2740
2781
|
/// Explicitly refresh the idle-activity timer. A stopped sandbox raises
|
|
2741
2782
|
/// `SandboxNotRunningError`. Returns `{ name, activity_seq }`.
|
|
2742
2783
|
fn touch(&self) -> Result<RHash, Error> {
|
|
2743
|
-
let result = block_on(self.inner.touch())
|
|
2784
|
+
let result = block_on(self.inner.touch())?.map_err(error::to_ruby)?;
|
|
2744
2785
|
Ok(touch_result_to_hash(&result))
|
|
2745
2786
|
}
|
|
2746
2787
|
|
|
@@ -2836,6 +2877,8 @@ pub(crate) fn log_entry_to_hash(entry: &LogEntry) -> RHash {
|
|
|
2836
2877
|
|
|
2837
2878
|
pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
2838
2879
|
let class = native.define_class("Sandbox", ruby.class_object())?;
|
|
2880
|
+
// Resolve the wrapper class now, not on first use (see `preload_class`).
|
|
2881
|
+
crate::runtime::preload_class::<Sandbox>(ruby);
|
|
2839
2882
|
|
|
2840
2883
|
class.define_singleton_method("create", function!(Sandbox::create, 2))?;
|
|
2841
2884
|
class.define_singleton_method(
|
|
@@ -2909,6 +2952,8 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
2909
2952
|
class.define_method("attach_shell", method!(Sandbox::attach_shell, 0))?;
|
|
2910
2953
|
|
|
2911
2954
|
let handle = native.define_class("SandboxHandle", ruby.class_object())?;
|
|
2955
|
+
// Resolve the wrapper class now, not on first use (see `preload_class`).
|
|
2956
|
+
crate::runtime::preload_class::<SbHandle>(ruby);
|
|
2912
2957
|
handle.define_method("name", method!(SbHandle::name, 0))?;
|
|
2913
2958
|
handle.define_method("id", method!(SbHandle::id, 0))?;
|
|
2914
2959
|
handle.define_method("status", method!(SbHandle::status, 0))?;
|
|
@@ -2936,6 +2981,8 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
2936
2981
|
handle.define_method("modify", method!(SbHandle::modify, 1))?;
|
|
2937
2982
|
|
|
2938
2983
|
let session = native.define_class("PullSession", ruby.class_object())?;
|
|
2984
|
+
// Resolve the wrapper class now, not on first use (see `preload_class`).
|
|
2985
|
+
crate::runtime::preload_class::<PullSession>(ruby);
|
|
2939
2986
|
session.define_method("recv", method!(PullSession::recv, 0))?;
|
|
2940
2987
|
session.define_method("result", method!(PullSession::result, 0))?;
|
|
2941
2988
|
|