use crate::{
artifacts::ArtifactChecksumError, discovery::DiscoveryError, journal::JournalValidationError,
manifest::ManifestValidationError, persistence::PersistenceError, topology::TopologyHash,
};
use std::{
error::Error as StdError,
path::{Path, PathBuf},
};
use thiserror::Error as ThisError;
pub type SnapshotDriverError = Box<dyn StdError + Send + Sync + 'static>;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SnapshotArtifact {
pub canister_id: String,
pub snapshot_id: String,
pub path: PathBuf,
pub checksum: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SnapshotLifecycleMode {
StopBeforeSnapshot,
StopAndResume,
}
impl SnapshotLifecycleMode {
#[must_use]
pub const fn from_resume_flag(resume_after_snapshot: bool) -> Self {
if resume_after_snapshot {
Self::StopAndResume
} else {
Self::StopBeforeSnapshot
}
}
#[must_use]
pub const fn stop_before_snapshot(self) -> bool {
true
}
#[must_use]
pub const fn resume_after_snapshot(self) -> bool {
matches!(self, Self::StopAndResume)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SnapshotDownloadConfig {
pub canister: String,
pub out: PathBuf,
pub root: Option<String>,
pub include_children: bool,
pub recursive: bool,
pub dry_run: bool,
pub lifecycle: SnapshotLifecycleMode,
pub backup_id: String,
pub created_at: String,
pub tool_name: String,
pub tool_version: String,
pub environment: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SnapshotDownloadResult {
pub artifacts: Vec<SnapshotArtifact>,
pub planned_commands: Vec<String>,
}
#[derive(Debug, ThisError)]
pub enum SnapshotDownloadError {
#[error("missing --root when using --include-children")]
MissingRegistrySource,
#[error("snapshot capture requires stopping each canister before snapshot create")]
SnapshotRequiresStoppedCanister,
#[error("snapshot driver failed: {0}")]
Driver(#[source] SnapshotDriverError),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Checksum(#[from] ArtifactChecksumError),
#[error(transparent)]
Persistence(#[from] PersistenceError),
#[error(transparent)]
Journal(#[from] JournalValidationError),
#[error(transparent)]
Discovery(#[from] DiscoveryError),
#[error(transparent)]
Manifest(#[from] SnapshotManifestError),
}
pub trait SnapshotDriver {
fn registry_json(&mut self, root: &str) -> Result<String, SnapshotDriverError>;
fn create_snapshot(&mut self, canister_id: &str) -> Result<String, SnapshotDriverError>;
fn stop_canister(&mut self, canister_id: &str) -> Result<(), SnapshotDriverError>;
fn start_canister(&mut self, canister_id: &str) -> Result<(), SnapshotDriverError>;
fn download_snapshot(
&mut self,
canister_id: &str,
snapshot_id: &str,
artifact_path: &Path,
) -> Result<(), SnapshotDriverError>;
fn create_snapshot_command(&self, canister_id: &str) -> String;
fn stop_canister_command(&self, canister_id: &str) -> String;
fn start_canister_command(&self, canister_id: &str) -> String;
fn download_snapshot_command(
&self,
canister_id: &str,
snapshot_id: &str,
artifact_path: &Path,
) -> String;
}
pub struct SnapshotManifestInput<'a> {
pub backup_id: String,
pub created_at: String,
pub tool_name: String,
pub tool_version: String,
pub environment: String,
pub root_canister: String,
pub selected_canister: String,
pub include_children: bool,
pub targets: &'a [crate::discovery::SnapshotTarget],
pub artifacts: &'a [SnapshotArtifact],
pub discovery_topology_hash: TopologyHash,
pub pre_snapshot_topology_hash: TopologyHash,
}
#[derive(Debug, ThisError)]
pub enum SnapshotManifestError {
#[error("field {field} must be a valid principal: {value}")]
InvalidPrincipal { field: &'static str, value: String },
#[error(
"topology changed before snapshot start: discovery={discovery}, pre_snapshot={pre_snapshot}"
)]
TopologyChanged {
discovery: String,
pre_snapshot: String,
},
#[error("missing snapshot artifact for canister {0}")]
MissingArtifact(String),
#[error(transparent)]
InvalidManifest(#[from] ManifestValidationError),
}