mod archive;
mod create;
#[doc(hidden)]
pub mod downgrade;
pub(crate) mod migration;
mod store;
mod verify;
use std::path::{Path, PathBuf};
use crate::{MicrosandboxError, MicrosandboxResult};
#[derive(Debug, Clone)]
pub struct Snapshot {
path: PathBuf,
digest: String,
manifest: Manifest,
}
pub struct SnapshotBuilder {
name: String,
source_sandbox: Option<String>,
dest_dir: Option<PathBuf>,
labels: Vec<(String, String)>,
force: bool,
record_integrity: bool,
resumable: bool,
}
impl Snapshot {
pub fn builder(name: impl Into<String>) -> SnapshotBuilder {
SnapshotBuilder {
name: name.into(),
source_sandbox: None,
dest_dir: None,
labels: Vec::new(),
force: false,
record_integrity: false,
resumable: false,
}
}
pub async fn create(config: SnapshotConfig) -> MicrosandboxResult<Self> {
let backend = crate::backend::default_backend();
let local = backend.as_local().ok_or_else(snapshots_require_local)?;
create::create_snapshot(local, config).await
}
pub async fn open(path_or_name: impl AsRef<str>) -> MicrosandboxResult<Self> {
let backend = crate::backend::default_backend();
let local = backend.as_local().ok_or_else(snapshots_require_local)?;
store::open_snapshot(local, path_or_name.as_ref()).await
}
pub async fn verify(&self) -> MicrosandboxResult<SnapshotVerifyReport> {
verify::verify_snapshot(self).await
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn digest(&self) -> &str {
&self.digest
}
pub fn manifest(&self) -> &Manifest {
&self.manifest
}
pub fn size_bytes(&self) -> Option<u64> {
self.manifest
.state
.as_file()
.map(|state| state.upper.size_bytes)
}
pub fn state(&self) -> &SnapshotState {
&self.manifest.state
}
pub async fn get(name_or_digest: &str) -> MicrosandboxResult<SnapshotHandle> {
let backend = crate::backend::default_backend();
let local = backend.as_local().ok_or_else(snapshots_require_local)?;
store::get_handle(local, name_or_digest).await
}
pub async fn list() -> MicrosandboxResult<Vec<SnapshotHandle>> {
let backend = crate::backend::default_backend();
let local = backend.as_local().ok_or_else(snapshots_require_local)?;
store::list_indexed(local).await
}
pub async fn list_dir(dir: impl AsRef<Path>) -> MicrosandboxResult<Vec<Snapshot>> {
let backend = crate::backend::default_backend();
let local = backend.as_local().ok_or_else(snapshots_require_local)?;
store::list_dir(local, dir.as_ref()).await
}
pub async fn remove(path_or_name: &str, force: bool) -> MicrosandboxResult<()> {
let backend = crate::backend::default_backend();
let local = backend.as_local().ok_or_else(snapshots_require_local)?;
store::remove_snapshot(local, path_or_name, force).await
}
pub async fn reindex(dir: impl AsRef<Path>) -> MicrosandboxResult<usize> {
let backend = crate::backend::default_backend();
let local = backend.as_local().ok_or_else(snapshots_require_local)?;
store::reindex_dir(local, dir.as_ref()).await
}
pub async fn save(
name_or_path: &str,
out: &Path,
opts: archive::SaveOpts,
) -> MicrosandboxResult<()> {
let backend = crate::backend::default_backend();
let local = backend.as_local().ok_or_else(snapshots_require_local)?;
archive::save_snapshot(local, name_or_path, out, opts).await
}
pub async fn load(
archive_path: &Path,
dest: Option<&Path>,
) -> MicrosandboxResult<SnapshotHandle> {
let backend = crate::backend::default_backend();
let local = backend.as_local().ok_or_else(snapshots_require_local)?;
archive::load_snapshot(local, archive_path, dest).await
}
}
fn snapshots_require_local() -> MicrosandboxError {
MicrosandboxError::Unsupported {
feature: "Snapshot operations".into(),
available_when: "when cloud snapshots land".into(),
}
}
#[derive(Debug, Clone)]
pub struct SnapshotHandle {
pub(crate) digest: String,
pub(crate) name: Option<String>,
pub(crate) parent_digest: Option<String>,
pub(crate) scope: SnapshotScope,
pub(crate) image_ref: String,
pub(crate) state_kind: String,
pub(crate) format: Option<SnapshotFormat>,
pub(crate) fstype: Option<String>,
pub(crate) checkpoint_manifest_digest: Option<String>,
pub(crate) size_bytes: Option<u64>,
pub(crate) locality: String,
pub(crate) availability: String,
pub(crate) migration_state: String,
pub(crate) migration_error_code: Option<String>,
pub(crate) created_at: chrono::NaiveDateTime,
pub(crate) artifact_path: PathBuf,
}
impl SnapshotHandle {
pub fn digest(&self) -> &str {
&self.digest
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn parent_digest(&self) -> Option<&str> {
self.parent_digest.as_deref()
}
pub fn scope(&self) -> SnapshotScope {
self.scope
}
pub fn image_ref(&self) -> &str {
&self.image_ref
}
pub fn state_kind(&self) -> &str {
&self.state_kind
}
pub fn format(&self) -> Option<SnapshotFormat> {
self.format
}
pub fn fstype(&self) -> Option<&str> {
self.fstype.as_deref()
}
pub fn checkpoint_manifest_digest(&self) -> Option<&str> {
self.checkpoint_manifest_digest.as_deref()
}
pub fn size_bytes(&self) -> Option<u64> {
self.size_bytes
}
pub fn locality(&self) -> &str {
&self.locality
}
pub fn availability(&self) -> &str {
&self.availability
}
pub fn migration_state(&self) -> &str {
&self.migration_state
}
pub fn migration_error_code(&self) -> Option<&str> {
self.migration_error_code.as_deref()
}
pub fn created_at(&self) -> chrono::NaiveDateTime {
self.created_at
}
pub fn path(&self) -> &Path {
&self.artifact_path
}
pub async fn open(&self) -> MicrosandboxResult<Snapshot> {
Snapshot::open(self.artifact_path.to_string_lossy().as_ref()).await
}
pub async fn remove(&self, force: bool) -> MicrosandboxResult<()> {
Snapshot::remove(&self.digest, force).await
}
}
impl SnapshotBuilder {
pub fn from_sandbox(mut self, source_sandbox: impl Into<String>) -> Self {
self.source_sandbox = Some(source_sandbox.into());
self
}
pub fn dest_dir(mut self, dest_dir: impl Into<PathBuf>) -> Self {
self.dest_dir = Some(dest_dir.into());
self
}
pub fn label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.labels.push((key.into(), value.into()));
self
}
pub fn force(mut self) -> Self {
self.force = true;
self
}
pub fn record_integrity(mut self) -> Self {
self.record_integrity = true;
self
}
pub fn resumable(mut self) -> Self {
self.resumable = true;
self
}
pub fn build(self) -> MicrosandboxResult<SnapshotConfig> {
let source_sandbox = self.source_sandbox.ok_or_else(|| {
crate::MicrosandboxError::InvalidConfig(
"snapshot builder requires a source sandbox; set from_sandbox before create".into(),
)
})?;
Ok(SnapshotConfig {
name: self.name,
dest_dir: self.dest_dir,
source_sandbox,
labels: self.labels,
force: self.force,
record_integrity: self.record_integrity,
resumable: self.resumable,
})
}
pub async fn create(self) -> MicrosandboxResult<Snapshot> {
Snapshot::create(self.build()?).await
}
}
pub use archive::SaveOpts;
#[cfg(feature = "fuzzing")]
pub use archive::fuzz_unpack_archive;
pub use microsandbox_image::snapshot::{
CheckpointSnapshotState, DESCRIPTOR_FILENAME, FileSnapshotState, ImageRef, Manifest,
SnapshotDescriptor, SnapshotFormat, SnapshotScope, SnapshotState, UpperIntegrity, UpperLayer,
};
pub use microsandbox_types::{SnapshotSpec, SnapshotSpec as SnapshotConfig};
pub use verify::{SnapshotVerifyReport, UpperVerifyStatus};
impl Snapshot {
pub(crate) fn from_parts(path: PathBuf, digest: String, manifest: Manifest) -> Self {
Self {
path,
digest,
manifest,
}
}
}