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
12pub type SnapshotDriverError = Box<dyn StdError + Send + Sync + 'static>;
13
14///
15/// SnapshotArtifact
16///
17
18#[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///
27/// SnapshotLifecycleMode
28///
29
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub enum SnapshotLifecycleMode {
32    StopBeforeSnapshot,
33    StopAndResume,
34}
35
36impl SnapshotLifecycleMode {
37    /// Build the lifecycle mode from the optional post-snapshot resume flag.
38    #[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    /// Return whether snapshot capture should stop the canister first.
48    #[must_use]
49    pub const fn stop_before_snapshot(self) -> bool {
50        true
51    }
52
53    /// Return whether snapshot capture should resume the canister afterward.
54    #[must_use]
55    pub const fn resume_after_snapshot(self) -> bool {
56        matches!(self, Self::StopAndResume)
57    }
58}
59
60///
61/// SnapshotDownloadConfig
62///
63
64#[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///
81/// SnapshotDownloadResult
82///
83
84#[derive(Clone, Debug, Eq, PartialEq)]
85pub struct SnapshotDownloadResult {
86    pub artifacts: Vec<SnapshotArtifact>,
87    pub planned_commands: Vec<String>,
88}
89
90///
91/// SnapshotDownloadError
92///
93
94#[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
124///
125/// SnapshotDriver
126///
127
128pub trait SnapshotDriver {
129    /// Load the root registry entries used to resolve child snapshot targets.
130    fn registry_entries(&mut self, root: &str) -> Result<Vec<RegistryEntry>, SnapshotDriverError>;
131
132    /// Create one canister snapshot and return its snapshot id.
133    fn create_snapshot(&mut self, canister_id: &str) -> Result<String, SnapshotDriverError>;
134
135    /// Stop one canister before snapshot creation.
136    fn stop_canister(&mut self, canister_id: &str) -> Result<(), SnapshotDriverError>;
137
138    /// Start one canister after snapshot capture.
139    fn start_canister(&mut self, canister_id: &str) -> Result<(), SnapshotDriverError>;
140
141    /// Download one snapshot into the supplied artifact directory.
142    fn download_snapshot(
143        &mut self,
144        canister_id: &str,
145        snapshot_id: &str,
146        artifact_path: &Path,
147    ) -> Result<(), SnapshotDriverError>;
148
149    /// Render the planned create command for dry-run output.
150    fn create_snapshot_command(&self, canister_id: &str) -> String;
151
152    /// Render the planned stop command for dry-run output.
153    fn stop_canister_command(&self, canister_id: &str) -> String;
154
155    /// Render the planned start command for dry-run output.
156    fn start_canister_command(&self, canister_id: &str) -> String;
157
158    /// Render the planned download command for dry-run output.
159    fn download_snapshot_command(
160        &self,
161        canister_id: &str,
162        snapshot_id: &str,
163        artifact_path: &Path,
164    ) -> String;
165}
166
167///
168/// SnapshotManifestInput
169///
170
171pub 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///
187/// SnapshotManifestError
188///
189
190#[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}