Skip to main content

canic_backup/snapshot/
types.rs

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///
13/// SnapshotDriverError
14///
15
16#[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///
41/// SnapshotArtifact
42///
43
44#[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///
53/// SnapshotLifecycleMode
54///
55
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub enum SnapshotLifecycleMode {
58    StopBeforeSnapshot,
59    StopAndResume,
60}
61
62impl SnapshotLifecycleMode {
63    /// Build the lifecycle mode from the optional post-snapshot resume flag.
64    #[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    /// Return whether snapshot capture should stop the canister first.
74    #[must_use]
75    pub const fn stop_before_snapshot(self) -> bool {
76        true
77    }
78
79    /// Return whether snapshot capture should resume the canister afterward.
80    #[must_use]
81    pub const fn resume_after_snapshot(self) -> bool {
82        matches!(self, Self::StopAndResume)
83    }
84}
85
86///
87/// SnapshotDownloadConfig
88///
89
90#[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///
107/// SnapshotDownloadResult
108///
109
110#[derive(Clone, Debug, Eq, PartialEq)]
111pub struct SnapshotDownloadResult {
112    pub artifacts: Vec<SnapshotArtifact>,
113    pub planned_commands: Vec<String>,
114}
115
116///
117/// SnapshotDownloadError
118///
119
120#[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
150///
151/// SnapshotDriver
152///
153
154pub trait SnapshotDriver {
155    /// Load the root registry entries used to resolve child snapshot targets.
156    fn registry_entries(&mut self, root: &str) -> Result<Vec<RegistryEntry>, SnapshotDriverError>;
157
158    /// Create one canister snapshot and return its snapshot id.
159    fn create_snapshot(&mut self, canister_id: &str) -> Result<String, SnapshotDriverError>;
160
161    /// Stop one canister before snapshot creation.
162    fn stop_canister(&mut self, canister_id: &str) -> Result<(), SnapshotDriverError>;
163
164    /// Start one canister after snapshot capture.
165    fn start_canister(&mut self, canister_id: &str) -> Result<(), SnapshotDriverError>;
166
167    /// Download one snapshot into the supplied artifact directory.
168    fn download_snapshot(
169        &mut self,
170        canister_id: &str,
171        snapshot_id: &str,
172        artifact_path: &Path,
173    ) -> Result<(), SnapshotDriverError>;
174
175    /// Render the planned create command for dry-run output.
176    fn create_snapshot_command(&self, canister_id: &str) -> String;
177
178    /// Render the planned stop command for dry-run output.
179    fn stop_canister_command(&self, canister_id: &str) -> String;
180
181    /// Render the planned start command for dry-run output.
182    fn start_canister_command(&self, canister_id: &str) -> String;
183
184    /// Render the planned download command for dry-run output.
185    fn download_snapshot_command(
186        &self,
187        canister_id: &str,
188        snapshot_id: &str,
189        artifact_path: &Path,
190    ) -> String;
191}
192
193///
194/// SnapshotManifestInput
195///
196
197pub 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///
213/// SnapshotManifestError
214///
215
216#[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}