use anyhow::Result;
use tracing::{debug, error};
use s3rm_rs::config::Config;
use s3rm_rs::{
CLIArgs, DeletionPipeline, create_pipeline_cancellation_token, exit_code_from_error,
is_cancelled_error,
};
pub mod ctrl_c_handler;
pub mod indicator;
mod tracing_init;
pub mod ui_config;
pub const EXIT_CODE_WARNING: i32 = 3;
pub const EXIT_CODE_ABNORMAL_TERMINATION: i32 = 101;
pub fn load_config_exit_if_err(args: CLIArgs) -> Config {
match Config::try_from(args) {
Ok(config) => config,
Err(error_message) => {
clap::Error::raw(clap::error::ErrorKind::ValueValidation, error_message).exit();
}
}
}
pub fn start_tracing_if_necessary(config: &Config) -> bool {
if let Some(tracing_config) = config.tracing_config.as_ref() {
tracing_init::init_tracing(tracing_config);
true
} else {
false
}
}
pub async fn run(config: Config) -> Result<()> {
#[allow(unused_assignments)]
let mut has_warning = false;
{
let cancellation_token = create_pipeline_cancellation_token();
let start_time = tokio::time::Instant::now();
debug!("deletion pipeline start.");
let mut pipeline = DeletionPipeline::new(config.clone(), cancellation_token.clone()).await;
if let Err(e) = pipeline.check_prerequisites().await {
pipeline.close_stats_sender();
if is_cancelled_error(&e) {
println!("Deletion cancelled.");
debug!("deletion cancelled by user.");
return Ok(());
}
let code = exit_code_from_error(&e);
error!("{}", e);
std::process::exit(code);
}
ctrl_c_handler::spawn_ctrl_c_handler(cancellation_token);
let indicator_join_handle = indicator::show_indicator(
pipeline.get_stats_receiver(),
ui_config::is_progress_indicator_needed(&config),
ui_config::is_show_result_needed(&config),
config.dry_run,
);
pipeline.run().await;
match indicator_join_handle.await {
Ok(_summary) => {}
Err(e) => {
error!("indicator task panicked: {}", e);
std::process::exit(EXIT_CODE_ABNORMAL_TERMINATION);
}
}
let duration_sec = format!("{:.3}", start_time.elapsed().as_secs_f32());
if pipeline.has_error() {
if pipeline.has_panic() {
error!(
duration_sec = duration_sec,
"s7cmd clean abnormal termination."
);
std::process::exit(EXIT_CODE_ABNORMAL_TERMINATION);
}
let Some(errors) = pipeline.get_errors_and_consume() else {
error!(duration_sec = duration_sec, "s7cmd clean failed.");
std::process::exit(1);
};
let mut code = 1;
for err in &errors {
if is_cancelled_error(err) {
debug!("deletion cancelled by user.");
return Ok(());
}
code = code.max(exit_code_from_error(err));
error!("{}", err);
}
error!(duration_sec = duration_sec, "s7cmd clean failed.");
std::process::exit(code);
}
has_warning = pipeline.has_warning();
debug!(
duration_sec = duration_sec,
"s7cmd clean has been completed."
);
}
if has_warning {
std::process::exit(EXIT_CODE_WARNING);
}
Ok(())
}
#[cfg(test)]
mod tests {
use s3rm_rs::parse_from_args;
use super::*;
fn config_from_args(args: &[&str]) -> Config {
Config::try_from(parse_from_args(args).unwrap()).unwrap()
}
#[test]
fn load_config_exit_if_err_returns_config_for_valid_args() {
let args = vec![
"s3rm",
"--target-profile",
"p",
"--force",
"s3://test-bucket/prefix/",
];
let cli_args = parse_from_args(args).unwrap();
let config = load_config_exit_if_err(cli_args);
let s3rm_rs::types::StoragePath::S3 { bucket, prefix } = config.target;
assert_eq!(bucket, "test-bucket");
assert_eq!(prefix, "prefix/");
}
#[test]
fn start_tracing_if_necessary_returns_false_when_no_tracing_config() {
let config = config_from_args(&[
"s3rm",
"-qqq",
"--target-profile",
"p",
"--force",
"s3://test-bucket",
]);
assert!(config.tracing_config.is_none());
assert!(!start_tracing_if_necessary(&config));
}
}