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