Skip to main content

canic_cli/backup/smoke/
mod.rs

1use crate::restore as cli_restore;
2use canic_backup::restore::RestoreApplyJournal;
3use serde::Serialize;
4use std::{
5    fs,
6    path::{Path, PathBuf},
7};
8
9use super::{
10    BackupCommandError, BackupPreflightOptions, BackupPreflightReport, BackupSmokeOptions,
11    backup_preflight, write_json_file,
12};
13
14///
15/// BackupSmokeReport
16///
17
18#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
19pub struct BackupSmokeReport {
20    pub status: String,
21    pub backup_id: String,
22    pub backup_dir: String,
23    pub out_dir: String,
24    pub preflight_dir: String,
25    pub preflight_summary_path: String,
26    pub restore_apply_dry_run_path: String,
27    pub restore_apply_journal_path: String,
28    pub restore_run_dry_run_path: String,
29    pub smoke_summary_path: String,
30    pub manifest_design_v1_ready: bool,
31    pub restore_ready: bool,
32    pub restore_readiness_reasons: Vec<String>,
33    pub restore_planned_operations: usize,
34    pub runner_preview_written: bool,
35}
36
37///
38/// SmokeArtifactPaths
39///
40
41struct SmokeArtifactPaths {
42    preflight_dir: PathBuf,
43    restore_apply_dry_run: PathBuf,
44    restore_apply_journal: PathBuf,
45    restore_run_dry_run: PathBuf,
46    smoke_summary: PathBuf,
47}
48
49/// Run the post-capture backup/restore smoke path and write all release artifacts.
50pub fn backup_smoke(options: &BackupSmokeOptions) -> Result<BackupSmokeReport, BackupCommandError> {
51    fs::create_dir_all(&options.out_dir)?;
52
53    let paths = smoke_artifact_paths(&options.out_dir);
54    let preflight = backup_preflight(&BackupPreflightOptions {
55        dir: options.dir.clone(),
56        out_dir: paths.preflight_dir.clone(),
57        mapping: options.mapping.clone(),
58        require_design_v1: options.require_design_v1,
59        require_restore_ready: options.require_restore_ready,
60    })?;
61
62    let apply_options = smoke_restore_apply_options(options, &paths);
63    let dry_run = cli_restore::restore_apply_dry_run(&apply_options)?;
64    write_json_file(&paths.restore_apply_dry_run, &dry_run)?;
65    let journal = RestoreApplyJournal::from_dry_run(&dry_run);
66    write_json_file(&paths.restore_apply_journal, &journal)?;
67
68    let run_options = smoke_restore_run_options(options, &paths);
69    let runner_preview = cli_restore::restore_run_dry_run(&run_options)?;
70    write_json_file(&paths.restore_run_dry_run, &runner_preview)?;
71
72    let report = build_smoke_report(options, &paths, &preflight);
73    write_json_file(&paths.smoke_summary, &report)?;
74    Ok(report)
75}
76
77// Build the canonical smoke artifact path set under one output directory.
78fn smoke_artifact_paths(out_dir: &Path) -> SmokeArtifactPaths {
79    SmokeArtifactPaths {
80        preflight_dir: out_dir.join("preflight"),
81        restore_apply_dry_run: out_dir.join("restore-apply-dry-run.json"),
82        restore_apply_journal: out_dir.join("restore-apply-journal.json"),
83        restore_run_dry_run: out_dir.join("restore-run-dry-run.json"),
84        smoke_summary: out_dir.join("smoke-summary.json"),
85    }
86}
87
88// Build restore apply dry-run options for the smoke wrapper.
89fn smoke_restore_apply_options(
90    options: &BackupSmokeOptions,
91    paths: &SmokeArtifactPaths,
92) -> cli_restore::RestoreApplyOptions {
93    cli_restore::RestoreApplyOptions {
94        plan: paths.preflight_dir.join("restore-plan.json"),
95        status: Some(paths.preflight_dir.join("restore-status.json")),
96        backup_dir: Some(options.dir.clone()),
97        out: Some(paths.restore_apply_dry_run.clone()),
98        journal_out: Some(paths.restore_apply_journal.clone()),
99        dry_run: true,
100    }
101}
102
103// Build restore runner preview options for the smoke wrapper.
104fn smoke_restore_run_options(
105    options: &BackupSmokeOptions,
106    paths: &SmokeArtifactPaths,
107) -> cli_restore::RestoreRunOptions {
108    cli_restore::RestoreRunOptions {
109        journal: paths.restore_apply_journal.clone(),
110        dfx: options.dfx.clone(),
111        network: options.network.clone(),
112        out: Some(paths.restore_run_dry_run.clone()),
113        dry_run: true,
114        execute: false,
115        unclaim_pending: false,
116        max_steps: None,
117        updated_at: None,
118        require_complete: false,
119        require_no_attention: false,
120        require_run_mode: None,
121        require_stopped_reason: None,
122        require_next_action: None,
123        require_executed_count: None,
124        require_receipt_count: None,
125        require_completed_receipt_count: None,
126        require_failed_receipt_count: None,
127        require_recovered_receipt_count: None,
128        require_receipt_updated_at: None,
129        require_state_updated_at: None,
130        require_remaining_count: None,
131        require_attention_count: None,
132        require_completion_basis_points: None,
133        require_no_pending_before: None,
134    }
135}
136
137// Build the compact smoke summary mirrored by smoke-summary.json.
138fn build_smoke_report(
139    options: &BackupSmokeOptions,
140    paths: &SmokeArtifactPaths,
141    preflight: &BackupPreflightReport,
142) -> BackupSmokeReport {
143    BackupSmokeReport {
144        status: "ready".to_string(),
145        backup_id: preflight.backup_id.clone(),
146        backup_dir: options.dir.display().to_string(),
147        out_dir: options.out_dir.display().to_string(),
148        preflight_dir: paths.preflight_dir.display().to_string(),
149        preflight_summary_path: paths
150            .preflight_dir
151            .join("preflight-summary.json")
152            .display()
153            .to_string(),
154        restore_apply_dry_run_path: paths.restore_apply_dry_run.display().to_string(),
155        restore_apply_journal_path: paths.restore_apply_journal.display().to_string(),
156        restore_run_dry_run_path: paths.restore_run_dry_run.display().to_string(),
157        smoke_summary_path: paths.smoke_summary.display().to_string(),
158        manifest_design_v1_ready: preflight.manifest_design_v1_ready,
159        restore_ready: preflight.restore_ready,
160        restore_readiness_reasons: preflight.restore_readiness_reasons.clone(),
161        restore_planned_operations: preflight.restore_planned_operations,
162        runner_preview_written: true,
163    }
164}