Skip to main content

canic_backup/snapshot/
mod.rs

1mod 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
19/// Create and download snapshots for the selected canister set.
20pub 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;