use log::{info, error, LevelFilter};
use structopt::StructOpt;
use simplelog::{SimpleLogger, Config as LogConfig};
use s3::{
creds::Credentials,
region::Region,
bucket::Bucket,
};
use glob::glob as globber;
#[derive(Clone, PartialEq, Debug, StructOpt)]
pub struct Options {
#[structopt(long, env)]
pub access_key: String,
#[structopt(long, env)]
pub secret_key: String,
#[structopt(long, env="S3_BUCKET")]
pub bucket: String,
#[structopt(long, env="S3_REGION")]
pub region: String,
#[structopt(long, env="S3_ENDPOINT")]
pub endpoint: String,
#[structopt(subcommand)]
command: Command,
#[structopt(long, default_value="info")]
pub log_level: LevelFilter,
}
#[derive(Clone, PartialEq, Debug, StructOpt)]
pub enum Command {
List{
#[structopt(long, default_value="")]
prefix: String
},
Upload{
name: String,
file: String,
#[structopt(long)]
acl: Option<String>,
},
UploadDir{
#[structopt(long, default_value="")]
prefix: String,
glob: String,
#[structopt(long)]
acl: Option<String>,
},
Download{
name: String,
file: String,
},
Delete{
name: String,
}
}
#[async_std::main]
async fn main() -> Result<(), anyhow::Error> {
let opts = Options::from_args();
let _ = SimpleLogger::init(opts.log_level, LogConfig::default());
let creds = Credentials::new(Some(&opts.access_key), Some(&opts.secret_key), None, None, None)?;
let region = Region::Custom{ region: opts.region, endpoint: opts.endpoint };
let mut bucket = Bucket::new(&opts.bucket, region, creds)?;
match &opts.command {
Command::List{ prefix } => {
for list in bucket.list(prefix.to_string(), None).await? {
println!("{:?}", list);
}
},
Command::Upload{ name, file, acl } => {
let files: Vec<_> = globber(file)?.filter_map(|v| v.ok() ).collect();
if files.len() == 0 {
return Err(anyhow::anyhow!("No matching file found"));
} else if files.len() > 1 {
return Err(anyhow::anyhow!("Too many matching files"));
}
let f = &files[0];
info!("Loading file '{}'", f.to_str().unwrap());
let data = std::fs::read(f)?;
if let Some(acl) = acl {
bucket.add_header("x-amz-acl", acl);
}
info!("Uploading object: '{}'", name);
let (_, code) = bucket.put_object(name, &data).await?;
if code != 200 {
return Err(anyhow::anyhow!("Error uploading object: {}", code));
}
info!("Upload complete");
},
Command::UploadDir{ prefix, glob, acl } => {
let mut count = 0usize;
for e in globber(glob)? {
let p = match e {
Ok(p) => p,
Err(e) => {
error!("Error reading file: {:?}", e);
continue;
}
};
let f = match p.file_name() {
Some(n) => n.to_str().unwrap(),
None => continue,
};
let n = format!("{}{}", prefix, f);
if let Some(acl) = acl {
bucket.add_header("x-amz-acl", acl);
}
info!("Uploading {} as {}", p.to_str().unwrap(), f);
let data = std::fs::read(p)?;
let (_, code) = bucket.put_object(n, &data).await?;
if code != 200 {
return Err(anyhow::anyhow!("Error uploading object: {}", code));
}
count += 1;
}
info!("Uploaded {} files", count);
}
Command::Download{ name, file } => {
info!("Fetching object: '{}'", name);
let (data, code) = bucket.get_object(name).await?;
if code != 200 {
return Err(anyhow::anyhow!("Error fetching object: {}", code));
}
info!("Writing file: '{}'", file);
std::fs::write(file, data)?;
info!("File write done");
},
Command::Delete{ name } => {
let (_, code) = bucket.delete_object( name ).await?;
if code != 204 {
return Err(anyhow::anyhow!("Error deleting object: {}", code));
}
}
}
Ok(())
}