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#[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
37struct 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
49pub 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
77fn 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
88fn 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
103fn 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
137fn 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}