1use crate::{
2 artifacts::ArtifactChecksumError, discovery::DiscoveryError, journal::JournalValidationError,
3 manifest::ManifestValidationError, persistence::PersistenceError, registry::RegistryEntry,
4 topology::TopologyHash,
5};
6use std::{
7 error::Error as StdError,
8 path::{Path, PathBuf},
9};
10use thiserror::Error as ThisError;
11
12pub type SnapshotDriverError = Box<dyn StdError + Send + Sync + 'static>;
13
14#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct SnapshotArtifact {
20 pub canister_id: String,
21 pub snapshot_id: String,
22 pub path: PathBuf,
23 pub checksum: String,
24}
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub enum SnapshotLifecycleMode {
32 StopBeforeSnapshot,
33 StopAndResume,
34}
35
36impl SnapshotLifecycleMode {
37 #[must_use]
39 pub const fn from_resume_flag(resume_after_snapshot: bool) -> Self {
40 if resume_after_snapshot {
41 Self::StopAndResume
42 } else {
43 Self::StopBeforeSnapshot
44 }
45 }
46
47 #[must_use]
49 pub const fn stop_before_snapshot(self) -> bool {
50 true
51 }
52
53 #[must_use]
55 pub const fn resume_after_snapshot(self) -> bool {
56 matches!(self, Self::StopAndResume)
57 }
58}
59
60#[derive(Clone, Debug, Eq, PartialEq)]
65pub struct SnapshotDownloadConfig {
66 pub canister: String,
67 pub out: PathBuf,
68 pub root: Option<String>,
69 pub include_children: bool,
70 pub recursive: bool,
71 pub dry_run: bool,
72 pub lifecycle: SnapshotLifecycleMode,
73 pub backup_id: String,
74 pub created_at: String,
75 pub tool_name: String,
76 pub tool_version: String,
77 pub environment: String,
78}
79
80#[derive(Clone, Debug, Eq, PartialEq)]
85pub struct SnapshotDownloadResult {
86 pub artifacts: Vec<SnapshotArtifact>,
87 pub planned_commands: Vec<String>,
88}
89
90#[derive(Debug, ThisError)]
95pub enum SnapshotDownloadError {
96 #[error("missing --root when using --include-children")]
97 MissingRegistrySource,
98
99 #[error("snapshot capture requires stopping each canister before snapshot create")]
100 SnapshotRequiresStoppedCanister,
101
102 #[error("snapshot driver failed: {0}")]
103 Driver(#[source] SnapshotDriverError),
104
105 #[error(transparent)]
106 Io(#[from] std::io::Error),
107
108 #[error(transparent)]
109 Checksum(#[from] ArtifactChecksumError),
110
111 #[error(transparent)]
112 Persistence(#[from] PersistenceError),
113
114 #[error(transparent)]
115 Journal(#[from] JournalValidationError),
116
117 #[error(transparent)]
118 Discovery(#[from] DiscoveryError),
119
120 #[error(transparent)]
121 Manifest(#[from] SnapshotManifestError),
122}
123
124pub trait SnapshotDriver {
129 fn registry_entries(&mut self, root: &str) -> Result<Vec<RegistryEntry>, SnapshotDriverError>;
131
132 fn create_snapshot(&mut self, canister_id: &str) -> Result<String, SnapshotDriverError>;
134
135 fn stop_canister(&mut self, canister_id: &str) -> Result<(), SnapshotDriverError>;
137
138 fn start_canister(&mut self, canister_id: &str) -> Result<(), SnapshotDriverError>;
140
141 fn download_snapshot(
143 &mut self,
144 canister_id: &str,
145 snapshot_id: &str,
146 artifact_path: &Path,
147 ) -> Result<(), SnapshotDriverError>;
148
149 fn create_snapshot_command(&self, canister_id: &str) -> String;
151
152 fn stop_canister_command(&self, canister_id: &str) -> String;
154
155 fn start_canister_command(&self, canister_id: &str) -> String;
157
158 fn download_snapshot_command(
160 &self,
161 canister_id: &str,
162 snapshot_id: &str,
163 artifact_path: &Path,
164 ) -> String;
165}
166
167pub struct SnapshotManifestInput<'a> {
172 pub backup_id: String,
173 pub created_at: String,
174 pub tool_name: String,
175 pub tool_version: String,
176 pub environment: String,
177 pub root_canister: String,
178 pub selected_canister: String,
179 pub include_children: bool,
180 pub targets: &'a [crate::discovery::SnapshotTarget],
181 pub artifacts: &'a [SnapshotArtifact],
182 pub discovery_topology_hash: TopologyHash,
183 pub pre_snapshot_topology_hash: TopologyHash,
184}
185
186#[derive(Debug, ThisError)]
191pub enum SnapshotManifestError {
192 #[error("field {field} must be a valid principal: {value}")]
193 InvalidPrincipal { field: &'static str, value: String },
194
195 #[error(
196 "topology changed before snapshot start: discovery={discovery}, pre_snapshot={pre_snapshot}"
197 )]
198 TopologyChanged {
199 discovery: String,
200 pre_snapshot: String,
201 },
202
203 #[error("missing snapshot artifact for canister {0}")]
204 MissingArtifact(String),
205
206 #[error(transparent)]
207 InvalidManifest(#[from] ManifestValidationError),
208}