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
12#[derive(Debug, ThisError)]
17#[error(transparent)]
18pub struct SnapshotDriverError {
19 source: Box<dyn StdError + Send + Sync + 'static>,
20}
21
22impl SnapshotDriverError {
23 #[must_use]
24 pub fn new<E>(source: E) -> Self
25 where
26 E: StdError + Send + Sync + 'static,
27 {
28 Self {
29 source: Box::new(source),
30 }
31 }
32}
33
34impl From<std::io::Error> for SnapshotDriverError {
35 fn from(source: std::io::Error) -> Self {
36 Self::new(source)
37 }
38}
39
40#[derive(Clone, Debug, Eq, PartialEq)]
45pub struct SnapshotArtifact {
46 pub canister_id: String,
47 pub snapshot_id: String,
48 pub path: PathBuf,
49 pub checksum: String,
50}
51
52#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub enum SnapshotLifecycleMode {
58 StopBeforeSnapshot,
59 StopAndResume,
60}
61
62impl SnapshotLifecycleMode {
63 #[must_use]
65 pub const fn from_resume_flag(resume_after_snapshot: bool) -> Self {
66 if resume_after_snapshot {
67 Self::StopAndResume
68 } else {
69 Self::StopBeforeSnapshot
70 }
71 }
72
73 #[must_use]
75 pub const fn stop_before_snapshot(self) -> bool {
76 true
77 }
78
79 #[must_use]
81 pub const fn resume_after_snapshot(self) -> bool {
82 matches!(self, Self::StopAndResume)
83 }
84}
85
86#[derive(Clone, Debug, Eq, PartialEq)]
91pub struct SnapshotDownloadConfig {
92 pub canister: String,
93 pub out: PathBuf,
94 pub root: Option<String>,
95 pub include_children: bool,
96 pub recursive: bool,
97 pub dry_run: bool,
98 pub lifecycle: SnapshotLifecycleMode,
99 pub backup_id: String,
100 pub created_at: String,
101 pub tool_name: String,
102 pub tool_version: String,
103 pub environment: String,
104}
105
106#[derive(Clone, Debug, Eq, PartialEq)]
111pub struct SnapshotDownloadResult {
112 pub artifacts: Vec<SnapshotArtifact>,
113 pub planned_commands: Vec<String>,
114}
115
116#[derive(Debug, ThisError)]
121pub enum SnapshotDownloadError {
122 #[error("missing --root when using --include-children")]
123 MissingRegistrySource,
124
125 #[error("snapshot capture requires stopping each canister before snapshot create")]
126 SnapshotRequiresStoppedCanister,
127
128 #[error("snapshot driver failed: {0}")]
129 Driver(#[source] SnapshotDriverError),
130
131 #[error("canister restart after snapshot capture failed: {0}")]
132 Restart(#[source] SnapshotDriverError),
133
134 #[error("snapshot capture failed ({capture}) and canister restart also failed ({restart})")]
135 CaptureAndRestart {
136 #[source]
137 capture: Box<Self>,
138 restart: SnapshotDriverError,
139 },
140
141 #[error(transparent)]
142 Io(#[from] std::io::Error),
143
144 #[error(transparent)]
145 Checksum(#[from] ArtifactChecksumError),
146
147 #[error(transparent)]
148 Persistence(#[from] PersistenceError),
149
150 #[error(transparent)]
151 Journal(#[from] JournalValidationError),
152
153 #[error(transparent)]
154 Discovery(#[from] DiscoveryError),
155
156 #[error(transparent)]
157 Manifest(#[from] SnapshotManifestError),
158}
159
160pub trait SnapshotDriver {
165 fn registry_entries(&mut self, root: &str) -> Result<Vec<RegistryEntry>, SnapshotDriverError>;
167
168 fn create_snapshot(&mut self, canister_id: &str) -> Result<String, SnapshotDriverError>;
170
171 fn stop_canister(&mut self, canister_id: &str) -> Result<(), SnapshotDriverError>;
173
174 fn start_canister(&mut self, canister_id: &str) -> Result<(), SnapshotDriverError>;
176
177 fn download_snapshot(
179 &mut self,
180 canister_id: &str,
181 snapshot_id: &str,
182 artifact_path: &Path,
183 ) -> Result<(), SnapshotDriverError>;
184
185 fn create_snapshot_command(&self, canister_id: &str) -> String;
187
188 fn stop_canister_command(&self, canister_id: &str) -> String;
190
191 fn start_canister_command(&self, canister_id: &str) -> String;
193
194 fn download_snapshot_command(
196 &self,
197 canister_id: &str,
198 snapshot_id: &str,
199 artifact_path: &Path,
200 ) -> String;
201}
202
203pub struct SnapshotManifestInput<'a> {
208 pub backup_id: String,
209 pub created_at: String,
210 pub tool_name: String,
211 pub tool_version: String,
212 pub environment: String,
213 pub root_canister: String,
214 pub selected_canister: String,
215 pub include_children: bool,
216 pub targets: &'a [crate::discovery::SnapshotTarget],
217 pub artifacts: &'a [SnapshotArtifact],
218 pub discovery_topology_hash: TopologyHash,
219 pub pre_snapshot_topology_hash: TopologyHash,
220}
221
222#[derive(Debug, ThisError)]
227pub enum SnapshotManifestError {
228 #[error("field {field} must be a valid principal: {value}")]
229 InvalidPrincipal { field: &'static str, value: String },
230
231 #[error(
232 "topology changed before snapshot start: discovery={discovery}, pre_snapshot={pre_snapshot}"
233 )]
234 TopologyChanged {
235 discovery: String,
236 pre_snapshot: String,
237 },
238
239 #[error("missing snapshot artifact for canister {0}")]
240 MissingArtifact(String),
241
242 #[error(transparent)]
243 InvalidManifest(#[from] ManifestValidationError),
244}