use std::collections::BTreeMap;
use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[cfg(feature = "local")]
use super::DiskLayer;
use super::{
HeadUpdate, LoadOpts, Manifest, SaveOpts, SnapshotConfig, SnapshotCopyBuilder, SnapshotFormat,
SnapshotId, SnapshotScope, SnapshotState, SnapshotVerifyReport,
};
use crate::MicrosandboxResult;
use crate::backend::Backend;
#[derive(Clone)]
pub struct Snapshot {
pub(crate) backend: Arc<dyn Backend>,
pub(crate) reference: SnapshotReference,
pub(crate) digest: String,
pub(crate) manifest: Manifest,
pub(crate) reported_size_bytes: Option<u64>,
pub(crate) labels: BTreeMap<String, String>,
pub(crate) head_update: Option<HeadUpdate>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SnapshotReference {
Auto(String),
Id(String),
Path(String),
}
pub struct SnapshotBuilder {
name: String,
group: Option<String>,
source_sandbox: Option<String>,
dest_dir: Option<PathBuf>,
labels: Vec<(String, String)>,
force: bool,
record_integrity: bool,
full: bool,
}
#[derive(Clone)]
pub struct SnapshotHandle {
pub(crate) backend: Arc<dyn Backend>,
pub(crate) reference: SnapshotReference,
pub(crate) local_path: Option<PathBuf>,
pub(crate) snapshot_id: String,
pub(crate) group: Option<String>,
pub(crate) head_update: Option<HeadUpdate>,
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,
}
#[derive(Debug, Clone)]
pub struct SnapshotArchive {
path: PathBuf,
digest: String,
manifest: Manifest,
labels: BTreeMap<String, String>,
}
impl Snapshot {
pub fn builder(name: impl Into<String>) -> SnapshotBuilder {
SnapshotBuilder {
name: name.into(),
group: None,
source_sandbox: None,
dest_dir: None,
labels: Vec::new(),
force: false,
record_integrity: false,
full: false,
}
}
pub async fn create(config: SnapshotConfig) -> MicrosandboxResult<Self> {
let backend = crate::backend::default_backend();
backend.snapshots().create(backend.clone(), config).await
}
pub async fn open(path_or_name: impl AsRef<str>) -> MicrosandboxResult<Self> {
let reference = SnapshotReference::Auto(path_or_name.as_ref().to_string());
Self::open_ref(reference).await
}
pub async fn open_ref(reference: impl Into<SnapshotReference>) -> MicrosandboxResult<Self> {
let backend = crate::backend::default_backend();
let reference = reference.into();
backend.snapshots().open(backend.clone(), reference).await
}
pub fn reference(&self) -> SnapshotReference {
self.reference.clone()
}
pub fn digest(&self) -> &str {
&self.digest
}
pub fn manifest(&self) -> &Manifest {
&self.manifest
}
pub fn size_bytes(&self) -> Option<u64> {
self.reported_size_bytes.or_else(|| {
self.manifest
.state
.as_file()
.map(|state| state.virtual_size)
})
}
pub fn path(&self) -> MicrosandboxResult<&Path> {
self.backend.snapshots().path(&self.reference)
}
pub fn state(&self) -> &SnapshotState {
&self.manifest.state
}
pub async fn get(name_or_digest: &str) -> MicrosandboxResult<SnapshotHandle> {
let backend = crate::backend::default_backend();
backend
.snapshots()
.get(backend.clone(), name_or_digest)
.await
}
pub async fn list() -> MicrosandboxResult<Vec<SnapshotHandle>> {
let backend = crate::backend::default_backend();
backend.snapshots().list(backend.clone()).await
}
pub async fn remove(path_or_name: &str, force: bool) -> MicrosandboxResult<()> {
let reference = SnapshotReference::Auto(path_or_name.to_string());
Self::remove_ref(reference, force).await
}
pub async fn remove_ref(
reference: impl Into<SnapshotReference>,
force: bool,
) -> MicrosandboxResult<()> {
let backend = crate::backend::default_backend();
let reference = reference.into();
backend
.snapshots()
.remove(backend.clone(), reference, force)
.await
}
}
impl Snapshot {
pub async fn verify(&self) -> MicrosandboxResult<SnapshotVerifyReport> {
self.backend.snapshots().verify(self).await
}
pub fn copy_to(&self, output_archive_path: impl Into<PathBuf>) -> SnapshotCopyBuilder {
SnapshotCopyBuilder::new(self.clone(), output_archive_path.into())
}
pub async fn save_to(&self, out: &Path, opts: SaveOpts) -> MicrosandboxResult<()> {
self.backend
.snapshots()
.save(self.reference(), out, opts)
.await
}
pub async fn list_dir(dir: impl AsRef<Path>) -> MicrosandboxResult<Vec<Snapshot>> {
let backend = crate::backend::default_backend();
backend
.snapshots()
.list_dir(backend.clone(), dir.as_ref().to_path_buf())
.await
}
pub async fn reindex(dir: impl AsRef<Path>) -> MicrosandboxResult<usize> {
let backend = crate::backend::default_backend();
backend
.snapshots()
.reindex(Some(dir.as_ref().to_path_buf()))
.await
}
#[doc(hidden)]
pub async fn reindex_default() -> MicrosandboxResult<usize> {
let backend = crate::backend::default_backend();
backend.snapshots().reindex(None).await
}
pub async fn save(name_or_path: &str, out: &Path, opts: SaveOpts) -> MicrosandboxResult<()> {
let backend = crate::backend::default_backend();
let reference = SnapshotReference::Auto(name_or_path.to_string());
backend.snapshots().save(reference, out, opts).await
}
pub async fn load(
archive_path: &Path,
dest: Option<&Path>,
) -> MicrosandboxResult<SnapshotHandle> {
let backend = crate::backend::default_backend();
backend
.snapshots()
.load(backend.clone(), archive_path, dest)
.await
}
}
impl SnapshotReference {
pub fn auto(value: impl Into<String>) -> Self {
Self::Auto(value.into())
}
pub fn id(id: impl Into<String>) -> Self {
Self::Id(id.into())
}
pub fn path(path: impl Into<String>) -> Self {
Self::Path(path.into())
}
pub fn value(&self) -> &str {
match self {
Self::Auto(value) | Self::Id(value) | Self::Path(value) => value,
}
}
pub const fn kind(&self) -> &'static str {
match self {
Self::Auto(_) => "auto",
Self::Id(_) => "id",
Self::Path(_) => "path",
}
}
}
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 reference(&self) -> SnapshotReference {
self.reference.clone()
}
pub fn path(&self) -> MicrosandboxResult<&Path> {
self.local_path
.as_deref()
.ok_or_else(|| crate::MicrosandboxError::local_only(crate::Operation::SnapshotOps))
}
pub async fn open(&self) -> MicrosandboxResult<Snapshot> {
self.backend
.snapshots()
.open(self.backend.clone(), self.reference())
.await
}
pub async fn remove(&self, force: bool) -> MicrosandboxResult<()> {
self.backend
.snapshots()
.remove(self.backend.clone(), self.reference(), force)
.await
}
pub async fn save_to(&self, out: &Path, opts: SaveOpts) -> MicrosandboxResult<()> {
self.backend
.snapshots()
.save(self.reference(), out, opts)
.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 full(mut self) -> Self {
self.full = 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,
group: self.group,
dest_dir: self.dest_dir,
source_sandbox,
labels: self.labels,
force: self.force,
record_integrity: self.record_integrity,
full: self.full,
})
}
pub async fn create(self) -> MicrosandboxResult<Snapshot> {
Snapshot::create(self.build()?).await
}
}
impl Snapshot {
pub fn id(&self) -> &SnapshotId {
&self.manifest.snapshot_id
}
pub fn labels(&self) -> &BTreeMap<String, String> {
&self.labels
}
pub fn head_update(&self) -> Option<&HeadUpdate> {
self.head_update.as_ref()
}
pub async fn create_archive(
config: SnapshotConfig,
out: impl AsRef<Path>,
plain_tar: bool,
) -> MicrosandboxResult<SnapshotArchive> {
let backend = crate::backend::default_backend();
backend
.snapshots()
.create_archive(config, out.as_ref(), plain_tar)
.await
}
pub async fn load_with_base(
path: &Path,
dest: Option<&Path>,
base: &str,
) -> MicrosandboxResult<SnapshotHandle> {
Self::load_with_options(
path,
LoadOpts {
dest: dest.map(Path::to_path_buf),
base: Some(base.into()),
..Default::default()
},
)
.await
}
pub async fn load_with_options(
path: &Path,
opts: LoadOpts,
) -> MicrosandboxResult<SnapshotHandle> {
let mut handles = Self::load_many(&[path.to_path_buf()], opts).await?;
Ok(handles.remove(0))
}
pub async fn load_many(
paths: &[PathBuf],
opts: LoadOpts,
) -> MicrosandboxResult<Vec<SnapshotHandle>> {
let backend = crate::backend::default_backend();
backend
.snapshots()
.load_many(backend.clone(), paths, opts)
.await
}
pub async fn group_head(selector: &str) -> MicrosandboxResult<HeadUpdate> {
crate::backend::default_backend()
.snapshots()
.group_head(selector)
.await
}
#[cfg(feature = "local")]
pub fn layer_path(&self, layer: &DiskLayer) -> MicrosandboxResult<PathBuf> {
let path = self.path()?;
let canonical = path.join(microsandbox_types::snapshot::layer_path(
&layer.layer_id,
layer.format,
));
Ok(if canonical.exists() {
canonical
} else if self
.manifest
.state
.as_file()
.is_some_and(|file| file.layers.len() == 1)
&& path.join("upper.ext4").exists()
{
path.join("upper.ext4")
} else {
canonical
})
}
}
impl SnapshotHandle {
pub fn id(&self) -> &str {
&self.snapshot_id
}
pub fn group(&self) -> Option<&str> {
self.group.as_deref()
}
pub fn head_update(&self) -> Option<&HeadUpdate> {
self.head_update.as_ref()
}
}
impl SnapshotBuilder {
pub fn group(mut self, group: impl Into<String>) -> Self {
self.group = Some(group.into());
self
}
pub async fn create_archive(
self,
out: impl AsRef<Path>,
plain_tar: bool,
) -> MicrosandboxResult<SnapshotArchive> {
Snapshot::create_archive(self.build()?, out, plain_tar).await
}
}
impl SnapshotArchive {
pub fn id(&self) -> &SnapshotId {
&self.manifest.snapshot_id
}
pub fn descriptor_digest(&self) -> &str {
&self.digest
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn manifest(&self) -> &Manifest {
&self.manifest
}
pub fn labels(&self) -> &BTreeMap<String, String> {
&self.labels
}
}
impl SnapshotArchive {
#[cfg(feature = "local")]
pub(crate) fn from_parts(
path: PathBuf,
digest: String,
manifest: Manifest,
labels: BTreeMap<String, String>,
) -> Self {
Self {
path,
digest,
manifest,
labels,
}
}
}
impl fmt::Debug for Snapshot {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Snapshot")
.field("reference", &self.reference)
.field("digest", &self.digest)
.field("manifest", &self.manifest)
.finish()
}
}
impl fmt::Debug for SnapshotHandle {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SnapshotHandle")
.field("reference", &self.reference)
.field("digest", &self.digest)
.field("name", &self.name)
.finish_non_exhaustive()
}
}
impl From<&Snapshot> for SnapshotReference {
fn from(snapshot: &Snapshot) -> Self {
snapshot.reference()
}
}
impl From<&SnapshotHandle> for SnapshotReference {
fn from(snapshot: &SnapshotHandle) -> Self {
snapshot.reference()
}
}