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 error::RestoreCommandError;
20pub use options::{RestoreApplyOptions, RestorePlanOptions, RestoreRunOptions};
21
22pub fn run<I>(args: I) -> Result<(), RestoreCommandError>
24where
25 I: IntoIterator<Item = OsString>,
26{
27 let mut args = args.into_iter();
28 let Some(command) = args.next().and_then(|arg| arg.into_string().ok()) else {
29 return Err(RestoreCommandError::Usage(usage()));
30 };
31
32 match command.as_str() {
33 "plan" => {
34 let options = RestorePlanOptions::parse(args)?;
35 let plan = plan_restore(&options)?;
36 write_plan(&options, &plan)?;
37 enforce_restore_plan_requirements(&options, &plan)?;
38 Ok(())
39 }
40 "apply" => {
41 let options = RestoreApplyOptions::parse(args)?;
42 let dry_run = restore_apply_dry_run(&options)?;
43 write_apply_dry_run(&options, &dry_run)?;
44 write_apply_journal_if_requested(&options, &dry_run)?;
45 Ok(())
46 }
47 "run" => {
48 let options = RestoreRunOptions::parse(args)?;
49 let run = if options.execute {
50 restore_run_execute_result(&options)?
51 } else if options.unclaim_pending {
52 canic_backup::restore::RestoreRunnerOutcome {
53 response: restore_run_unclaim_pending(&options)?,
54 error: None,
55 }
56 } else {
57 canic_backup::restore::RestoreRunnerOutcome {
58 response: restore_run_dry_run(&options)?,
59 error: None,
60 }
61 };
62 write_restore_run(&options, &run.response)?;
63 if let Some(error) = run.error {
64 return Err(error.into());
65 }
66 enforce_restore_run_requirements(&options, &run.response)?;
67 Ok(())
68 }
69 "help" | "--help" | "-h" => {
70 println!("{}", usage());
71 Ok(())
72 }
73 "version" | "--version" | "-V" => {
74 println!("{}", version_text());
75 Ok(())
76 }
77 _ => Err(RestoreCommandError::UnknownOption(command)),
78 }
79}
80
81pub fn plan_restore(options: &RestorePlanOptions) -> Result<RestorePlan, RestoreCommandError> {
83 verify_backup_layout_if_required(options)?;
84
85 let manifest = read_manifest_source(options)?;
86 let mapping = options.mapping.as_ref().map(read_mapping).transpose()?;
87
88 RestorePlanner::plan(&manifest, mapping.as_ref()).map_err(RestoreCommandError::from)
89}
90
91pub fn restore_apply_dry_run(
93 options: &RestoreApplyOptions,
94) -> Result<RestoreApplyDryRun, RestoreCommandError> {
95 let plan = read_plan(&options.plan)?;
96 if let Some(backup_dir) = &options.backup_dir {
97 return RestoreApplyDryRun::try_from_plan_with_artifacts(&plan, backup_dir)
98 .map_err(RestoreCommandError::from);
99 }
100
101 Ok(RestoreApplyDryRun::from_plan(&plan))
102}
103
104pub fn restore_run_dry_run(
106 options: &RestoreRunOptions,
107) -> Result<RestoreRunResponse, RestoreCommandError> {
108 canic_backup::restore::restore_run_dry_run(&restore_runner_config(options))
109 .map_err(RestoreCommandError::from)
110}
111
112pub fn restore_run_unclaim_pending(
114 options: &RestoreRunOptions,
115) -> Result<RestoreRunResponse, RestoreCommandError> {
116 canic_backup::restore::restore_run_unclaim_pending(&restore_runner_config(options))
117 .map_err(RestoreCommandError::from)
118}
119
120fn restore_run_execute_result(
122 options: &RestoreRunOptions,
123) -> Result<canic_backup::restore::RestoreRunnerOutcome, RestoreCommandError> {
124 canic_backup::restore::restore_run_execute_result(&restore_runner_config(options))
125 .map_err(RestoreCommandError::from)
126}
127
128fn restore_command_config(program: &str, network: Option<&str>) -> RestoreApplyCommandConfig {
130 RestoreApplyCommandConfig {
131 program: program.to_string(),
132 network: network.map(str::to_string),
133 }
134}
135
136fn restore_runner_config(options: &RestoreRunOptions) -> RestoreRunnerConfig {
138 RestoreRunnerConfig {
139 journal: options.journal.clone(),
140 command: restore_command_config(&options.dfx, options.network.as_deref()),
141 max_steps: options.max_steps,
142 updated_at: None,
143 }
144}
145
146const fn usage() -> &'static str {
148 "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."
149}
150
151#[cfg(test)]
152mod tests;