Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for multiple profiles #18

Merged
merged 2 commits into from
Nov 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 80 additions & 50 deletions src/commands/configure/configure.rs
Original file line number Diff line number Diff line change
@@ -1,77 +1,102 @@
use log::debug;
use std::path::Path;
use serde::{de::DeserializeOwned, Serialize};

use maplit::hashmap;
use tokio::{
fs::{self, File},
io::{self, AsyncBufReadExt, AsyncWriteExt, BufReader},
};
use tokio::io::{self, AsyncBufReadExt, AsyncWriteExt, BufReader};

use crate::{
credentials::{Credentials, CredentialsConfig},
utils::{get_credentials_file_path, get_momento_dir, read_toml_file},
config::{Config, Credentials, Profiles},
utils::{
file::{
create_dir_if_not_exists, create_file_if_not_exists, get_config_file_path,
get_credentials_file_path, get_momento_dir, read_toml_file, set_file_read_write,
set_file_readonly, write_to_existing_file,
},
user::{get_config_for_profile, get_creds_for_profile},
},
};

pub async fn configure_momento() {
let token = prompt_user_for_input("Token: ").await;
pub async fn configure_momento(profile_name: &str) {
let credentials = prompt_user_for_creds(profile_name).await;
let config = prompt_user_for_config(profile_name).await;

let momento_dir = get_momento_dir();
let credentials_file_path = get_credentials_file_path();
let config_file_path = get_config_file_path();

create_dir_if_not_exists(&momento_dir).await;
create_file_if_not_exists(&credentials_file_path).await;
create_file_if_not_exists(&config_file_path).await;

// explicitly allowing read/write access to the credentials file
set_file_read_write(&credentials_file_path).await.unwrap();
add_profile(profile_name, credentials, &credentials_file_path).await;
// explicitly revoking that access
set_file_readonly(&credentials_file_path).await.unwrap();

let default_credentials = Credentials { token: token };
add_credentials("default", default_credentials).await;
add_profile(profile_name, config, &config_file_path).await;
}

async fn add_credentials(profile: &str, creds: Credentials) {
let path = get_credentials_file_path();
async fn prompt_user_for_creds(profile_name: &str) -> Credentials {
let current_credentials = get_creds_for_profile(profile_name)
.await
.unwrap_or_default();

let mut credentials_toml = match read_toml_file::<CredentialsConfig>(&path).await {
Ok(t) => t,
Err(_) => {
debug!("credentials file is invalid, most likely we are creating it for the first time. Overwriting it with new profile");
CredentialsConfig {
profile: hashmap! {},
}
}
};
credentials_toml.profile.insert(profile.to_string(), creds);
let new_creds_string = toml::to_string(&credentials_toml).unwrap();
write_to_existing_file(&path, &new_creds_string).await;
}
let token = prompt_user_for_input("Token", current_credentials.token.as_str(), true).await;

async fn write_to_existing_file(filepath: &str, buffer: &str) {
match tokio::fs::write(filepath, buffer).await {
Ok(_) => debug!("wrote buffer to file {}", filepath),
Err(e) => panic!("failed to write to file {}, error: {}", filepath, e),
};
return Credentials { token };
}

async fn create_file_if_not_exists(path: &str) {
if !Path::new(path).exists() {
let res = File::create(path).await;
match res {
Ok(_) => debug!("created file {}", path),
Err(e) => panic!("failed to create file {}, error: {}", path, e),
}
async fn prompt_user_for_config(profile_name: &str) -> Config {
let current_config = get_config_for_profile(profile_name)
.await
.unwrap_or_default();

let cache_name =
prompt_user_for_input("Default Cache", current_config.cache.as_str(), false).await;
let ttl = prompt_user_for_input(
"Default Ttl Seconds",
current_config.ttl.to_string().as_str(),
false,
)
.await
.parse::<u32>()
.unwrap();

return Config {
cache: cache_name,
ttl,
};
}

async fn create_dir_if_not_exists(path: &str) {
if !Path::new(path).exists() {
let res = fs::create_dir_all(path).await;
match res {
Ok(_) => debug!("created directory {}", path),
Err(e) => panic!("failed to created directory {}, error: {}", path, e),
async fn add_profile<T>(profile_name: &str, config: T, config_file_path: &str)
where
T: DeserializeOwned + Default + Serialize,
{
let mut toml = match read_toml_file::<Profiles<T>>(config_file_path).await {
Ok(t) => t,
Err(_) => {
debug!("config file is invalid, most likely we are creating it for the first time. Overwriting it with new profile");
Profiles::<T>::default()
}
}
};
toml.profile.insert(profile_name.to_string(), config);
let new_profile_string = toml::to_string(&toml).unwrap();
write_to_existing_file(config_file_path, &new_profile_string).await;
}

async fn prompt_user_for_input(prompt: &str) -> String {
async fn prompt_user_for_input(prompt: &str, default_value: &str, is_secret: bool) -> String {
let mut stdout = io::stdout();
match stdout.write(prompt.as_bytes()).await {
Ok(_) => debug!("wrote prompt '{}' to stdout", prompt),

let formatted_prompt = if default_value.is_empty() {
format!("{}: ", prompt)
} else if is_secret {
format!("{} [****]: ", prompt)
} else {
format!("{} [{}]: ", prompt, default_value)
};

match stdout.write(formatted_prompt.as_bytes()).await {
Ok(_) => debug!("wrote prompt '{}' to stdout", formatted_prompt),
Err(e) => panic!("failed to write prompt to stdout: {}", e),
};
match stdout.flush().await {
Expand All @@ -85,5 +110,10 @@ async fn prompt_user_for_input(prompt: &str) -> String {
Ok(_) => debug!("read line from stdin"),
Err(e) => panic!("failed to read line from stdin: {}", e),
};
return buffer.as_str().trim_end().to_string();

let input = buffer.as_str().trim_end().to_string();
if input.is_empty() {
return default_value.to_string();
}
return input;
}
22 changes: 22 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::collections::HashMap;

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Clone, Default)]
pub struct Profiles<T>
where
T: Default,
{
pub profile: HashMap<String, T>,
}

#[derive(Deserialize, Serialize, Clone, Default)]
pub struct Config {
pub cache: String,
pub ttl: u32,
}

#[derive(Deserialize, Serialize, Clone, Default)]
pub struct Credentials {
pub token: String,
}
13 changes: 0 additions & 13 deletions src/credentials.rs

This file was deleted.

40 changes: 26 additions & 14 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ use std::panic;
use env_logger::Env;
use log::error;
use structopt::StructOpt;
use utils::get_creds_for_profile;
use utils::user::get_creds_and_config;

mod commands;
mod credentials;
mod config;
mod utils;

#[derive(StructOpt)]
Expand All @@ -27,7 +27,10 @@ enum Subcommand {
operation: CacheCommand,
},
#[structopt(about = "Configure Momento Credentials")]
Configure {},
Configure {
#[structopt(name = "profile", long, short, default_value = "default")]
profile: String,
},
#[structopt(about = "Manage Accounts")]
Account {
#[structopt(subcommand)]
Expand Down Expand Up @@ -55,7 +58,7 @@ enum CacheCommand {
#[structopt(about = "Stores a given item in cache")]
Set {
#[structopt(name = "name", long, short)]
cache_name: String,
cache_name: Option<String>,
// TODO: Add support for non-string key-value
#[structopt(long, short)]
key: String,
Expand All @@ -64,16 +67,15 @@ enum CacheCommand {
#[structopt(
long = "ttl",
short = "ttl",
default_value = "300",
help = "Max time, in seconds, that the item will be stored in cache"
)]
ttl_seconds: u32,
ttl_seconds: Option<u32>,
},

#[structopt(about = "Gets item from the cache")]
Get {
#[structopt(name = "name", long, short)]
cache_name: String,
cache_name: Option<String>,
// TODO: Add support for non-string key-value
#[structopt(long, short)]
key: String,
Expand Down Expand Up @@ -106,7 +108,7 @@ async fn main() {
match args.command {
Subcommand::Cache { operation } => match operation {
CacheCommand::Create { cache_name } => {
let creds = get_creds_for_profile(None).await;
let (creds, _config) = get_creds_and_config().await;
commands::cache::cache::create_cache(cache_name, creds.token).await
}
CacheCommand::Set {
Expand All @@ -115,19 +117,29 @@ async fn main() {
value,
ttl_seconds,
} => {
let creds = get_creds_for_profile(None).await;
commands::cache::cache::set(cache_name, creds.token, key, value, ttl_seconds).await
let (creds, config) = get_creds_and_config().await;
commands::cache::cache::set(
cache_name.unwrap_or(config.cache),
creds.token,
key,
value,
ttl_seconds.unwrap_or(config.ttl),
)
.await
}
CacheCommand::Get { cache_name, key } => {
let creds = get_creds_for_profile(None).await;
commands::cache::cache::get(cache_name, creds.token, key).await;
let (creds, config) = get_creds_and_config().await;
commands::cache::cache::get(cache_name.unwrap_or(config.cache), creds.token, key)
.await;
}
CacheCommand::Delete { cache_name } => {
let creds = get_creds_for_profile(None).await;
let (creds, _config) = get_creds_and_config().await;
commands::cache::cache::delete_cache(cache_name, creds.token).await
}
},
Subcommand::Configure {} => commands::configure::configure::configure_momento().await,
Subcommand::Configure { profile } => {
commands::configure::configure::configure_momento(&profile).await
}
Subcommand::Account { operation } => match operation {
AccountCommand::Signup { email } => {
commands::account::signup_user(email).await;
Expand Down
48 changes: 0 additions & 48 deletions src/utils.rs

This file was deleted.

Loading