Skip to content

Commit 27ceb0f

Browse files
committed
keylimectl: Make configuration file optional
When the configuration file is not found, use the default values. Signed-off-by: Anderson Toshiyuki Sasaki <[email protected]>
1 parent 21575e1 commit 27ceb0f

File tree

1 file changed

+63
-19
lines changed

1 file changed

+63
-19
lines changed

keylimectl/src/config.rs

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
//!
1414
//! # Configuration Sources
1515
//!
16-
//! ## Configuration Files
17-
//! The configuration system searches for TOML files in the following order:
18-
//! - Explicit path provided via CLI argument
16+
//! ## Configuration Files (Optional)
17+
//! Configuration files are completely optional. The system searches for TOML files in the following order:
18+
//! - Explicit path provided via CLI argument (required to exist if specified)
1919
//! - `keylimectl.toml` (current directory)
2020
//! - `keylimectl.conf` (current directory)
2121
//! - `/etc/keylime/tenant.conf` (system-wide)
@@ -24,6 +24,8 @@
2424
//! - `~/.keylimectl.toml` (user-specific)
2525
//! - `$XDG_CONFIG_HOME/keylime/tenant.conf` (XDG standard)
2626
//!
27+
//! If no configuration files are found, keylimectl will work perfectly with defaults and environment variables.
28+
//!
2729
//! ## Environment Variables
2830
//! Environment variables use the prefix `KEYLIME_` with double underscores as separators:
2931
//! - `KEYLIME_VERIFIER__IP=192.168.1.100`
@@ -208,6 +210,7 @@ pub struct TlsConfig {
208210
/// Client key password
209211
pub client_key_password: Option<String>,
210212
/// Trusted CA certificates
213+
#[serde(default)]
211214
pub trusted_ca: Vec<String>,
212215
/// Verify server certificates
213216
pub verify_server_cert: bool,
@@ -273,51 +276,69 @@ impl Config {
273276
///
274277
/// Loads configuration with the following precedence (highest to lowest):
275278
/// 1. Environment variables (KEYLIME_*)
276-
/// 2. Configuration files (TOML format)
279+
/// 2. Configuration files (TOML format) - **OPTIONAL**
277280
/// 3. Default values
278281
///
282+
/// Configuration files are completely optional. If no configuration files are found,
283+
/// the system will use default values combined with any environment variables.
284+
/// This allows keylimectl to work out-of-the-box without requiring any configuration.
285+
///
279286
/// # Arguments
280287
///
281288
/// * `config_path` - Optional explicit path to configuration file.
282-
/// If None, searches standard locations.
289+
/// If None, searches standard locations. If Some() but file doesn't exist, returns error.
283290
///
284291
/// # Returns
285292
///
286-
/// Returns the merged configuration or a ConfigError if loading fails.
293+
/// Returns the merged configuration. Will not fail if no config files are found when
294+
/// using automatic discovery (config_path = None).
287295
///
288296
/// # Examples
289297
///
290298
/// ```rust
291299
/// use keylimectl::config::Config;
292300
///
293-
/// // Load from standard locations
301+
/// // Works with no config files - uses defaults + env vars
294302
/// let config = Config::load(None)?;
295303
///
296-
/// // Load from specific file
304+
/// // Load from specific file (errors if file doesn't exist)
297305
/// let config = Config::load(Some("/path/to/config.toml"))?;
298306
/// # Ok::<(), config::ConfigError>(())
299307
/// ```
300308
///
301309
/// # Errors
302310
///
303311
/// Returns ConfigError if:
312+
/// - Explicit configuration file path provided but file doesn't exist
304313
/// - Configuration file has invalid syntax
305314
/// - Environment variables have invalid values
306-
/// - Required configuration is missing
307315
pub fn load(config_path: Option<&str>) -> Result<Self, ConfigError> {
308316
let mut builder = config::Config::builder()
309317
.add_source(config::Config::try_from(&Config::default())?);
310318

311319
// Add configuration file sources
312320
let config_paths = Self::get_config_paths(config_path);
321+
let mut config_file_found = false;
322+
313323
for path in config_paths {
314324
if path.exists() {
325+
config_file_found = true;
326+
log::debug!("Loading config from: {}", path.display());
315327
builder = builder.add_source(
316328
File::from(path).format(FileFormat::Toml).required(false),
317329
);
318330
}
319331
}
320332

333+
// If an explicit config path was provided but the file doesn't exist, that's an error
334+
if let Some(explicit_path) = config_path {
335+
if !PathBuf::from(explicit_path).exists() {
336+
return Err(ConfigError::Message(format!(
337+
"Specified configuration file not found: {explicit_path}"
338+
)));
339+
}
340+
}
341+
321342
// Add environment variables
322343
builder = builder.add_source(
323344
Environment::with_prefix("KEYLIME")
@@ -326,7 +347,18 @@ impl Config {
326347
.try_parsing(true),
327348
);
328349

329-
builder.build()?.try_deserialize()
350+
let config = builder.build()?.try_deserialize()?;
351+
352+
// Log information about configuration sources used
353+
if config_file_found {
354+
log::debug!(
355+
"Configuration loaded successfully with config files"
356+
);
357+
} else {
358+
log::info!("No configuration files found, using defaults and environment variables");
359+
}
360+
361+
Ok(config)
330362
}
331363

332364
/// Apply command-line argument overrides
@@ -956,25 +988,37 @@ retry_interval = 2.0
956988
#[test]
957989
fn test_load_config_no_files() {
958990
// Test loading config when no config files exist
959-
// This should succeed with defaults
991+
// This should always succeed with defaults since config files are optional
960992
let result = Config::load(None);
961993

962-
// This may fail due to config crate serialization issues with empty Vec
963-
// If it fails, that's actually expected behavior - let's test that
994+
// Should always succeed now that config files are optional
964995
match result {
965996
Ok(config) => {
966997
assert_eq!(config.verifier.ip, "127.0.0.1"); // Default value
998+
assert_eq!(config.verifier.port, 8881); // Default value
999+
assert_eq!(config.registrar.ip, "127.0.0.1"); // Default value
1000+
assert_eq!(config.registrar.port, 8891); // Default value
9671001
}
968-
Err(_) => {
969-
// This is acceptable - the config crate may have issues with
970-
// serializing/deserializing empty Vecs or other edge cases
971-
// The important thing is that Config::default() works
972-
let default_config = Config::default();
973-
assert_eq!(default_config.verifier.ip, "127.0.0.1");
1002+
Err(e) => {
1003+
panic!("Config loading should succeed with no files, but got error: {e:?}");
9741004
}
9751005
}
9761006
}
9771007

1008+
#[test]
1009+
fn test_load_config_explicit_file_not_found() {
1010+
// Test that explicit config file paths are still required to exist
1011+
let result = Config::load(Some("/nonexistent/path/config.toml"));
1012+
1013+
assert!(
1014+
result.is_err(),
1015+
"Should error when explicit config file doesn't exist"
1016+
);
1017+
let error_msg = result.unwrap_err().to_string();
1018+
assert!(error_msg.contains("Specified configuration file not found"));
1019+
assert!(error_msg.contains("/nonexistent/path/config.toml"));
1020+
}
1021+
9781022
#[test]
9791023
fn test_get_config_paths_explicit() {
9801024
let paths = Config::get_config_paths(Some("/custom/path.toml"));

0 commit comments

Comments
 (0)