use crate::client::Client;
use crate::error::SailError;
use crate::exec::{ExecOptions, ExecProcess, ExecResult, RunOptions};
use crate::sailbox::api::UpgradeResult;
use crate::sailbox::fs::DirEntry;
use crate::sailbox::ssh::{EnableSshOptions, SshEndpoint};
use crate::sailbox::types::{
AutoSleep, CheckpointOptions, IngressProtocol, SailboxCheckpoint, SailboxHandle, SailboxInfo,
WaitForListenerOptions,
};
use crate::worker::{FileReader, FileWriter, Listener, WriteOptions};
use std::sync::{Arc, RwLock};
use time::OffsetDateTime;
fn collect_argv(argv: impl IntoIterator<Item = impl Into<String>>) -> Vec<String> {
argv.into_iter().map(Into::into).collect()
}
#[derive(Clone)]
pub struct Sailbox {
client: Client,
handle: SailboxHandle,
exec_endpoint: Arc<RwLock<String>>,
}
impl std::fmt::Debug for Sailbox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Sailbox")
.field("sailbox_id", &self.handle.sailbox_id)
.finish_non_exhaustive()
}
}
impl Sailbox {
pub(crate) fn bind(client: Client, handle: SailboxHandle) -> Sailbox {
let exec_endpoint = Arc::new(RwLock::new(handle.exec_endpoint.clone()));
Sailbox {
client,
handle,
exec_endpoint,
}
}
pub(crate) fn client(&self) -> &Client {
&self.client
}
pub fn sailbox_id(&self) -> &str {
&self.handle.sailbox_id
}
pub fn handle(&self) -> &SailboxHandle {
&self.handle
}
pub fn into_handle(self) -> SailboxHandle {
self.handle
}
fn exec_endpoint_hint(&self) -> String {
self.exec_endpoint
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
fn set_exec_endpoint_hint(&self, endpoint: String) {
*self
.exec_endpoint
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner) = endpoint;
}
fn clear_exec_endpoint_hint(&self) {
self.set_exec_endpoint_hint(String::new());
}
pub async fn info(&self) -> Result<SailboxInfo, SailError> {
self.client.get_sailbox(self.sailbox_id()).await
}
pub async fn terminate(&self) -> Result<(), SailError> {
self.client.terminate_sailbox(self.sailbox_id()).await?;
self.clear_exec_endpoint_hint();
Ok(())
}
pub async fn pause(&self) -> Result<(), SailError> {
self.client.pause_sailbox(self.sailbox_id()).await?;
self.clear_exec_endpoint_hint();
Ok(())
}
pub async fn sleep(
&self,
wake_at: Option<OffsetDateTime>,
) -> Result<Option<OffsetDateTime>, SailError> {
let effective = self
.client
.sleep_sailbox(self.sailbox_id(), wake_at)
.await?;
self.clear_exec_endpoint_hint();
Ok(effective)
}
pub async fn set_auto_sleep(&self, auto_sleep: AutoSleep) -> Result<(), SailError> {
self.client
.set_sailbox_auto_sleep(self.sailbox_id(), auto_sleep)
.await
}
pub async fn resume(&self) -> Result<(), SailError> {
let handle = self.client.resume_sailbox(self.sailbox_id()).await?;
self.set_exec_endpoint_hint(handle.exec_endpoint);
Ok(())
}
pub async fn checkpoint(
&self,
options: CheckpointOptions,
) -> Result<SailboxCheckpoint, SailError> {
self.client
.checkpoint_sailbox(
self.sailbox_id(),
options.name.as_deref(),
options.ttl.map(crate::client::duration_to_whole_seconds),
)
.await
}
pub async fn upgrade(&self) -> Result<UpgradeResult, SailError> {
self.client.upgrade_sailbox(self.sailbox_id()).await
}
pub async fn exec(
&self,
argv: impl IntoIterator<Item = impl Into<String>>,
options: ExecOptions,
) -> Result<ExecProcess, SailError> {
let exec_endpoint = self.exec_endpoint_hint();
self.client
.exec_at_endpoint(
self.sailbox_id(),
Some(&exec_endpoint),
collect_argv(argv),
options,
)
.await
}
pub async fn exec_shell(
&self,
command: &str,
options: ExecOptions,
) -> Result<ExecProcess, SailError> {
let exec_endpoint = self.exec_endpoint_hint();
self.client
.exec_shell_at_endpoint(self.sailbox_id(), Some(&exec_endpoint), command, options)
.await
}
pub async fn run(
&self,
argv: impl IntoIterator<Item = impl Into<String>>,
options: RunOptions,
) -> Result<ExecResult, SailError> {
self.exec(argv, options.into_exec_options())
.await?
.wait()
.await
}
pub async fn run_shell(
&self,
command: &str,
options: RunOptions,
) -> Result<ExecResult, SailError> {
self.exec_shell(command, options.into_exec_options())
.await?
.wait()
.await
}
pub fn fs(&self) -> SailboxFs<'_> {
SailboxFs { sailbox: self }
}
pub async fn expose(
&self,
guest_port: u32,
protocol: IngressProtocol,
allowlist: &[String],
) -> Result<Listener, SailError> {
self.client
.expose_listener(self.sailbox_id(), guest_port, protocol, allowlist)
.await
}
pub async fn unexpose(&self, guest_port: u32) -> Result<(), SailError> {
self.client
.unexpose_listener(self.sailbox_id(), guest_port)
.await
}
pub async fn listeners(&self) -> Result<Vec<Listener>, SailError> {
self.client.list_listeners(self.sailbox_id()).await
}
pub async fn listener(&self, guest_port: u32) -> Result<Listener, SailError> {
self.client
.get_listener(self.sailbox_id(), guest_port)
.await
}
pub async fn wait_for_listener(
&self,
guest_port: u32,
options: WaitForListenerOptions,
) -> Result<Listener, SailError> {
self.client
.wait_for_listener(self.sailbox_id(), guest_port, options.timeout)
.await
}
pub async fn ingress_auth_headers(&self) -> Result<Vec<(String, String)>, SailError> {
self.client.ingress_auth_headers(self.sailbox_id()).await
}
pub async fn enable_ssh(
&self,
options: EnableSshOptions,
) -> Result<Option<SshEndpoint>, SailError> {
self.client
.enable_ssh(
self.sailbox_id(),
&options.allowlist,
options.wait,
options.timeout,
)
.await
}
}
pub struct SailboxFs<'a> {
sailbox: &'a Sailbox,
}
impl std::fmt::Debug for SailboxFs<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SailboxFs")
.field("sailbox_id", &self.sailbox.sailbox_id())
.finish()
}
}
impl SailboxFs<'_> {
pub async fn read(&self, path: &str) -> Result<Vec<u8>, SailError> {
self.sailbox
.client
.read_file(self.sailbox.sailbox_id(), path)
.await
}
pub async fn write(
&self,
path: &str,
data: &[u8],
options: WriteOptions,
) -> Result<(), SailError> {
self.sailbox
.client
.write_file(self.sailbox.sailbox_id(), path, data, options)
.await
}
pub async fn read_stream(&self, path: &str) -> Result<FileReader, SailError> {
self.sailbox
.client
.read_stream(self.sailbox.sailbox_id(), path)
.await
}
pub async fn write_stream(
&self,
path: &str,
options: WriteOptions,
) -> Result<FileWriter, SailError> {
self.sailbox
.client
.write_stream(self.sailbox.sailbox_id(), path, options)
.await
}
pub async fn mkdir(&self, path: &str) -> Result<(), SailError> {
self.sailbox
.client
.make_dir(self.sailbox.sailbox_id(), path)
.await
}
pub async fn remove(&self, path: &str) -> Result<(), SailError> {
self.sailbox
.client
.remove_path(self.sailbox.sailbox_id(), path)
.await
}
pub async fn exists(&self, path: &str) -> Result<bool, SailError> {
self.sailbox
.client
.path_exists(self.sailbox.sailbox_id(), path)
.await
}
pub async fn ls(&self, path: &str) -> Result<Vec<DirEntry>, SailError> {
self.sailbox
.client
.list_dir(self.sailbox.sailbox_id(), path)
.await
}
}
impl Client {
pub fn sailbox(&self, sailbox_id: impl Into<String>) -> Sailbox {
Sailbox::bind(
self.clone(),
SailboxHandle {
sailbox_id: sailbox_id.into(),
..Default::default()
},
)
}
}