use anyhow::Result;
use tracing::{debug, error};
use s3ls_rs::bucket_lister;
use s3ls_rs::config::Config;
use s3ls_rs::{
ListingPipeline, create_pipeline_cancellation_token, exit_code_from_error, is_cancelled_error,
};
mod ctrl_c_handler;
mod tracing_init;
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> {
if config.target.bucket.is_empty() {
return match bucket_lister::list_buckets(&config).await {
Ok(()) => Ok(0),
Err(e) => {
if let Some(io_err) = e.downcast_ref::<std::io::Error>()
&& io_err.kind() == std::io::ErrorKind::BrokenPipe
{
return Ok(0);
}
error!("{}", e);
Ok(1)
}
};
}
let cancellation_token = create_pipeline_cancellation_token();
ctrl_c_handler::spawn_ctrl_c_handler(cancellation_token.clone());
let start_time = tokio::time::Instant::now();
debug!("listing pipeline start.");
let pipeline = ListingPipeline::new(config, cancellation_token);
match pipeline.run().await {
Ok(()) => {
let duration_sec = format!("{:.3}", start_time.elapsed().as_secs_f32());
debug!(duration_sec = duration_sec, "s7cmd ls has been completed.");
Ok(0)
}
Err(e) => {
if let Some(io_err) = e.downcast_ref::<std::io::Error>()
&& io_err.kind() == std::io::ErrorKind::BrokenPipe
{
return Ok(0);
}
let duration_sec = format!("{:.3}", start_time.elapsed().as_secs_f32());
if is_cancelled_error(&e) {
debug!("listing cancelled by user.");
return Ok(0);
}
let code = exit_code_from_error(&e);
error!(duration_sec = duration_sec, "s7cmd ls failed.");
error!("{}", e);
Ok(code)
}
}
}
#[cfg(test)]
mod tests {
use clap::Parser;
use s3ls_rs::CLIArgs;
use super::*;
fn parse(args: &[&str]) -> CLIArgs {
CLIArgs::try_parse_from(args).unwrap()
}
fn build_config(args: &[&str]) -> Config {
Config::try_from(parse(args)).unwrap()
}
#[test]
fn start_tracing_if_necessary_returns_false_when_no_tracing_config() {
let config = build_config(&["s3ls", "-qqq", "--target-profile", "p", "s3://test-bucket"]);
assert!(config.tracing_config.is_none());
assert!(!start_tracing_if_necessary(&config));
}
#[tokio::test]
async fn run_bucket_listing_against_fake_endpoint_returns_one() {
let config = build_config(&[
"s3ls",
"--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",
]);
assert!(config.target.bucket.is_empty());
let code = run(config).await.unwrap();
assert_eq!(code, 1);
}
#[tokio::test]
async fn run_listing_pipeline_against_fake_endpoint_returns_nonzero() {
let config = build_config(&[
"s3ls",
"--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/",
]);
assert!(!config.target.bucket.is_empty());
let code = run(config).await.unwrap();
assert_ne!(code, 0);
}
}