Skip to content

Commit 3294523

Browse files
feat: implement profile management commands (#213)
Implements profile set, remove, and default commands with safety features: - profile set: Create/update profiles with overwrite protection - profile remove: Delete profiles with confirmation prompt - profile default: Set the default profile Safety features: - Confirmation prompts before overwriting existing profiles - Confirmation prompts before removing profiles - Warning when removing the default profile - Password prompting for Enterprise profiles when not provided - Automatic default profile suggestion for first/only profile Fixes #181
1 parent 79005a5 commit 3294523

File tree

2 files changed

+175
-6
lines changed

2 files changed

+175
-6
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/redisctl/src/main.rs

Lines changed: 172 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::Result;
1+
use anyhow::{Context, Result};
22
use clap::Parser;
33
use tracing::{debug, error, info, trace};
44
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@@ -325,8 +325,177 @@ async fn execute_profile_command(
325325
None => Err(RedisCtlError::ProfileNotFound { name: name.clone() }),
326326
},
327327

328-
_ => {
329-
println!("Profile management commands (set, remove, default) are not yet implemented");
328+
Set {
329+
name,
330+
deployment,
331+
api_key,
332+
api_secret,
333+
api_url,
334+
url,
335+
username,
336+
password,
337+
insecure,
338+
} => {
339+
debug!("Setting profile: {}", name);
340+
341+
// Check if profile already exists
342+
if conn_mgr.config.profiles.contains_key(name) {
343+
// Ask for confirmation before overwriting
344+
println!("Profile '{}' already exists.", name);
345+
print!("Do you want to overwrite it? (y/N): ");
346+
use std::io::{self, Write};
347+
io::stdout().flush().unwrap();
348+
349+
let mut input = String::new();
350+
io::stdin().read_line(&mut input).unwrap();
351+
let input = input.trim().to_lowercase();
352+
353+
if input != "y" && input != "yes" {
354+
println!("Profile update cancelled.");
355+
return Ok(());
356+
}
357+
}
358+
359+
// Create the profile based on deployment type
360+
let profile = match deployment {
361+
config::DeploymentType::Cloud => {
362+
let api_key = api_key
363+
.clone()
364+
.ok_or_else(|| anyhow::anyhow!("API key is required for Cloud profiles"))?;
365+
let api_secret = api_secret.clone().ok_or_else(|| {
366+
anyhow::anyhow!("API secret is required for Cloud profiles")
367+
})?;
368+
369+
config::Profile {
370+
deployment_type: config::DeploymentType::Cloud,
371+
credentials: config::ProfileCredentials::Cloud {
372+
api_key: api_key.clone(),
373+
api_secret: api_secret.clone(),
374+
api_url: api_url.clone(),
375+
},
376+
}
377+
}
378+
config::DeploymentType::Enterprise => {
379+
let url = url.clone().ok_or_else(|| {
380+
anyhow::anyhow!("URL is required for Enterprise profiles")
381+
})?;
382+
let username = username.clone().ok_or_else(|| {
383+
anyhow::anyhow!("Username is required for Enterprise profiles")
384+
})?;
385+
386+
// Prompt for password if not provided
387+
let password = match password {
388+
Some(p) => Some(p.clone()),
389+
None => {
390+
let pass = rpassword::prompt_password("Enter password: ")
391+
.context("Failed to read password")?;
392+
Some(pass)
393+
}
394+
};
395+
396+
config::Profile {
397+
deployment_type: config::DeploymentType::Enterprise,
398+
credentials: config::ProfileCredentials::Enterprise {
399+
url: url.clone(),
400+
username: username.clone(),
401+
password,
402+
insecure: *insecure,
403+
},
404+
}
405+
}
406+
};
407+
408+
// Update the configuration
409+
let mut config = conn_mgr.config.clone();
410+
config.profiles.insert(name.clone(), profile);
411+
412+
// Save the configuration
413+
config.save().context("Failed to save configuration")?;
414+
415+
println!("Profile '{}' saved successfully.", name);
416+
417+
// Ask if this should be the default profile
418+
if config.default_profile.is_none() || config.profiles.len() == 1 {
419+
print!("Set '{}' as the default profile? (Y/n): ", name);
420+
use std::io::{self, Write};
421+
io::stdout().flush().unwrap();
422+
423+
let mut input = String::new();
424+
io::stdin().read_line(&mut input).unwrap();
425+
let input = input.trim().to_lowercase();
426+
427+
if input.is_empty() || input == "y" || input == "yes" {
428+
config.default_profile = Some(name.clone());
429+
config.save().context("Failed to save default profile")?;
430+
println!("Profile '{}' set as default.", name);
431+
}
432+
}
433+
434+
Ok(())
435+
}
436+
Remove { name } => {
437+
debug!("Removing profile: {}", name);
438+
439+
// Check if profile exists
440+
if !conn_mgr.config.profiles.contains_key(name) {
441+
return Err(RedisCtlError::ProfileNotFound { name: name.clone() });
442+
}
443+
444+
// Check if it's the default profile
445+
let is_default = conn_mgr.config.default_profile.as_ref() == Some(name);
446+
if is_default {
447+
println!("Warning: '{}' is the default profile.", name);
448+
}
449+
450+
// Ask for confirmation
451+
print!(
452+
"Are you sure you want to remove profile '{}'? (y/N): ",
453+
name
454+
);
455+
use std::io::{self, Write};
456+
io::stdout().flush().unwrap();
457+
458+
let mut input = String::new();
459+
io::stdin().read_line(&mut input).unwrap();
460+
let input = input.trim().to_lowercase();
461+
462+
if input != "y" && input != "yes" {
463+
println!("Profile removal cancelled.");
464+
return Ok(());
465+
}
466+
467+
// Remove the profile
468+
let mut config = conn_mgr.config.clone();
469+
config.profiles.remove(name);
470+
471+
// Clear default if this was the default profile
472+
if is_default {
473+
config.default_profile = None;
474+
println!("Default profile cleared.");
475+
}
476+
477+
// Save the configuration
478+
config.save().context("Failed to save configuration")?;
479+
480+
println!("Profile '{}' removed successfully.", name);
481+
Ok(())
482+
}
483+
Default { name } => {
484+
debug!("Setting default profile: {}", name);
485+
486+
// Check if profile exists
487+
if !conn_mgr.config.profiles.contains_key(name) {
488+
return Err(RedisCtlError::ProfileNotFound { name: name.clone() });
489+
}
490+
491+
// Update the configuration
492+
let mut config = conn_mgr.config.clone();
493+
config.default_profile = Some(name.clone());
494+
495+
// Save the configuration
496+
config.save().context("Failed to save configuration")?;
497+
498+
println!("Default profile set to '{}'.", name);
330499
Ok(())
331500
}
332501
}

0 commit comments

Comments
 (0)