1mod enforce;
2mod error;
3mod io;
4mod options;
5
6use crate::version_text;
7use canic_backup::restore::{
8 RestoreApplyCommandConfig, RestoreApplyDryRun, RestorePlan, RestorePlanner, RestoreRunResponse,
9 RestoreRunnerConfig,
10};
11use std::ffi::OsString;
12
13use enforce::{enforce_restore_plan_requirements, enforce_restore_run_requirements};
14use io::{
15 read_manifest_source, read_mapping, read_plan, verify_backup_layout_if_required,
16 write_apply_dry_run, write_apply_journal_if_requested, write_plan, write_restore_run,
17};
18
19pub use canic_backup::restore::parse_uploaded_snapshot_id;
20pub use error::RestoreCommandError;
21pub use options::{RestoreApplyOptions, RestorePlanOptions, RestoreRunOptions};
22
23pub fn run<I>(args: I) -> Result<(), RestoreCommandError>
25where
26 I: IntoIterator<Item = OsString>,
27{
28 let mut args = args.into_iter();
29 let Some(command) = args.next().and_then(|arg| arg.into_string().ok()) else {
30 return Err(RestoreCommandError::Usage(usage()));
31 };
32
33 match command.as_str() {
34 "plan" => {
35 let options = RestorePlanOptions::parse(args)?;
36 let plan = plan_restore(&options)?;
37 write_plan(&options, &plan)?;
38 enforce_restore_plan_requirements(&options, &plan)?;
39 Ok(())
40 }
41 "apply" => {
42 let options = RestoreApplyOptions::parse(args)?;
43 let dry_run = restore_apply_dry_run(&options)?;
44 write_apply_dry_run(&options, &dry_run)?;
45 write_apply_journal_if_requested(&options, &dry_run)?;
46 Ok(())
47 }
48 "run" => {
49 let options = RestoreRunOptions::parse(args)?;
50 let run = if options.execute {
51 restore_run_execute_result(&options)?
52 } else if options.unclaim_pending {
53 canic_backup::restore::RestoreRunnerOutcome {
54 response: restore_run_unclaim_pending(&options)?,
55 error: None,
56 }
57 } else {
58 canic_backup::restore::RestoreRunnerOutcome {
59 response: restore_run_dry_run(&options)?,
60 error: None,
61 }
62 };
63 write_restore_run(&options, &run.response)?;
64 if let Some(error) = run.error {
65 return Err(error.into());
66 }
67 enforce_restore_run_requirements(&options, &run.response)?;
68 Ok(())
69 }
70 "help" | "--help" | "-h" => {
71 println!("{}", usage());
72 Ok(())
73 }
74 "version" | "--version" | "-V" => {
75 println!("{}", version_text());
76 Ok(())
77 }
78 _ => Err(RestoreCommandError::UnknownOption(command)),
79 }
80}
81
82pub fn plan_restore(options: &RestorePlanOptions) -> Result<RestorePlan, RestoreCommandError> {
84 verify_backup_layout_if_required(options)?;
85
86 let manifest = read_manifest_source(options)?;
87 let mapping = options.mapping.as_ref().map(read_mapping).transpose()?;
88
89 RestorePlanner::plan(&manifest, mapping.as_ref()).map_err(RestoreCommandError::from)
90}
91
92pub fn restore_apply_dry_run(
94 options: &RestoreApplyOptions,
95) -> Result<RestoreApplyDryRun, RestoreCommandError> {
96 let plan = read_plan(&options.plan)?;
97 if let Some(backup_dir) = &options.backup_dir {
98 return RestoreApplyDryRun::try_from_plan_with_artifacts(&plan, backup_dir)
99 .map_err(RestoreCommandError::from);
100 }
101
102 Ok(RestoreApplyDryRun::from_plan(&plan))
103}
104
105pub fn restore_run_dry_run(
107 options: &RestoreRunOptions,
108) -> Result<RestoreRunResponse, RestoreCommandError> {
109 canic_backup::restore::restore_run_dry_run(&restore_runner_config(options))
110 .map_err(RestoreCommandError::from)
111}
112
113pub fn restore_run_unclaim_pending(
115 options: &RestoreRunOptions,
116) -> Result<RestoreRunResponse, RestoreCommandError> {
117 canic_backup::restore::restore_run_unclaim_pending(&restore_runner_config(options))
118 .map_err(RestoreCommandError::from)
119}
120
121fn restore_run_execute_result(
123 options: &RestoreRunOptions,
124) -> Result<canic_backup::restore::RestoreRunnerOutcome, RestoreCommandError> {
125 canic_backup::restore::restore_run_execute_result(&restore_runner_config(options))
126 .map_err(RestoreCommandError::from)
127}
128
129fn restore_command_config(program: &str, network: Option<&str>) -> RestoreApplyCommandConfig {
131 RestoreApplyCommandConfig {
132 program: program.to_string(),
133 network: network.map(str::to_string),
134 }
135}
136
137fn restore_runner_config(options: &RestoreRunOptions) -> RestoreRunnerConfig {
139 RestoreRunnerConfig {
140 journal: options.journal.clone(),
141 command: restore_command_config(&options.dfx, options.network.as_deref()),
142 max_steps: options.max_steps,
143 updated_at: None,
144 }
145}
146
147const fn usage() -> &'static str {
149 "usage: canic restore <command> [<args>]\n\ncommands:\n plan Build a no-mutation restore plan.\n apply Render restore operations and optionally write an apply journal.\n run Preview, execute, or recover the native restore runner."
150}
151
152#[cfg(test)]
153mod tests;