Skip to main content

canic_backup/snapshot/
types.rs

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