use aws_config::environment::credentials::EnvironmentVariableCredentialsProvider;
use aws_config::meta::credentials::CredentialsProviderChain;
use aws_config::meta::region::RegionProviderChain;
use aws_config::profile::ProfileFileCredentialsProvider;
use aws_config::BehaviorVersion;
use aws_types::region::Region;
use std::env;
use std::fs;
use std::io;
use std::io::Write;
use std::path::PathBuf;
use std::process::exit;
use tracing::Level;
use tracing_subscriber::FmtSubscriber;
use clap::Parser;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use crate::constants;
use colored::*;
use dirs::home_dir;
pub fn configure_tracing(level: Level) {
let subscriber = FmtSubscriber::builder()
.with_max_level(level)
.finish();
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
}
pub async fn configure_aws(fallback_region: &str, profile_name: Option<String>) -> aws_config::SdkConfig {
let region_provider =
RegionProviderChain::first_try(env::var("AWS_DEFAULT_REGION").ok().map(Region::new))
.or_default_provider()
.or_else(Region::new(fallback_region.to_string()));
let mut provider = CredentialsProviderChain::first_try(
"Environment",
EnvironmentVariableCredentialsProvider::new(),
);
if let Some(profile_name) = profile_name {
provider = provider
.or_else( "Profile",
ProfileFileCredentialsProvider::builder()
.profile_name(profile_name) .build(),
);
};
let provider = provider.or_default_provider().await;
aws_config::defaults(BehaviorVersion::latest())
.credentials_provider(provider)
.region(region_provider)
.load()
.await
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
pub bucket_name: String,
#[serde(deserialize_with = "deserialize_prefix")]
pub bucket_prefix: Option<String>,
pub presigned_time: u64,
pub aws_profile: Option<String>,
}
fn deserialize_prefix<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let mut prefix = String::deserialize(deserializer)?;
if prefix.is_empty() {
Ok(None)
} else if prefix.ends_with('/') {
Ok(Some(prefix))
} else {
prefix = format!("{}/", prefix);
Ok(Some(prefix))
}
}
impl Config {
pub fn load_config() -> Result<Self, anyhow::Error> {
let home_dir = home_dir().expect("Failed to get HOME directory");
let config_dir = home_dir.join(".config/shuk");
let config_file_path = config_dir.join(constants::CONFIG_FILE_NAME);
if check_for_config() {
let _contents: String = match fs::read_to_string(config_file_path) {
Ok(c) => {
let config: Config = toml::from_str::<Config>(&c).unwrap();
return Ok(config);
}
Err(e) => {
eprintln!("Could not read config file! {}", e);
eprintln!("Your configuration file needs to be in $HOME/.config/shuk/shuk.toml; Please run the configuration command: shuk --init");
exit(1);
}
};
} else {
eprintln!("Could not read config file!");
eprintln!("Your configuration file needs to be in $HOME/.config/shuk/shuk.toml; Please run the configuration command: shuk --init");
exit(1);
}
}
}
pub fn check_for_config() -> bool {
let home_dir = home_dir().expect("Failed to get HOME directory");
let config_dir = home_dir.join(".config/shuk");
let config_file_path = config_dir.join("shuk.toml");
match config_file_path.try_exists() {
Ok(b) => b,
Err(e) => {
eprintln!("Was unable to determine if the config file exists: {}", e);
exit(1);
}
}
}
pub async fn initialize_config() -> Result<(), anyhow::Error> {
let home_dir = home_dir().expect("Failed to get HOME directory");
let config_dir = home_dir.join(format!(".config/{}", constants::CONFIG_DIR_NAME));
fs::create_dir_all(&config_dir)?;
let config_file_path = config_dir.join(constants::CONFIG_FILE_NAME);
let config_content = constants::CONFIG_FILE.to_string();
let mut default_config: Config =
toml::from_str::<Config>(&config_content).expect("default config must be valid");
let mut bucket_name = String::new();
print!("Enter the name of the bucket you wish to use for file uploads: ");
io::stdout().flush()?; io::stdin().read_line(&mut bucket_name)?;
default_config.bucket_name = bucket_name.trim().to_string();
let mut bucket_prefix = String::new();
print!("Enter the prefix (folder) in that bucket where the files will be uploaded (leave blank for the root of the bucket): ");
io::stdout().flush()?; io::stdin().read_line(&mut bucket_prefix)?;
default_config.bucket_prefix = Some(bucket_prefix.trim().to_string());
let mut config_profile = String::new();
print!("Enter the AWS profile name (enter for None): ");
io::stdout().flush()?; io::stdin().read_line(&mut config_profile)?;
let config_profile = config_profile.trim();
default_config.aws_profile = if config_profile.is_empty() {
None
} else {
Some(config_profile.to_string())
};
fs::write(&config_file_path, toml::to_string_pretty(&default_config)?)?;
println!(
"⏳| Shuk configuration file created at: {:?}",
config_file_path
);
println!("This file is used to store configuration items for the shuk application.");
println!("✅ | Shuk configuration has been initialized in ~/.config/shuk. You may now use it as normal.");
Ok(())
}
pub fn print_warning(s: &str) {
println!("{}", s.yellow());
}
#[derive(Parser, Default)]
#[command(version, about, long_about = None)]
pub struct Args {
#[arg(required_unless_present("init"))]
pub filename: Option<PathBuf>,
#[arg(long, conflicts_with("filename"))]
pub init: bool,
}