use anyhow::Result;
use tracing::{debug, error};
use s3rm_rs::config::Config;
use s3rm_rs::{
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_SUCCESS: i32 = 0;
pub const EXIT_CODE_WARNING: i32 = 3;
pub const EXIT_CODE_ABNORMAL_TERMINATION: i32 = 101;
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<i32> {
#[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) {
eprintln!("Deletion cancelled.");
debug!("deletion cancelled by user.");
return Ok(EXIT_CODE_SUCCESS);
}
let code = exit_code_from_error(&e);
error!("{}", e);
return Ok(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);
return Ok(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."
);
return Ok(EXIT_CODE_ABNORMAL_TERMINATION);
}
let Some(errors) = pipeline.get_errors_and_consume() else {
error!(duration_sec = duration_sec, "s7cmd clean failed.");
return Ok(1);
};
let mut code = 1;
for err in &errors {
if is_cancelled_error(err) {
debug!("deletion cancelled by user.");
return Ok(EXIT_CODE_SUCCESS);
}
code = code.max(exit_code_from_error(err));
error!("{}", err);
}
error!(duration_sec = duration_sec, "s7cmd clean failed.");
return Ok(code);
}
has_warning = pipeline.has_warning();
debug!(
duration_sec = duration_sec,
"s7cmd clean has been completed."
);
}
if has_warning {
return Ok(EXIT_CODE_WARNING);
}
Ok(EXIT_CODE_SUCCESS)
}
#[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 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));
}
#[tokio::test]
async fn run_with_force_against_fake_endpoint_returns_error_code() {
let config = config_from_args(&[
"s3rm",
"--force",
"--target-access-key",
"dummy",
"--target-secret-access-key",
"dummy",
"--target-endpoint-url",
"http://127.0.0.1:1",
"--target-region",
"us-east-1",
"--connect-timeout-milliseconds",
"1",
"--aws-max-attempts",
"0",
"s3://nonexistent-bucket-for-s7cmd-tests/prefix/",
]);
let code = run(config).await.unwrap();
assert_ne!(code, EXIT_CODE_SUCCESS);
assert_ne!(code, EXIT_CODE_ABNORMAL_TERMINATION);
}
#[tokio::test]
async fn run_dry_run_against_fake_endpoint_returns_error_code() {
let config = config_from_args(&[
"s3rm",
"--dry-run",
"--target-access-key",
"dummy",
"--target-secret-access-key",
"dummy",
"--target-endpoint-url",
"http://127.0.0.1:1",
"--target-region",
"us-east-1",
"--connect-timeout-milliseconds",
"1",
"--aws-max-attempts",
"0",
"s3://nonexistent-bucket-for-s7cmd-tests/prefix/",
]);
let code = run(config).await.unwrap();
assert_ne!(code, EXIT_CODE_ABNORMAL_TERMINATION);
}
}