#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate log;
use std::fs::{create_dir, read_dir, File, OpenOptions};
use std::io;
use std::io::stdout;
use std::io::{BufRead, BufReader, Read, Write};
use std::str;
use std::str::FromStr;
use clap::{CommandFactory, Parser};
use colored::{self, *};
use dirs::home_dir;
use log::LevelFilter;
use regex::Regex;
use command::{do_command, secret, Cli, S3rsCmd};
use config::Config;
use logger::Logger;
mod command;
mod config;
mod logger;
static MY_LOGGER: Logger = Logger;
static S3RS_CONFIG_FOLDER: &str = ".config/s3rs";
fn read_parse<T>(tty: &mut File, prompt: &str, min: T, max: T) -> io::Result<T>
where
T: FromStr + Ord,
{
let _ = tty.write_all(prompt.as_bytes());
let mut reader = io::BufReader::new(tty);
let mut result = String::new();
let _ = reader.read_line(&mut result);
match result.replace('\n', "").parse::<T>() {
Ok(x) => {
if x >= min && x <= max {
Ok(x)
} else {
read_parse(reader.into_inner(), prompt, min, max)
}
}
_ => read_parse(reader.into_inner(), prompt, min, max),
}
}
fn my_pick_from_list_internal<T: AsRef<str>>(items: &[T], prompt: &str) -> io::Result<usize> {
let mut tty = OpenOptions::new().read(true).write(true).open("/dev/tty")?;
let pad_len = ((items.len() as f32).log10().floor() + 1.0) as usize;
for (i, item) in items.iter().enumerate() {
tty.write_all(
format!(
"{1:0$}. {2}\n",
pad_len,
i + 1,
item.as_ref().replace('\n', "")
)
.as_bytes(),
)?
}
let idx = read_parse::<usize>(&mut tty, prompt, 1, items.len())? - 1;
Ok(idx)
}
fn main() -> io::Result<()> {
log::set_logger(&MY_LOGGER).unwrap();
log::set_max_level(LevelFilter::Warn);
let mut run_time_secret: Vec<u8> = Vec::new();
let mut matches = Cli::parse();
if let Some(s) = matches.secret {
secret::change_secret(&mut run_time_secret, s.to_string(), false);
}
let mut config_contents = String::new();
let mut interactive: bool;
let s3rs_config_folder = home_dir().unwrap().join(S3RS_CONFIG_FOLDER); match matches.config {
Some(ref path) => {
let mut config_path = path.to_string();
if s3rs_config_folder.exists() {
for entry in read_dir(s3rs_config_folder).unwrap() {
let p = entry.unwrap().path();
if path == p.file_stem().unwrap().to_str().unwrap_or("") {
config_path = p.to_str().unwrap_or("").to_string();
}
}
};
let mut f = File::open(config_path).expect("cannot open file");
f.read_to_string(&mut config_contents)
.expect("cannot read file");
interactive = false;
let args: Vec<String> = std::env::args().collect();
matches.s3rs_cmd = Some(S3rsCmd::parse_from(args[1..].iter()));
}
None => {
let legacy_s3rs_config = home_dir().unwrap().join(".s3rs.toml");
if s3rs_config_folder.exists() {
for entry in read_dir(s3rs_config_folder).unwrap() {
let path = entry.unwrap().path();
if !path.is_dir() {
let mut f = File::open(path).expect("cannot open file");
f.read_to_string(&mut config_contents)
.expect("cannot read file");
}
}
} else if legacy_s3rs_config.exists() {
println!("{}", "legacy s3rs config file detected, you may split it into different config files, and put them under ~/.config/s3rs".bold());
let mut f =
File::open(legacy_s3rs_config).expect("Can not open legacy 3rs config file");
f.read_to_string(&mut config_contents)
.expect("legacy s3rs config is not readable");
} else {
create_dir(s3rs_config_folder.clone()).expect("create config folder fail");
let mut f = File::create(s3rs_config_folder.join("aws-example.toml"))
.expect("Can not write s3rs config example file");
let _ = f.write_all(include_str!("../config_examples/aws-example.toml").as_bytes());
let mut f = File::create(s3rs_config_folder.join("ceph-example.toml"))
.expect("Can not write s3rs config example file");
let _ =
f.write_all(include_str!("../config_examples/ceph-example.toml").as_bytes());
println!(
"Example files is created in {}, multiple toml files can put under this folder",
"~/.config/s3rs".bold()
);
return Ok(());
}
if config_contents.is_empty() {
println!(
"{}",
"Lack of config files please put in ~/.config/s3rs".bold()
);
return Ok(());
}
interactive = true;
}
};
let mut config: Config = toml::from_str(config_contents.as_str()).unwrap();
let mut reload_config = true;
while reload_config {
reload_config = false;
config.decrypt(&run_time_secret);
let config_option: Vec<String> = config.gen_selections();
let chosen_int = if config_option.len() == 1 {
0usize
} else {
my_pick_from_list_internal(&config_option, "Selection: ").unwrap()
};
let config_list = config.credential.clone().unwrap().clone();
let mut handler = s3handler::Handler::from(&config_list[chosen_int]);
let login_user = config_list[chosen_int]
.user
.clone()
.unwrap_or("unknown".to_string());
let s3_type = config_list[chosen_int]
.s3_type
.clone()
.unwrap_or("aws".to_string());
if matches.config.is_none() {
println!(
"enter command, type {} for usage or type {} for quit",
"help".bold(),
"exit".bold()
);
};
let mut command = String::new();
while matches.s3rs_cmd != Some(S3rsCmd::Quit) {
stdout().flush().expect("Could not flush stdout");
if command.starts_with("logout") {
reload_config = true;
break;
} else if command.starts_with("secret") {
let mut command = command.strip_prefix("secret").unwrap().trim().to_string();
secret::do_command(&mut run_time_secret, &mut command, &config_list, chosen_int)
} else if command.starts_with("exit") || command.starts_with("quit") {
interactive = false;
} else {
do_command(&mut handler, &s3_type, matches.s3rs_cmd.take());
}
if !interactive {
break;
}
command = match OpenOptions::new().read(true).write(true).open("/dev/tty") {
Ok(mut tty) => {
tty.flush().expect("Could not open tty");
let _ = tty.write_all(
format!("{} {} {} ", "s3rs".green(), login_user.cyan(), ">".green())
.as_bytes(),
);
let reader = BufReader::new(&tty);
let mut command_iter = reader.lines().map(|l| l.unwrap());
command_iter.next().unwrap_or("logout".to_string())
}
Err(e) => {
println!("{:?}", e);
"quit".to_string()
}
};
matches.s3rs_cmd = if command.starts_with('/') {
Some(S3rsCmd::Query {
url: command.clone(),
})
} else if command.starts_with("help") {
let mut usage = Vec::<u8>::new();
let mut command = <S3rsCmd as CommandFactory>::command();
command
.write_help(&mut usage)
.expect("fail to get help of program");
let usage = unsafe { std::str::from_utf8_unchecked(&usage) };
let mut after_match = false;
let re = Regex::new("Commands:").unwrap();
let option_re = Regex::new("Options:").unwrap();
for line in usage.split('\n') {
if option_re.is_match(line) {
break;
}
if after_match {
println!("{}", line);
}
if !after_match && re.is_match(line) {
after_match = true;
}
}
None
} else {
let mut new_s3_cmd = vec![""];
new_s3_cmd.append(&mut command.split_whitespace().collect());
S3rsCmd::try_parse_from(new_s3_cmd).ok()
};
}
}
Ok(())
}