canic_backup/snapshot/
mod.rs1mod capture;
2mod manifest;
3mod support;
4mod targets;
5mod topology;
6mod types;
7
8pub use manifest::build_snapshot_manifest;
9pub use targets::resolve_snapshot_targets;
10pub use topology::{ensure_topology_stable, topology_hash_for_targets};
11pub use types::*;
12
13use crate::{
14 journal::DownloadJournal, journal::DownloadOperationMetrics, persistence::BackupLayout,
15};
16use capture::{SnapshotArtifactPaths, capture_snapshot_artifact, dry_run_artifact};
17use topology::accepted_pre_snapshot_topology_hash;
18
19pub fn download_snapshots(
21 config: &SnapshotDownloadConfig,
22 driver: &mut impl SnapshotDriver,
23) -> Result<SnapshotDownloadResult, SnapshotDownloadError> {
24 validate_snapshot_lifecycle(config.lifecycle)?;
25 let targets = resolve_snapshot_targets(config, driver)?;
26 let discovery_topology_hash = topology_hash_for_targets(&config.canister, &targets)?;
27 let pre_snapshot_topology_hash =
28 accepted_pre_snapshot_topology_hash(config, driver, &discovery_topology_hash)?;
29 let layout = BackupLayout::new(config.out.clone());
30 let mut artifacts = Vec::with_capacity(targets.len());
31 let mut planned_commands = Vec::new();
32 let mut journal = DownloadJournal {
33 journal_version: 1,
34 backup_id: config.backup_id.clone(),
35 discovery_topology_hash: Some(discovery_topology_hash.hash.clone()),
36 pre_snapshot_topology_hash: Some(pre_snapshot_topology_hash.hash.clone()),
37 operation_metrics: DownloadOperationMetrics {
38 target_count: targets.len(),
39 ..DownloadOperationMetrics::default()
40 },
41 artifacts: Vec::new(),
42 };
43
44 for target in &targets {
45 let paths = SnapshotArtifactPaths::new(&config.out, &target.canister_id);
46
47 if config.dry_run {
48 let (artifact, commands) =
49 dry_run_artifact(config, driver, target, paths.artifact_path);
50 artifacts.push(artifact);
51 planned_commands.extend(commands);
52 continue;
53 }
54
55 artifacts.push(capture_snapshot_artifact(
56 config,
57 driver,
58 &layout,
59 &mut journal,
60 target,
61 paths,
62 )?);
63 }
64
65 if !config.dry_run {
66 let manifest = build_snapshot_manifest(SnapshotManifestInput {
67 backup_id: config.backup_id.clone(),
68 created_at: config.created_at.clone(),
69 tool_name: config.tool_name.clone(),
70 tool_version: config.tool_version.clone(),
71 environment: config.environment.clone(),
72 root_canister: config
73 .root
74 .clone()
75 .unwrap_or_else(|| config.canister.clone()),
76 selected_canister: config.canister.clone(),
77 include_children: config.include_children,
78 targets: &targets,
79 artifacts: &artifacts,
80 discovery_topology_hash,
81 pre_snapshot_topology_hash,
82 })?;
83 layout.write_manifest(&manifest)?;
84 }
85
86 Ok(SnapshotDownloadResult {
87 artifacts,
88 planned_commands,
89 })
90}
91
92const fn validate_snapshot_lifecycle(
93 lifecycle: SnapshotLifecycleMode,
94) -> Result<(), SnapshotDownloadError> {
95 if lifecycle.stop_before_snapshot() {
96 return Ok(());
97 }
98
99 Err(SnapshotDownloadError::SnapshotRequiresStoppedCanister)
100}
101
102#[cfg(test)]
103mod tests;