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(transparent)]
132 Io(#[from] std::io::Error),
133
134 #[error(transparent)]
135 Checksum(#[from] ArtifactChecksumError),
136
137 #[error(transparent)]
138 Persistence(#[from] PersistenceError),
139
140 #[error(transparent)]
141 Journal(#[from] JournalValidationError),
142
143 #[error(transparent)]
144 Discovery(#[from] DiscoveryError),
145
146 #[error(transparent)]
147 Manifest(#[from] SnapshotManifestError),
148}
149
150pub trait SnapshotDriver {
155 fn registry_entries(&mut self, root: &str) -> Result<Vec<RegistryEntry>, SnapshotDriverError>;
157
158 fn create_snapshot(&mut self, canister_id: &str) -> Result<String, SnapshotDriverError>;
160
161 fn stop_canister(&mut self, canister_id: &str) -> Result<(), SnapshotDriverError>;
163
164 fn start_canister(&mut self, canister_id: &str) -> Result<(), SnapshotDriverError>;
166
167 fn download_snapshot(
169 &mut self,
170 canister_id: &str,
171 snapshot_id: &str,
172 artifact_path: &Path,
173 ) -> Result<(), SnapshotDriverError>;
174
175 fn create_snapshot_command(&self, canister_id: &str) -> String;
177
178 fn stop_canister_command(&self, canister_id: &str) -> String;
180
181 fn start_canister_command(&self, canister_id: &str) -> String;
183
184 fn download_snapshot_command(
186 &self,
187 canister_id: &str,
188 snapshot_id: &str,
189 artifact_path: &Path,
190 ) -> String;
191}
192
193pub struct SnapshotManifestInput<'a> {
198 pub backup_id: String,
199 pub created_at: String,
200 pub tool_name: String,
201 pub tool_version: String,
202 pub environment: String,
203 pub root_canister: String,
204 pub selected_canister: String,
205 pub include_children: bool,
206 pub targets: &'a [crate::discovery::SnapshotTarget],
207 pub artifacts: &'a [SnapshotArtifact],
208 pub discovery_topology_hash: TopologyHash,
209 pub pre_snapshot_topology_hash: TopologyHash,
210}
211
212#[derive(Debug, ThisError)]
217pub enum SnapshotManifestError {
218 #[error("field {field} must be a valid principal: {value}")]
219 InvalidPrincipal { field: &'static str, value: String },
220
221 #[error(
222 "topology changed before snapshot start: discovery={discovery}, pre_snapshot={pre_snapshot}"
223 )]
224 TopologyChanged {
225 discovery: String,
226 pre_snapshot: String,
227 },
228
229 #[error("missing snapshot artifact for canister {0}")]
230 MissingArtifact(String),
231
232 #[error(transparent)]
233 InvalidManifest(#[from] ManifestValidationError),
234}