microsandbox-rb 0.17.1 → 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 +94 -8
- data/Cargo.lock +1 -1
- data/DESIGN.md +85 -36
- data/README.md +6 -6
- data/SECURITY.md +1 -1
- data/docs/execution.md +8 -1
- data/docs/lifecycle.md +33 -0
- data/docs/releasing.md +85 -34
- data/docs/runtime.md +3 -1
- data/docs/surface.md +13 -8
- 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/image.rs +2 -2
- data/ext/microsandbox/src/lib.rs +2 -2
- data/ext/microsandbox/src/runtime.rs +189 -13
- data/ext/microsandbox/src/sandbox.rs +88 -71
- 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 +18 -16
- data/lib/microsandbox/sandbox.rb +75 -4
- data/lib/microsandbox/version.rb +1 -1
- data/sig/microsandbox.rbs +24 -0
- metadata +1 -1
|
@@ -546,7 +546,7 @@ impl Sandbox {
|
|
|
546
546
|
/// Create and boot a sandbox. `opts` is a string-keyed options Hash.
|
|
547
547
|
fn create(name: String, opts: RHash) -> Result<Sandbox, Error> {
|
|
548
548
|
let b = Self::build_builder(name, opts)?;
|
|
549
|
-
let inner = block_on(b.create())
|
|
549
|
+
let inner = block_on(b.create())?.map_err(error::to_ruby)?;
|
|
550
550
|
Ok(Sandbox::from_inner(inner))
|
|
551
551
|
}
|
|
552
552
|
|
|
@@ -559,7 +559,7 @@ impl Sandbox {
|
|
|
559
559
|
// `create_with_pull_progress` spawns a tokio task, so it must run inside
|
|
560
560
|
// the runtime context even though the call itself is synchronous.
|
|
561
561
|
let (handle, join) =
|
|
562
|
-
block_on(async move { b.create_with_pull_progress() })
|
|
562
|
+
block_on(async move { b.create_with_pull_progress() })?.map_err(error::to_ruby)?;
|
|
563
563
|
Ok(PullSession::new(handle, join))
|
|
564
564
|
}
|
|
565
565
|
|
|
@@ -569,7 +569,7 @@ impl Sandbox {
|
|
|
569
569
|
/// the combination with `replace`.
|
|
570
570
|
fn connect_or_create(name: String, opts: RHash) -> Result<Sandbox, Error> {
|
|
571
571
|
let b = Self::build_builder(name, opts)?;
|
|
572
|
-
let inner = block_on(b.connect_or_create())
|
|
572
|
+
let inner = block_on(b.connect_or_create())?.map_err(error::to_ruby)?;
|
|
573
573
|
Ok(Sandbox::from_inner(inner))
|
|
574
574
|
}
|
|
575
575
|
|
|
@@ -577,9 +577,9 @@ impl Sandbox {
|
|
|
577
577
|
fn start(name: String, opts: RHash) -> Result<Sandbox, Error> {
|
|
578
578
|
let detached = conv::opt_bool(opts, "detached")?;
|
|
579
579
|
let inner = if detached {
|
|
580
|
-
block_on(microsandbox::Sandbox::start_detached(&name))
|
|
580
|
+
block_on(microsandbox::Sandbox::start_detached(&name))?.map_err(error::to_ruby)?
|
|
581
581
|
} else {
|
|
582
|
-
block_on(microsandbox::Sandbox::start(&name))
|
|
582
|
+
block_on(microsandbox::Sandbox::start(&name))?.map_err(error::to_ruby)?
|
|
583
583
|
};
|
|
584
584
|
Ok(Sandbox::from_inner(inner))
|
|
585
585
|
}
|
|
@@ -587,7 +587,7 @@ impl Sandbox {
|
|
|
587
587
|
/// A controllable handle for a sandbox by name (running or not). Carries
|
|
588
588
|
/// metadata accessors and the full lifecycle surface (see `SbHandle`).
|
|
589
589
|
fn get(name: String) -> Result<SbHandle, Error> {
|
|
590
|
-
let handle = block_on(microsandbox::Sandbox::get(&name))
|
|
590
|
+
let handle = block_on(microsandbox::Sandbox::get(&name))?.map_err(error::to_ruby)?;
|
|
591
591
|
Ok(SbHandle::from_inner(handle))
|
|
592
592
|
}
|
|
593
593
|
|
|
@@ -607,7 +607,7 @@ impl Sandbox {
|
|
|
607
607
|
/// First page of sandboxes (default page size), as a page Hash of
|
|
608
608
|
/// controllable handles.
|
|
609
609
|
fn list() -> Result<RHash, Error> {
|
|
610
|
-
let page = block_on(microsandbox::Sandbox::list())
|
|
610
|
+
let page = block_on(microsandbox::Sandbox::list())?.map_err(error::to_ruby)?;
|
|
611
611
|
Self::page_to_hash(page)
|
|
612
612
|
}
|
|
613
613
|
|
|
@@ -626,14 +626,14 @@ impl Sandbox {
|
|
|
626
626
|
b = b.cursor(cursor);
|
|
627
627
|
}
|
|
628
628
|
b.labels(labels)
|
|
629
|
-
}))
|
|
629
|
+
}))?
|
|
630
630
|
.map_err(error::to_ruby)?;
|
|
631
631
|
Self::page_to_hash(page)
|
|
632
632
|
}
|
|
633
633
|
|
|
634
634
|
/// Remove a (stopped) sandbox by name.
|
|
635
635
|
fn remove(name: String) -> Result<(), Error> {
|
|
636
|
-
block_on(microsandbox::Sandbox::remove(&name))
|
|
636
|
+
block_on(microsandbox::Sandbox::remove(&name))?.map_err(error::to_ruby)
|
|
637
637
|
}
|
|
638
638
|
|
|
639
639
|
//----------------------------------------------------------------------
|
|
@@ -655,7 +655,7 @@ impl Sandbox {
|
|
|
655
655
|
/// string-keyed Hash (cwd, user, env, timeout, tty, stdin).
|
|
656
656
|
fn exec(&self, cmd: String, args: Vec<String>, opts: RHash) -> Result<RHash, Error> {
|
|
657
657
|
let parsed = ExecOpts::parse(args, opts)?;
|
|
658
|
-
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)))?
|
|
659
659
|
.map_err(error::to_ruby)?;
|
|
660
660
|
exec_output_to_hash(output)
|
|
661
661
|
}
|
|
@@ -663,7 +663,7 @@ impl Sandbox {
|
|
|
663
663
|
/// Run a shell script (pipes/redirects allowed).
|
|
664
664
|
fn shell(&self, script: String, opts: RHash) -> Result<RHash, Error> {
|
|
665
665
|
let parsed = ExecOpts::parse(Vec::new(), opts)?;
|
|
666
|
-
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)))?
|
|
667
667
|
.map_err(error::to_ruby)?;
|
|
668
668
|
exec_output_to_hash(output)
|
|
669
669
|
}
|
|
@@ -676,7 +676,7 @@ impl Sandbox {
|
|
|
676
676
|
opts: RHash,
|
|
677
677
|
) -> Result<ExecHandle, Error> {
|
|
678
678
|
let parsed = ExecOpts::parse(args, opts)?;
|
|
679
|
-
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)))?
|
|
680
680
|
.map_err(error::to_ruby)?;
|
|
681
681
|
Ok(ExecHandle::from_core(handle))
|
|
682
682
|
}
|
|
@@ -688,7 +688,7 @@ impl Sandbox {
|
|
|
688
688
|
/// NoDefaultCommandError when the image resolves no executable command.
|
|
689
689
|
fn exec_default(&self, opts: RHash) -> Result<RHash, Error> {
|
|
690
690
|
let parsed = ExecOpts::parse(Vec::new(), opts)?;
|
|
691
|
-
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)))?
|
|
692
692
|
.map_err(error::to_ruby)?;
|
|
693
693
|
exec_output_to_hash(output)
|
|
694
694
|
}
|
|
@@ -699,7 +699,7 @@ impl Sandbox {
|
|
|
699
699
|
let handle = block_on(
|
|
700
700
|
self.inner
|
|
701
701
|
.exec_default_stream_with(move |b| parsed.apply(b)),
|
|
702
|
-
)
|
|
702
|
+
)?
|
|
703
703
|
.map_err(error::to_ruby)?;
|
|
704
704
|
Ok(ExecHandle::from_core(handle))
|
|
705
705
|
}
|
|
@@ -710,7 +710,7 @@ impl Sandbox {
|
|
|
710
710
|
let handle = block_on(
|
|
711
711
|
self.inner
|
|
712
712
|
.shell_stream_with(script, move |b| parsed.apply(b)),
|
|
713
|
-
)
|
|
713
|
+
)?
|
|
714
714
|
.map_err(error::to_ruby)?;
|
|
715
715
|
Ok(ExecHandle::from_core(handle))
|
|
716
716
|
}
|
|
@@ -731,29 +731,29 @@ impl Sandbox {
|
|
|
731
731
|
/// Fine-grained control — a custom timeout or fire-and-return `request_*` —
|
|
732
732
|
/// lives on `SandboxHandle`, obtained via `Sandbox.get`.
|
|
733
733
|
fn stop(&self) -> Result<(), Error> {
|
|
734
|
-
block_on(self.inner.stop())
|
|
734
|
+
block_on(self.inner.stop())?.map_err(error::to_ruby)
|
|
735
735
|
}
|
|
736
736
|
|
|
737
737
|
/// Graceful stop, then wait for the process to exit. Returns an exit-status
|
|
738
738
|
/// Hash (`exit_code`, `success`). Local backend only.
|
|
739
739
|
fn stop_and_wait(&self) -> Result<RHash, Error> {
|
|
740
|
-
let status = block_on(self.inner.stop_and_wait())
|
|
740
|
+
let status = block_on(self.inner.stop_and_wait())?.map_err(error::to_ruby)?;
|
|
741
741
|
Ok(exit_status_to_hash(status))
|
|
742
742
|
}
|
|
743
743
|
|
|
744
744
|
/// Force kill (SIGKILL).
|
|
745
745
|
fn kill(&self) -> Result<(), Error> {
|
|
746
|
-
block_on(self.inner.kill())
|
|
746
|
+
block_on(self.inner.kill())?.map_err(error::to_ruby)
|
|
747
747
|
}
|
|
748
748
|
|
|
749
749
|
/// Trigger a graceful drain (SIGUSR1 on local).
|
|
750
750
|
fn drain(&self) -> Result<(), Error> {
|
|
751
|
-
block_on(self.inner.drain())
|
|
751
|
+
block_on(self.inner.drain())?.map_err(error::to_ruby)
|
|
752
752
|
}
|
|
753
753
|
|
|
754
754
|
/// Wait for the process to exit. Returns an exit-status Hash. Local only.
|
|
755
755
|
fn wait(&self) -> Result<RHash, Error> {
|
|
756
|
-
let status = block_on(self.inner.wait())
|
|
756
|
+
let status = block_on(self.inner.wait())?.map_err(error::to_ruby)?;
|
|
757
757
|
Ok(exit_status_to_hash(status))
|
|
758
758
|
}
|
|
759
759
|
|
|
@@ -762,7 +762,7 @@ impl Sandbox {
|
|
|
762
762
|
/// `SandboxReplacedError` instead of silently redirecting the wait.
|
|
763
763
|
fn wait_for_status(&self, status: String) -> Result<SbHandle, Error> {
|
|
764
764
|
let status = sandbox_status_from_str(&status)?;
|
|
765
|
-
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)?;
|
|
766
766
|
Ok(SbHandle::from_inner(handle))
|
|
767
767
|
}
|
|
768
768
|
|
|
@@ -770,19 +770,19 @@ impl Sandbox {
|
|
|
770
770
|
/// `opts` carries `force`/`timeout`/`detached`.
|
|
771
771
|
fn restart(&self, opts: RHash) -> Result<Sandbox, Error> {
|
|
772
772
|
let options = restart_options(opts)?;
|
|
773
|
-
let inner = block_on(self.inner.restart_with(options))
|
|
773
|
+
let inner = block_on(self.inner.restart_with(options))?.map_err(error::to_ruby)?;
|
|
774
774
|
Ok(Sandbox::from_inner(inner))
|
|
775
775
|
}
|
|
776
776
|
|
|
777
777
|
/// Stop and remove this exact sandbox. `opts` carries `force`/`timeout`.
|
|
778
778
|
fn destroy(&self, opts: RHash) -> Result<(), Error> {
|
|
779
779
|
let options = destroy_options(opts)?;
|
|
780
|
-
block_on(self.inner.destroy_with(options))
|
|
780
|
+
block_on(self.inner.destroy_with(options))?.map_err(error::to_ruby)
|
|
781
781
|
}
|
|
782
782
|
|
|
783
783
|
/// Live status fetched from the backend (a round-trip per call).
|
|
784
784
|
fn status(&self) -> Result<String, Error> {
|
|
785
|
-
let status = block_on(self.inner.status())
|
|
785
|
+
let status = block_on(self.inner.status())?.map_err(error::to_ruby)?;
|
|
786
786
|
Ok(sandbox_status_str(status).to_string())
|
|
787
787
|
}
|
|
788
788
|
|
|
@@ -798,13 +798,13 @@ impl Sandbox {
|
|
|
798
798
|
/// detaching a clone disarms the shared handle just the same.
|
|
799
799
|
fn detach(&self) -> Result<(), Error> {
|
|
800
800
|
let inner = self.inner.clone();
|
|
801
|
-
block_on(inner.detach())
|
|
801
|
+
block_on(inner.detach())?;
|
|
802
802
|
Ok(())
|
|
803
803
|
}
|
|
804
804
|
|
|
805
805
|
/// Latest metrics snapshot as a Hash.
|
|
806
806
|
fn metrics(&self) -> Result<RHash, Error> {
|
|
807
|
-
let m = block_on(self.inner.metrics())
|
|
807
|
+
let m = block_on(self.inner.metrics())?.map_err(error::to_ruby)?;
|
|
808
808
|
Ok(metrics_to_hash(&m))
|
|
809
809
|
}
|
|
810
810
|
|
|
@@ -816,14 +816,14 @@ impl Sandbox {
|
|
|
816
816
|
/// `{ name, latency_secs }` (round-trip latency as f64 seconds; the Ruby
|
|
817
817
|
/// layer exposes both seconds and milliseconds).
|
|
818
818
|
fn ping(&self) -> Result<RHash, Error> {
|
|
819
|
-
let result = block_on(self.inner.ping())
|
|
819
|
+
let result = block_on(self.inner.ping())?.map_err(error::to_ruby)?;
|
|
820
820
|
Ok(ping_result_to_hash(&result))
|
|
821
821
|
}
|
|
822
822
|
|
|
823
823
|
/// Explicitly refresh the idle-activity timer. Returns
|
|
824
824
|
/// `{ name, activity_seq }`.
|
|
825
825
|
fn touch(&self) -> Result<RHash, Error> {
|
|
826
|
-
let result = block_on(self.inner.touch())
|
|
826
|
+
let result = block_on(self.inner.touch())?.map_err(error::to_ruby)?;
|
|
827
827
|
Ok(touch_result_to_hash(&result))
|
|
828
828
|
}
|
|
829
829
|
|
|
@@ -840,7 +840,7 @@ impl Sandbox {
|
|
|
840
840
|
/// until_ms, sources (Array of "stdout"/"stderr"/"output"/"system"/"all").
|
|
841
841
|
fn logs(&self, opts: RHash) -> Result<RArray, Error> {
|
|
842
842
|
let log_opts = parse_log_options(opts)?;
|
|
843
|
-
let entries = block_on(self.inner.logs(&log_opts))
|
|
843
|
+
let entries = block_on(self.inner.logs(&log_opts))?.map_err(error::to_ruby)?;
|
|
844
844
|
rhash_array(entries.iter().map(log_entry_to_hash))
|
|
845
845
|
}
|
|
846
846
|
|
|
@@ -860,7 +860,7 @@ impl Sandbox {
|
|
|
860
860
|
// which panics ("no reactor running") unless constructed inside the
|
|
861
861
|
// runtime context — so build it under `block_on`. (`log_stream` is async
|
|
862
862
|
// and already runs inside `block_on`, so it needs no such wrapper.)
|
|
863
|
-
let stream = block_on(async { self.inner.metrics_stream(dur) })
|
|
863
|
+
let stream = block_on(async { self.inner.metrics_stream(dur) })?;
|
|
864
864
|
Ok(MetricsStream::from_stream(stream))
|
|
865
865
|
}
|
|
866
866
|
|
|
@@ -868,7 +868,7 @@ impl Sandbox {
|
|
|
868
868
|
/// from_cursor, until_ms, follow. Returns a LogStream.
|
|
869
869
|
fn log_stream(&self, opts: RHash) -> Result<LogStream, Error> {
|
|
870
870
|
let log_opts = parse_log_stream_options(opts)?;
|
|
871
|
-
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)?;
|
|
872
872
|
Ok(LogStream::from_stream(stream))
|
|
873
873
|
}
|
|
874
874
|
|
|
@@ -878,13 +878,13 @@ impl Sandbox {
|
|
|
878
878
|
|
|
879
879
|
fn fs_read(&self, path: String) -> Result<RString, Error> {
|
|
880
880
|
let fs = self.inner.fs();
|
|
881
|
-
let bytes = block_on(fs.read(&path))
|
|
881
|
+
let bytes = block_on(fs.read(&path))?.map_err(error::to_ruby)?;
|
|
882
882
|
Ok(ruby().str_from_slice(bytes.as_ref()))
|
|
883
883
|
}
|
|
884
884
|
|
|
885
885
|
fn fs_read_text(&self, path: String) -> Result<String, Error> {
|
|
886
886
|
let fs = self.inner.fs();
|
|
887
|
-
block_on(fs.read_to_string(&path))
|
|
887
|
+
block_on(fs.read_to_string(&path))?.map_err(error::to_ruby)
|
|
888
888
|
}
|
|
889
889
|
|
|
890
890
|
fn fs_write(&self, path: String, data: RString) -> Result<(), Error> {
|
|
@@ -892,72 +892,72 @@ impl Sandbox {
|
|
|
892
892
|
// could be moved/freed by GC.compact once block_on releases it.
|
|
893
893
|
let bytes = unsafe { data.as_slice() }.to_vec();
|
|
894
894
|
let fs = self.inner.fs();
|
|
895
|
-
block_on(fs.write(&path, &bytes))
|
|
895
|
+
block_on(fs.write(&path, &bytes))?.map_err(error::to_ruby)
|
|
896
896
|
}
|
|
897
897
|
|
|
898
898
|
fn fs_list(&self, path: String) -> Result<RArray, Error> {
|
|
899
899
|
let fs = self.inner.fs();
|
|
900
|
-
let entries = block_on(fs.list(&path))
|
|
900
|
+
let entries = block_on(fs.list(&path))?.map_err(error::to_ruby)?;
|
|
901
901
|
rhash_array(entries.iter().map(fs_entry_to_hash))
|
|
902
902
|
}
|
|
903
903
|
|
|
904
904
|
fn fs_mkdir(&self, path: String) -> Result<(), Error> {
|
|
905
905
|
let fs = self.inner.fs();
|
|
906
|
-
block_on(fs.mkdir(&path))
|
|
906
|
+
block_on(fs.mkdir(&path))?.map_err(error::to_ruby)
|
|
907
907
|
}
|
|
908
908
|
|
|
909
909
|
fn fs_remove(&self, path: String) -> Result<(), Error> {
|
|
910
910
|
let fs = self.inner.fs();
|
|
911
|
-
block_on(fs.remove(&path))
|
|
911
|
+
block_on(fs.remove(&path))?.map_err(error::to_ruby)
|
|
912
912
|
}
|
|
913
913
|
|
|
914
914
|
fn fs_remove_dir(&self, path: String) -> Result<(), Error> {
|
|
915
915
|
let fs = self.inner.fs();
|
|
916
|
-
block_on(fs.remove_dir(&path))
|
|
916
|
+
block_on(fs.remove_dir(&path))?.map_err(error::to_ruby)
|
|
917
917
|
}
|
|
918
918
|
|
|
919
919
|
fn fs_copy(&self, src: String, dst: String) -> Result<(), Error> {
|
|
920
920
|
let fs = self.inner.fs();
|
|
921
|
-
block_on(fs.copy(&src, &dst))
|
|
921
|
+
block_on(fs.copy(&src, &dst))?.map_err(error::to_ruby)
|
|
922
922
|
}
|
|
923
923
|
|
|
924
924
|
fn fs_rename(&self, src: String, dst: String) -> Result<(), Error> {
|
|
925
925
|
let fs = self.inner.fs();
|
|
926
|
-
block_on(fs.rename(&src, &dst))
|
|
926
|
+
block_on(fs.rename(&src, &dst))?.map_err(error::to_ruby)
|
|
927
927
|
}
|
|
928
928
|
|
|
929
929
|
fn fs_exists(&self, path: String) -> Result<bool, Error> {
|
|
930
930
|
let fs = self.inner.fs();
|
|
931
|
-
block_on(fs.exists(&path))
|
|
931
|
+
block_on(fs.exists(&path))?.map_err(error::to_ruby)
|
|
932
932
|
}
|
|
933
933
|
|
|
934
934
|
fn fs_stat(&self, path: String) -> Result<RHash, Error> {
|
|
935
935
|
let fs = self.inner.fs();
|
|
936
|
-
let meta = block_on(fs.stat(&path))
|
|
936
|
+
let meta = block_on(fs.stat(&path))?.map_err(error::to_ruby)?;
|
|
937
937
|
Ok(fs_metadata_to_hash(&meta))
|
|
938
938
|
}
|
|
939
939
|
|
|
940
940
|
fn fs_copy_from_host(&self, host_path: String, guest_path: String) -> Result<(), Error> {
|
|
941
941
|
let fs = self.inner.fs();
|
|
942
|
-
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)
|
|
943
943
|
}
|
|
944
944
|
|
|
945
945
|
fn fs_copy_to_host(&self, guest_path: String, host_path: String) -> Result<(), Error> {
|
|
946
946
|
let fs = self.inner.fs();
|
|
947
|
-
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)
|
|
948
948
|
}
|
|
949
949
|
|
|
950
950
|
/// Open a streaming reader over a guest file (for files too large to buffer).
|
|
951
951
|
fn fs_read_stream(&self, path: String) -> Result<crate::fs_stream::FsReadStreamHandle, Error> {
|
|
952
952
|
let fs = self.inner.fs();
|
|
953
|
-
let stream = block_on(fs.read_stream(&path))
|
|
953
|
+
let stream = block_on(fs.read_stream(&path))?.map_err(error::to_ruby)?;
|
|
954
954
|
Ok(crate::fs_stream::FsReadStreamHandle::new(stream))
|
|
955
955
|
}
|
|
956
956
|
|
|
957
957
|
/// Open a streaming writer to a guest file.
|
|
958
958
|
fn fs_write_stream(&self, path: String) -> Result<crate::fs_stream::FsWriteSinkHandle, Error> {
|
|
959
959
|
let fs = self.inner.fs();
|
|
960
|
-
let sink = block_on(fs.write_stream(&path))
|
|
960
|
+
let sink = block_on(fs.write_stream(&path))?.map_err(error::to_ruby)?;
|
|
961
961
|
Ok(crate::fs_stream::FsWriteSinkHandle::new(sink))
|
|
962
962
|
}
|
|
963
963
|
|
|
@@ -986,7 +986,7 @@ impl Sandbox {
|
|
|
986
986
|
b = b.inactivity_timeout(t);
|
|
987
987
|
}
|
|
988
988
|
b.sftp(sftp)
|
|
989
|
-
}))
|
|
989
|
+
}))?
|
|
990
990
|
.map_err(error::to_ruby)?;
|
|
991
991
|
Ok(crate::ssh::SshClient::from_core(client))
|
|
992
992
|
}
|
|
@@ -1017,7 +1017,7 @@ impl Sandbox {
|
|
|
1017
1017
|
b = b.inactivity_timeout(t);
|
|
1018
1018
|
}
|
|
1019
1019
|
b.sftp(sftp)
|
|
1020
|
-
}))
|
|
1020
|
+
}))?
|
|
1021
1021
|
.map_err(error::to_ruby)?;
|
|
1022
1022
|
Ok(crate::ssh::SshServer::from_core(server))
|
|
1023
1023
|
}
|
|
@@ -1032,12 +1032,12 @@ impl Sandbox {
|
|
|
1032
1032
|
/// cwd, user, env, detach_keys, rlimits.
|
|
1033
1033
|
fn attach(&self, cmd: String, args: Vec<String>, opts: RHash) -> Result<i32, Error> {
|
|
1034
1034
|
let parsed = AttachOpts::parse(args, opts)?;
|
|
1035
|
-
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)
|
|
1036
1036
|
}
|
|
1037
1037
|
|
|
1038
1038
|
/// Attach an interactive terminal running the sandbox's default shell.
|
|
1039
1039
|
fn attach_shell(&self) -> Result<i32, Error> {
|
|
1040
|
-
block_on(self.inner.attach_shell())
|
|
1040
|
+
block_on(self.inner.attach_shell())?.map_err(error::to_ruby)
|
|
1041
1041
|
}
|
|
1042
1042
|
|
|
1043
1043
|
/// Attach an interactive terminal to the image's resolved OCI ENTRYPOINT
|
|
@@ -1045,7 +1045,7 @@ impl Sandbox {
|
|
|
1045
1045
|
/// cwd, user, env, detach_keys, rlimits.
|
|
1046
1046
|
fn attach_default(&self, opts: RHash) -> Result<i32, Error> {
|
|
1047
1047
|
let parsed = AttachOpts::parse(Vec::new(), opts)?;
|
|
1048
|
-
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)
|
|
1049
1049
|
}
|
|
1050
1050
|
}
|
|
1051
1051
|
|
|
@@ -2373,7 +2373,7 @@ fn run_modify(builder: SandboxModificationBuilder, opts: RHash) -> Result<String
|
|
|
2373
2373
|
} else {
|
|
2374
2374
|
builder.apply().await
|
|
2375
2375
|
}
|
|
2376
|
-
})
|
|
2376
|
+
})?
|
|
2377
2377
|
.map_err(error::to_ruby)?;
|
|
2378
2378
|
serde_json::to_string(&plan)
|
|
2379
2379
|
.map_err(|e| error::base_error(format!("failed to serialize modification plan: {e}")))
|
|
@@ -2601,7 +2601,7 @@ impl PullSession {
|
|
|
2601
2601
|
/// Next progress event as a Hash, or nil when the pull is finished.
|
|
2602
2602
|
fn recv(&self) -> Result<Option<RHash>, Error> {
|
|
2603
2603
|
let progress = Arc::clone(&self.progress);
|
|
2604
|
-
let event = block_on(async move { progress.lock().await.recv().await })
|
|
2604
|
+
let event = block_on(async move { progress.lock().await.recv().await })?;
|
|
2605
2605
|
Ok(event.map(|p| pull_progress_to_hash(&p)))
|
|
2606
2606
|
}
|
|
2607
2607
|
|
|
@@ -2609,9 +2609,20 @@ impl PullSession {
|
|
|
2609
2609
|
/// create task). Consumes the join handle, so it is callable only once.
|
|
2610
2610
|
fn result(&self) -> Result<Sandbox, Error> {
|
|
2611
2611
|
let join = Arc::clone(&self.join);
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
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"))?
|
|
2615
2626
|
.map_err(|e| error::base_error(format!("sandbox creation task failed: {e}")))?
|
|
2616
2627
|
.map_err(error::to_ruby)?;
|
|
2617
2628
|
Ok(Sandbox::from_inner(inner))
|
|
@@ -2663,37 +2674,37 @@ impl SbHandle {
|
|
|
2663
2674
|
|
|
2664
2675
|
/// Graceful stop (SIGTERM→SIGKILL escalation, 10s default) and wait.
|
|
2665
2676
|
fn stop(&self) -> Result<(), Error> {
|
|
2666
|
-
block_on(self.inner.stop())
|
|
2677
|
+
block_on(self.inner.stop())?.map_err(error::to_ruby)
|
|
2667
2678
|
}
|
|
2668
2679
|
|
|
2669
2680
|
/// Graceful stop with a custom escalation timeout (seconds).
|
|
2670
2681
|
fn stop_with_timeout(&self, secs: f64) -> Result<(), Error> {
|
|
2671
|
-
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)
|
|
2672
2683
|
}
|
|
2673
2684
|
|
|
2674
2685
|
/// Force kill (SIGKILL) and wait.
|
|
2675
2686
|
fn kill(&self) -> Result<(), Error> {
|
|
2676
|
-
block_on(self.inner.kill())
|
|
2687
|
+
block_on(self.inner.kill())?.map_err(error::to_ruby)
|
|
2677
2688
|
}
|
|
2678
2689
|
|
|
2679
2690
|
/// Force kill, waiting up to `secs` for the process to disappear.
|
|
2680
2691
|
fn kill_with_timeout(&self, secs: f64) -> Result<(), Error> {
|
|
2681
|
-
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)
|
|
2682
2693
|
}
|
|
2683
2694
|
|
|
2684
2695
|
/// Send the graceful-shutdown request and return without waiting.
|
|
2685
2696
|
fn request_stop(&self) -> Result<(), Error> {
|
|
2686
|
-
block_on(self.inner.request_stop())
|
|
2697
|
+
block_on(self.inner.request_stop())?.map_err(error::to_ruby)
|
|
2687
2698
|
}
|
|
2688
2699
|
|
|
2689
2700
|
/// Send the force-kill request and return without waiting.
|
|
2690
2701
|
fn request_kill(&self) -> Result<(), Error> {
|
|
2691
|
-
block_on(self.inner.request_kill())
|
|
2702
|
+
block_on(self.inner.request_kill())?.map_err(error::to_ruby)
|
|
2692
2703
|
}
|
|
2693
2704
|
|
|
2694
2705
|
/// Send the drain request (SIGUSR1) and return without waiting.
|
|
2695
2706
|
fn request_drain(&self) -> Result<(), Error> {
|
|
2696
|
-
block_on(self.inner.request_drain())
|
|
2707
|
+
block_on(self.inner.request_drain())?.map_err(error::to_ruby)
|
|
2697
2708
|
}
|
|
2698
2709
|
|
|
2699
2710
|
/// Connect when this exact sandbox is running, wait while it is starting,
|
|
@@ -2707,7 +2718,7 @@ impl SbHandle {
|
|
|
2707
2718
|
} else {
|
|
2708
2719
|
self.inner.connect_or_start().await
|
|
2709
2720
|
}
|
|
2710
|
-
})
|
|
2721
|
+
})?
|
|
2711
2722
|
.map_err(error::to_ruby)?;
|
|
2712
2723
|
Ok(Sandbox::from_inner(inner))
|
|
2713
2724
|
}
|
|
@@ -2717,7 +2728,7 @@ impl SbHandle {
|
|
|
2717
2728
|
/// `SandboxReplacedError` instead of silently redirecting the wait.
|
|
2718
2729
|
fn wait_for_status(&self, status: String) -> Result<SbHandle, Error> {
|
|
2719
2730
|
let status = sandbox_status_from_str(&status)?;
|
|
2720
|
-
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)?;
|
|
2721
2732
|
Ok(SbHandle::from_inner(handle))
|
|
2722
2733
|
}
|
|
2723
2734
|
|
|
@@ -2725,20 +2736,20 @@ impl SbHandle {
|
|
|
2725
2736
|
/// `opts` carries `force`/`timeout`/`detached`.
|
|
2726
2737
|
fn restart(&self, opts: RHash) -> Result<Sandbox, Error> {
|
|
2727
2738
|
let options = restart_options(opts)?;
|
|
2728
|
-
let inner = block_on(self.inner.restart_with(options))
|
|
2739
|
+
let inner = block_on(self.inner.restart_with(options))?.map_err(error::to_ruby)?;
|
|
2729
2740
|
Ok(Sandbox::from_inner(inner))
|
|
2730
2741
|
}
|
|
2731
2742
|
|
|
2732
2743
|
/// Stop and remove this exact sandbox. `opts` carries `force`/`timeout`.
|
|
2733
2744
|
fn destroy(&self, opts: RHash) -> Result<(), Error> {
|
|
2734
2745
|
let options = destroy_options(opts)?;
|
|
2735
|
-
block_on(self.inner.destroy_with(options))
|
|
2746
|
+
block_on(self.inner.destroy_with(options))?.map_err(error::to_ruby)
|
|
2736
2747
|
}
|
|
2737
2748
|
|
|
2738
2749
|
/// Block until the sandbox reaches a terminal state; returns a stop-result
|
|
2739
2750
|
/// Hash (name, status, exit_code, signal, observed_at_ms, source).
|
|
2740
2751
|
fn wait_until_stopped(&self) -> Result<RHash, Error> {
|
|
2741
|
-
let result = block_on(self.inner.wait_until_stopped())
|
|
2752
|
+
let result = block_on(self.inner.wait_until_stopped())?.map_err(error::to_ruby)?;
|
|
2742
2753
|
Ok(stop_result_to_hash(&result))
|
|
2743
2754
|
}
|
|
2744
2755
|
|
|
@@ -2755,7 +2766,7 @@ impl SbHandle {
|
|
|
2755
2766
|
/// (`snapshot_to` was removed upstream in v0.6.7 — use `Snapshot.create`
|
|
2756
2767
|
/// with `dest_dir:` for explicit placement.)
|
|
2757
2768
|
fn snapshot(&self, name: String) -> Result<RHash, Error> {
|
|
2758
|
-
let snap = block_on(self.inner.snapshot(&name))
|
|
2769
|
+
let snap = block_on(self.inner.snapshot(&name))?.map_err(error::to_ruby)?;
|
|
2759
2770
|
Ok(crate::snapshot::snapshot_to_hash(&snap))
|
|
2760
2771
|
}
|
|
2761
2772
|
|
|
@@ -2763,14 +2774,14 @@ impl SbHandle {
|
|
|
2763
2774
|
/// sandbox raises `SandboxNotRunningError` (the handle does not start it
|
|
2764
2775
|
/// implicitly). Returns `{ name, latency_secs }`.
|
|
2765
2776
|
fn ping(&self) -> Result<RHash, Error> {
|
|
2766
|
-
let result = block_on(self.inner.ping())
|
|
2777
|
+
let result = block_on(self.inner.ping())?.map_err(error::to_ruby)?;
|
|
2767
2778
|
Ok(ping_result_to_hash(&result))
|
|
2768
2779
|
}
|
|
2769
2780
|
|
|
2770
2781
|
/// Explicitly refresh the idle-activity timer. A stopped sandbox raises
|
|
2771
2782
|
/// `SandboxNotRunningError`. Returns `{ name, activity_seq }`.
|
|
2772
2783
|
fn touch(&self) -> Result<RHash, Error> {
|
|
2773
|
-
let result = block_on(self.inner.touch())
|
|
2784
|
+
let result = block_on(self.inner.touch())?.map_err(error::to_ruby)?;
|
|
2774
2785
|
Ok(touch_result_to_hash(&result))
|
|
2775
2786
|
}
|
|
2776
2787
|
|
|
@@ -2866,6 +2877,8 @@ pub(crate) fn log_entry_to_hash(entry: &LogEntry) -> RHash {
|
|
|
2866
2877
|
|
|
2867
2878
|
pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
2868
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);
|
|
2869
2882
|
|
|
2870
2883
|
class.define_singleton_method("create", function!(Sandbox::create, 2))?;
|
|
2871
2884
|
class.define_singleton_method(
|
|
@@ -2939,6 +2952,8 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
2939
2952
|
class.define_method("attach_shell", method!(Sandbox::attach_shell, 0))?;
|
|
2940
2953
|
|
|
2941
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);
|
|
2942
2957
|
handle.define_method("name", method!(SbHandle::name, 0))?;
|
|
2943
2958
|
handle.define_method("id", method!(SbHandle::id, 0))?;
|
|
2944
2959
|
handle.define_method("status", method!(SbHandle::status, 0))?;
|
|
@@ -2966,6 +2981,8 @@ pub fn define(ruby: &Ruby, native: &RModule) -> Result<(), Error> {
|
|
|
2966
2981
|
handle.define_method("modify", method!(SbHandle::modify, 1))?;
|
|
2967
2982
|
|
|
2968
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);
|
|
2969
2986
|
session.define_method("recv", method!(PullSession::recv, 0))?;
|
|
2970
2987
|
session.define_method("result", method!(PullSession::result, 0))?;
|
|
2971
2988
|
|
|
@@ -110,7 +110,7 @@ fn create(name: String, opts: RHash) -> Result<RHash, Error> {
|
|
|
110
110
|
b = b.resumable();
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
let snap = block_on(b.create())
|
|
113
|
+
let snap = block_on(b.create())?.map_err(error::to_ruby)?;
|
|
114
114
|
Ok(snapshot_to_hash(&snap))
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -119,14 +119,14 @@ fn create(name: String, opts: RHash) -> Result<RHash, Error> {
|
|
|
119
119
|
/// Hash — the only way to inspect an artifact addressed by path (`get`/`list`
|
|
120
120
|
/// read the local index, which path-addressed artifacts are absent from).
|
|
121
121
|
fn open(path_or_name: String) -> Result<RHash, Error> {
|
|
122
|
-
let snap = block_on(Snapshot::open(&path_or_name))
|
|
122
|
+
let snap = block_on(Snapshot::open(&path_or_name))?.map_err(error::to_ruby)?;
|
|
123
123
|
Ok(snapshot_to_hash(&snap))
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/// Walk `dir` and parse each subdirectory's `snapshot.json` without touching
|
|
127
127
|
/// the local index — for enumerating external/un-imported snapshot collections.
|
|
128
128
|
fn list_dir(dir: String) -> Result<RArray, Error> {
|
|
129
|
-
let snaps = block_on(Snapshot::list_dir(Path::new(&dir)))
|
|
129
|
+
let snaps = block_on(Snapshot::list_dir(Path::new(&dir)))?.map_err(error::to_ruby)?;
|
|
130
130
|
let arr = ruby().ary_new();
|
|
131
131
|
for snap in &snaps {
|
|
132
132
|
arr.push(snapshot_to_hash(snap))?;
|
|
@@ -145,19 +145,19 @@ fn reindex(dir: Option<String>) -> Result<u64, Error> {
|
|
|
145
145
|
.map(|l| l.snapshots_dir())
|
|
146
146
|
.unwrap_or_else(|| PathBuf::from(".")),
|
|
147
147
|
};
|
|
148
|
-
let n = block_on(Snapshot::reindex(&dir))
|
|
148
|
+
let n = block_on(Snapshot::reindex(&dir))?.map_err(error::to_ruby)?;
|
|
149
149
|
Ok(n as u64)
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/// Metadata for one snapshot by name or digest.
|
|
153
153
|
fn get(name_or_digest: String) -> Result<RHash, Error> {
|
|
154
|
-
let handle = block_on(Snapshot::get(&name_or_digest))
|
|
154
|
+
let handle = block_on(Snapshot::get(&name_or_digest))?.map_err(error::to_ruby)?;
|
|
155
155
|
Ok(handle_to_hash(&handle))
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/// All snapshots as metadata hashes.
|
|
159
159
|
fn list() -> Result<RArray, Error> {
|
|
160
|
-
let handles = block_on(Snapshot::list())
|
|
160
|
+
let handles = block_on(Snapshot::list())?.map_err(error::to_ruby)?;
|
|
161
161
|
let arr = ruby().ary_new();
|
|
162
162
|
for h in &handles {
|
|
163
163
|
arr.push(handle_to_hash(h))?;
|
|
@@ -167,7 +167,7 @@ fn list() -> Result<RArray, Error> {
|
|
|
167
167
|
|
|
168
168
|
/// Remove a snapshot artifact by name or path.
|
|
169
169
|
fn remove(name_or_path: String, force: bool) -> Result<(), Error> {
|
|
170
|
-
block_on(Snapshot::remove(&name_or_path, force))
|
|
170
|
+
block_on(Snapshot::remove(&name_or_path, force))?.map_err(error::to_ruby)
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
/// Verify a snapshot's recorded upper-layer integrity. Returns
|
|
@@ -176,8 +176,14 @@ fn remove(name_or_path: String, force: bool) -> Result<(), Error> {
|
|
|
176
176
|
/// "verified" (mismatches raise SnapshotIntegrityError), snapshots without
|
|
177
177
|
/// recorded integrity report "not_recorded" with no algorithm/digest keys.
|
|
178
178
|
fn verify(name_or_path: String) -> Result<RHash, Error> {
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
// Open AND verify in ONE `block_on`, so the opened snapshot never sits in
|
|
180
|
+
// this frame between two calls (`block_on` returns `Err` when an interrupt
|
|
181
|
+
// is delivered before it starts; one call leaves no such in-between state).
|
|
182
|
+
let report = block_on(async move {
|
|
183
|
+
let snap = Snapshot::open(&name_or_path).await?;
|
|
184
|
+
snap.verify().await
|
|
185
|
+
})?
|
|
186
|
+
.map_err(error::to_ruby)?;
|
|
181
187
|
Ok(verify_report_to_hash(&report))
|
|
182
188
|
}
|
|
183
189
|
|
|
@@ -193,7 +199,7 @@ fn save(name_or_path: String, out_path: String, opts: RHash) -> Result<(), Error
|
|
|
193
199
|
&name_or_path,
|
|
194
200
|
std::path::Path::new(&out_path),
|
|
195
201
|
save_opts,
|
|
196
|
-
))
|
|
202
|
+
))?
|
|
197
203
|
.map_err(error::to_ruby)
|
|
198
204
|
}
|
|
199
205
|
|
|
@@ -204,7 +210,7 @@ fn load(archive_path: String, dest: Option<String>) -> Result<RHash, Error> {
|
|
|
204
210
|
let handle = block_on(Snapshot::load(
|
|
205
211
|
std::path::Path::new(&archive_path),
|
|
206
212
|
dest_path.as_deref(),
|
|
207
|
-
))
|
|
213
|
+
))?
|
|
208
214
|
.map_err(error::to_ruby)?;
|
|
209
215
|
Ok(handle_to_hash(&handle))
|
|
210
216
|
}
|