Skip to content

Commit 2f5c6a3

Browse files
feat: add --config-file flag for alternate configuration file (#430)
- Add --config-file global flag and REDISCTL_CONFIG_FILE environment variable - Add Config::load_from_path() and Config::save_to_path() methods - Update ConnectionManager to track and use alternate config path - Update all profile commands to save to the correct location - Tested with both flag and environment variable Closes #428
1 parent c9b3564 commit 2f5c6a3

File tree

5 files changed

+101
-23
lines changed

5 files changed

+101
-23
lines changed

crates/redisctl-config/src/config.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use directories::ProjectDirs;
99
use serde::{Deserialize, Serialize};
1010
use std::collections::HashMap;
1111
use std::fs;
12-
use std::path::PathBuf;
12+
use std::path::{Path, PathBuf};
1313

1414
use crate::credential::CredentialStore;
1515
use crate::error::{ConfigError, Result};
@@ -306,12 +306,16 @@ impl Config {
306306
/// Load configuration from the standard location
307307
pub fn load() -> Result<Self> {
308308
let config_path = Self::config_path()?;
309+
Self::load_from_path(&config_path)
310+
}
309311

312+
/// Load configuration from a specific path
313+
pub fn load_from_path(config_path: &Path) -> Result<Self> {
310314
if !config_path.exists() {
311315
return Ok(Config::default());
312316
}
313317

314-
let content = fs::read_to_string(&config_path).map_err(|e| ConfigError::LoadError {
318+
let content = fs::read_to_string(config_path).map_err(|e| ConfigError::LoadError {
315319
path: config_path.display().to_string(),
316320
source: e,
317321
})?;
@@ -327,7 +331,11 @@ impl Config {
327331
/// Save configuration to the standard location
328332
pub fn save(&self) -> Result<()> {
329333
let config_path = Self::config_path()?;
334+
self.save_to_path(&config_path)
335+
}
330336

337+
/// Save configuration to a specific path
338+
pub fn save_to_path(&self, config_path: &Path) -> Result<()> {
331339
// Create parent directories if they don't exist
332340
if let Some(parent) = config_path.parent() {
333341
fs::create_dir_all(parent).map_err(|e| ConfigError::SaveError {
@@ -338,7 +346,7 @@ impl Config {
338346

339347
let content = toml::to_string_pretty(self)?;
340348

341-
fs::write(&config_path, content).map_err(|e| ConfigError::SaveError {
349+
fs::write(config_path, content).map_err(|e| ConfigError::SaveError {
342350
path: config_path.display().to_string(),
343351
source: e,
344352
})?;

crates/redisctl/src/cli.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ pub struct Cli {
4747
#[arg(long, short, global = true, env = "REDISCTL_PROFILE")]
4848
pub profile: Option<String>,
4949

50+
/// Path to alternate configuration file
51+
#[arg(long, global = true, env = "REDISCTL_CONFIG_FILE")]
52+
pub config_file: Option<String>,
53+
5054
/// Output format
5155
#[arg(long, short = 'o', global = true, value_enum, default_value = "auto")]
5256
pub output: OutputFormat,

crates/redisctl/src/commands/profile.rs

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,15 @@ async fn handle_list(
6868

6969
match output_format {
7070
OutputFormat::Json | OutputFormat::Yaml => {
71-
let config_path = Config::config_path()
72-
.ok()
73-
.and_then(|p| p.to_str().map(String::from));
71+
let config_path = conn_mgr
72+
.config_path
73+
.as_ref()
74+
.map(|p| p.to_string_lossy().to_string())
75+
.or_else(|| {
76+
Config::config_path()
77+
.ok()
78+
.and_then(|p| p.to_str().map(String::from))
79+
});
7480

7581
let profile_list: Vec<serde_json::Value> = profiles
7682
.iter()
@@ -123,7 +129,10 @@ async fn handle_list(
123129
}
124130
_ => {
125131
// Show config file path at the top
126-
if let Ok(config_path) = Config::config_path() {
132+
if let Some(ref path) = conn_mgr.config_path {
133+
println!("Configuration file: {}", path.display());
134+
println!();
135+
} else if let Ok(config_path) = Config::config_path() {
127136
println!("Configuration file: {}", config_path.display());
128137
println!();
129138
}
@@ -443,14 +452,21 @@ async fn handle_set(
443452
let mut config = conn_mgr.config.clone();
444453
config.profiles.insert(name.to_string(), profile);
445454

446-
// Save the configuration
447-
config.save().context("Failed to save configuration")?;
448-
449-
if let Ok(config_path) = Config::config_path() {
455+
// Save the configuration to the appropriate location
456+
if let Some(ref path) = conn_mgr.config_path {
457+
config
458+
.save_to_path(path)
459+
.context("Failed to save configuration")?;
450460
println!("Profile '{}' saved successfully to:", name);
451-
println!(" {}", config_path.display());
461+
println!(" {}", path.display());
452462
} else {
453-
println!("Profile '{}' saved successfully.", name);
463+
config.save().context("Failed to save configuration")?;
464+
if let Ok(config_path) = Config::config_path() {
465+
println!("Profile '{}' saved successfully to:", name);
466+
println!(" {}", config_path.display());
467+
} else {
468+
println!("Profile '{}' saved successfully.", name);
469+
}
454470
}
455471

456472
// Suggest setting as default if it's the only profile of its type
@@ -527,8 +543,14 @@ async fn handle_remove(conn_mgr: &ConnectionManager, name: &str) -> Result<(), R
527543
println!("Default cloud profile cleared.");
528544
}
529545

530-
// Save the configuration
531-
config.save().context("Failed to save configuration")?;
546+
// Save the configuration to the appropriate location
547+
if let Some(ref path) = conn_mgr.config_path {
548+
config
549+
.save_to_path(path)
550+
.context("Failed to save configuration")?;
551+
} else {
552+
config.save().context("Failed to save configuration")?;
553+
}
532554

533555
println!("Profile '{}' removed successfully.", name);
534556
Ok(())
@@ -558,8 +580,14 @@ async fn handle_default_enterprise(
558580
let mut config = conn_mgr.config.clone();
559581
config.default_enterprise = Some(name.to_string());
560582

561-
// Save the configuration
562-
config.save().context("Failed to save configuration")?;
583+
// Save the configuration to the appropriate location
584+
if let Some(ref path) = conn_mgr.config_path {
585+
config
586+
.save_to_path(path)
587+
.context("Failed to save configuration")?;
588+
} else {
589+
config.save().context("Failed to save configuration")?;
590+
}
563591

564592
println!("Default enterprise profile set to '{}'.", name);
565593
Ok(())
@@ -589,8 +617,14 @@ async fn handle_default_cloud(
589617
let mut config = conn_mgr.config.clone();
590618
config.default_cloud = Some(name.to_string());
591619

592-
// Save the configuration
593-
config.save().context("Failed to save configuration")?;
620+
// Save the configuration to the appropriate location
621+
if let Some(ref path) = conn_mgr.config_path {
622+
config
623+
.save_to_path(path)
624+
.context("Failed to save configuration")?;
625+
} else {
626+
config.save().context("Failed to save configuration")?;
627+
}
594628

595629
println!("Default cloud profile set to '{}'.", name);
596630
Ok(())

crates/redisctl/src/connection.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,39 @@ use tracing::{debug, info, trace};
1010
#[derive(Clone)]
1111
pub struct ConnectionManager {
1212
pub config: Config,
13+
pub config_path: Option<std::path::PathBuf>,
1314
}
1415

1516
impl ConnectionManager {
1617
/// Create a new connection manager with the given configuration
1718
#[allow(dead_code)] // Used by binary target
1819
pub fn new(config: Config) -> Self {
19-
Self { config }
20+
Self {
21+
config,
22+
config_path: None,
23+
}
24+
}
25+
26+
/// Create a new connection manager with a custom config path
27+
#[allow(dead_code)] // Used by binary target
28+
pub fn with_config_path(config: Config, config_path: Option<std::path::PathBuf>) -> Self {
29+
Self {
30+
config,
31+
config_path,
32+
}
33+
}
34+
35+
/// Save the configuration to the appropriate location
36+
#[allow(dead_code)] // Used by binary target
37+
pub fn save_config(&self) -> CliResult<()> {
38+
if let Some(ref path) = self.config_path {
39+
self.config
40+
.save_to_path(path)
41+
.context("Failed to save configuration")?;
42+
} else {
43+
self.config.save().context("Failed to save configuration")?;
44+
}
45+
Ok(())
2046
}
2147

2248
/// Create a Cloud client from profile credentials with environment variable override support

crates/redisctl/src/main.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,15 @@ async fn main() -> Result<()> {
2323
// Initialize tracing based on verbosity level
2424
init_tracing(cli.verbose);
2525

26-
// Load configuration
27-
let config = Config::load()?;
28-
let conn_mgr = ConnectionManager::new(config);
26+
// Load configuration from specified path or default location
27+
let (config, config_path) = if let Some(config_file) = &cli.config_file {
28+
let path = std::path::PathBuf::from(config_file);
29+
let config = Config::load_from_path(&path)?;
30+
(config, Some(path))
31+
} else {
32+
(Config::load()?, None)
33+
};
34+
let conn_mgr = ConnectionManager::with_config_path(config, config_path);
2935

3036
// Execute command
3137
if let Err(e) = execute_command(&cli, &conn_mgr).await {

0 commit comments

Comments
 (0)