You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iggy.apache.org by ma...@apache.org on 2025/11/24 12:49:17 UTC
(iggy) 01/01: refactor(config): use common config provider in server crate
This is an automated email from the ASF dual-hosted git repository.
maciej pushed a commit to branch unify-config
in repository https://gitbox.apache.org/repos/asf/iggy.git
commit eaf60d5079ffbf7b37d01e8b77a566281a317161
Author: Maciej Modzelewski <ma...@modzelewski.dev>
AuthorDate: Mon Nov 24 12:17:11 2025 +0100
refactor(config): use common config provider in server crate
---
core/common/src/configs/mod.rs | 50 ++-
core/integration/src/test_server.rs | 12 +-
core/integration/tests/config_provider/mod.rs | 34 +-
core/server/src/bootstrap.rs | 41 ++-
core/server/src/configs/config_provider.rs | 464 --------------------------
core/server/src/configs/mod.rs | 4 +-
core/server/src/configs/server.rs | 73 +++-
core/server/src/configs/validators.rs | 120 +++----
core/server/src/main.rs | 7 +-
core/server/src/server_error.rs | 19 +-
10 files changed, 218 insertions(+), 606 deletions(-)
diff --git a/core/common/src/configs/mod.rs b/core/common/src/configs/mod.rs
index fa3289519..88fd498f2 100644
--- a/core/common/src/configs/mod.rs
+++ b/core/common/src/configs/mod.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
@@ -51,6 +52,7 @@ pub enum ConfigurationError {
DefaultSerializationFailed,
DefaultParsingFailed,
EnvironmentVariableParsingFailed,
+ InvalidConfigurationValue,
}
impl Display for ConfigurationError {
@@ -64,6 +66,9 @@ impl Display for ConfigurationError {
Self::EnvironmentVariableParsingFailed => {
write!(f, "Failed to parse environment variables")
}
+ Self::InvalidConfigurationValue => {
+ write!(f, "Provided configuration value is invalid")
+ }
}
}
}
@@ -71,7 +76,7 @@ impl Display for ConfigurationError {
impl std::error::Error for ConfigurationError {}
pub trait ConfigProvider<T: ConfigurationType> {
- fn load_config(self) -> impl Future<Output = Result<T, ConfigurationError>>;
+ fn load_config(&self) -> impl Future<Output = Result<T, ConfigurationError>>;
}
/// File-based configuration provider that combines file, default, and environment configurations
@@ -263,12 +268,29 @@ impl<T: ConfigurationType> CustomEnvProvider<T> {
if remaining_path.is_empty() {
arr[array_index] = value;
} else if let FigmentValue::Dict(_, elem_dict) = &mut arr[array_index] {
- Self::insert_environment_override(
- &Dict::new(),
- elem_dict,
- remaining_path.to_vec(),
- value,
- );
+ // TODO(hubcio): this is workaround done by Claude because this code is overly
+ // complicated and I don't want to spend time on it.
+ // For nested structures in arrays, check if we need to create intermediate dicts
+ // Handle the "ports" case where it should be a nested structure
+ if remaining_path.len() >= 2 && remaining_path[0] == "ports" {
+ // Create the ports dict if it doesn't exist
+ elem_dict
+ .entry("ports".to_string())
+ .or_insert_with(|| FigmentValue::Dict(Tag::Default, Dict::new()));
+
+ if let Some(FigmentValue::Dict(_, ports_dict)) = elem_dict.get_mut("ports") {
+ // Insert the specific port value (tcp, quic, http, websocket)
+ ports_dict.insert(remaining_path[1].clone(), value);
+ }
+ } else {
+ // Default behavior for other fields
+ Self::insert_environment_override(
+ &Dict::new(),
+ elem_dict,
+ remaining_path.to_vec(),
+ value,
+ );
+ }
}
}
}
@@ -682,7 +704,11 @@ impl<T: ConfigurationType> CustomEnvProvider<T> {
_ => {}
}
- // Try parsing as integer
+ // Try u64 first for most numeric values (ports, byte sizes, etc.)
+ if let Ok(uint_val) = value.parse::<u64>() {
+ return FigmentValue::from(uint_val);
+ }
+ // Fall back to i64 for signed integers
if let Ok(int_val) = value.parse::<i64>() {
return FigmentValue::from(int_val);
}
@@ -725,13 +751,13 @@ fn file_exists<P: AsRef<Path>>(path: P) -> bool {
}
impl<T: ConfigurationType, P: Provider + Clone> ConfigProvider<T> for FileConfigProvider<P> {
- async fn load_config(self) -> Result<T, ConfigurationError> {
+ async fn load_config(&self) -> Result<T, ConfigurationError> {
println!("Loading config from path: '{}'...", self.file_path);
// Start with the default configuration if provided
let mut config_builder = Figment::new();
let has_default = self.default_config.is_some();
- if let Some(default) = self.default_config {
+ if let Some(default) = &self.default_config {
config_builder = config_builder.merge(default);
} else {
println!("No default configuration provided.");
@@ -752,7 +778,7 @@ impl<T: ConfigurationType, P: Provider + Clone> ConfigProvider<T> for FileConfig
}
// Merge environment variables into the configuration
- config_builder = config_builder.merge(self.env_provider);
+ config_builder = config_builder.merge(self.env_provider.clone());
// Finally, attempt to extract the final configuration
let config_result: Result<T, figment::Error> = config_builder.extract();
diff --git a/core/integration/src/test_server.rs b/core/integration/src/test_server.rs
index 160094d26..a13f44e2e 100644
--- a/core/integration/src/test_server.rs
+++ b/core/integration/src/test_server.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
@@ -22,9 +23,9 @@ use derive_more::Display;
use futures::executor::block_on;
use iggy::prelude::UserStatus::Active;
use iggy::prelude::*;
-use iggy_common::TransportProtocol;
+use iggy_common::{ConfigProvider, TransportProtocol};
use rand::Rng;
-use server::configs::config_provider::{ConfigProvider, FileConfigProvider};
+use server::configs::server::ServerConfig;
use std::collections::HashMap;
use std::fs;
use std::fs::{File, OpenOptions};
@@ -338,7 +339,7 @@ impl TestServer {
fn wait_until_server_has_bound(&mut self) {
let config_path = format!("{}/runtime/current_config.toml", self.local_data_path);
- let file_config_provider = FileConfigProvider::new(config_path.clone());
+ let file_config_provider = ServerConfig::config_provider(&config_path);
let max_attempts = (MAX_PORT_WAIT_DURATION_S * 1000) / SLEEP_INTERVAL_MS;
self.server_addrs.clear();
@@ -356,7 +357,8 @@ impl TestServer {
sleep(Duration::from_millis(SLEEP_INTERVAL_MS));
continue;
}
- match file_config_provider.load_config().await {
+ let config: Result<ServerConfig, _> = file_config_provider.load_config().await;
+ match config {
Ok(config) => {
// Verify config contains fresh addresses, not stale defaults
// Default ports: TCP=8090, HTTP=3000, QUIC=8080, WebSocket=8092
diff --git a/core/integration/tests/config_provider/mod.rs b/core/integration/tests/config_provider/mod.rs
index 882431747..82e039732 100644
--- a/core/integration/tests/config_provider/mod.rs
+++ b/core/integration/tests/config_provider/mod.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
@@ -16,12 +17,12 @@
* under the License.
*/
+use iggy_common::ConfigProvider;
+use integration::file::get_root_path;
use serial_test::serial;
-use server::configs::config_provider::{ConfigProvider, FileConfigProvider};
+use server::configs::server::ServerConfig;
use std::env;
-use integration::file::get_root_path;
-
#[serial]
#[tokio::test]
async fn validate_config_env_override() {
@@ -44,8 +45,9 @@ async fn validate_config_env_override() {
}
let config_path = get_root_path().join("../configs/server.toml");
- let file_config_provider = FileConfigProvider::new(config_path.as_path().display().to_string());
- let config = file_config_provider
+ let file_config_provider =
+ ServerConfig::config_provider(&config_path.as_path().display().to_string());
+ let config: ServerConfig = file_config_provider
.load_config()
.await
.expect("Failed to load server.toml config");
@@ -85,8 +87,9 @@ async fn validate_socket_override() {
}
let config_path = get_root_path().join("../configs/server.toml");
- let file_config_provider = FileConfigProvider::new(config_path.as_path().display().to_string());
- let config = file_config_provider
+ let file_config_provider =
+ ServerConfig::config_provider(&config_path.as_path().display().to_string());
+ let config: ServerConfig = file_config_provider
.load_config()
.await
.expect("Failed to load server.toml config with socket override");
@@ -113,8 +116,9 @@ async fn validate_socket_override() {
#[tokio::test]
async fn validate_socket_no_override() {
let config_path = get_root_path().join("../configs/server.toml");
- let file_config_provider = FileConfigProvider::new(config_path.as_path().display().to_string());
- let config = file_config_provider
+ let file_config_provider =
+ ServerConfig::config_provider(&config_path.as_path().display().to_string());
+ let config: ServerConfig = file_config_provider
.load_config()
.await
.expect("Failed to load server.toml config without socket override");
@@ -198,8 +202,9 @@ async fn validate_cluster_config_env_override() {
}
let config_path = get_root_path().join("../configs/server.toml");
- let file_config_provider = FileConfigProvider::new(config_path.as_path().display().to_string());
- let config = file_config_provider
+ let file_config_provider =
+ ServerConfig::config_provider(&config_path.as_path().display().to_string());
+ let config: ServerConfig = file_config_provider
.load_config()
.await
.expect("Failed to load server.toml config with cluster env overrides");
@@ -388,8 +393,9 @@ async fn validate_four_node_cluster_config_env_override() {
}
let config_path = get_root_path().join("../configs/server.toml");
- let file_config_provider = FileConfigProvider::new(config_path.as_path().display().to_string());
- let config = file_config_provider
+ let file_config_provider =
+ ServerConfig::config_provider(&config_path.as_path().display().to_string());
+ let config: ServerConfig = file_config_provider
.load_config()
.await
.expect("Failed to load server.toml config with 4-node cluster env overrides");
diff --git a/core/server/src/bootstrap.rs b/core/server/src/bootstrap.rs
index 5f7f771ac..3f95da049 100644
--- a/core/server/src/bootstrap.rs
+++ b/core/server/src/bootstrap.rs
@@ -1,26 +1,27 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
use crate::{
IGGY_ROOT_PASSWORD_ENV, IGGY_ROOT_USERNAME_ENV,
compat::index_rebuilding::index_rebuilder::IndexRebuilder,
configs::{
cache_indexes::CacheIndexesConfig,
- config_provider::ConfigProviderKind,
server::ServerConfig,
system::{INDEX_EXTENSION, LOG_EXTENSION, SystemConfig},
},
@@ -266,10 +267,8 @@ pub fn create_shard_connections(
(connectors, shutdown_handles)
}
-pub async fn load_config(
- config_provider: &ConfigProviderKind,
-) -> Result<ServerConfig, ServerError> {
- let config = ServerConfig::load(config_provider).await?;
+pub async fn load_config() -> Result<ServerConfig, ServerError> {
+ let config = ServerConfig::load().await?;
Ok(config)
}
diff --git a/core/server/src/configs/config_provider.rs b/core/server/src/configs/config_provider.rs
deleted file mode 100644
index 3461d44ef..000000000
--- a/core/server/src/configs/config_provider.rs
+++ /dev/null
@@ -1,464 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-use crate::IGGY_ROOT_PASSWORD_ENV;
-use crate::configs::server::ServerConfig;
-use crate::server_error::ConfigError;
-use figment::{
- Error, Figment, Metadata, Profile, Provider,
- providers::{Format, Toml},
- value::{Dict, Map as FigmentMap, Tag, Value as FigmentValue},
-};
-use std::{env, future::Future, path::Path};
-use toml::{Value as TomlValue, map::Map};
-
-const DEFAULT_CONFIG_PROVIDER: &str = "file";
-const DEFAULT_CONFIG_PATH: &str = "configs/server.toml";
-const SECRET_KEYS: [&str; 6] = [
- IGGY_ROOT_PASSWORD_ENV,
- "IGGY_DATA_MAINTENANCE_ARCHIVER_S3_KEY_SECRET",
- "IGGY_HTTP_JWT_ENCODING_SECRET",
- "IGGY_HTTP_JWT_DECODING_SECRET",
- "IGGY_TCP_TLS_PASSWORD",
- "IGGY_SYSTEM_ENCRYPTION_KEY",
-];
-
-pub enum ConfigProviderKind {
- File(FileConfigProvider),
-}
-
-impl ConfigProviderKind {
- pub async fn load_config(&self) -> Result<ServerConfig, ConfigError> {
- match self {
- Self::File(p) => p.load_config().await,
- }
- }
-}
-
-pub trait ConfigProvider {
- fn load_config(&self) -> impl Future<Output = Result<ServerConfig, ConfigError>>;
-}
-
-#[derive(Debug)]
-pub struct FileConfigProvider {
- path: String,
-}
-
-pub struct CustomEnvProvider {
- prefix: String,
-}
-
-impl FileConfigProvider {
- pub fn new(path: String) -> Self {
- Self { path }
- }
-}
-
-impl CustomEnvProvider {
- pub fn new(prefix: &str) -> Self {
- Self {
- prefix: prefix.to_string(),
- }
- }
-
- fn walk_toml_table_to_dict(prefix: &str, table: Map<String, TomlValue>, dict: &mut Dict) {
- for (key, value) in table {
- let new_prefix = if prefix.is_empty() {
- key.clone()
- } else {
- format!("{prefix}.{key}")
- };
- match value {
- TomlValue::Table(inner_table) => {
- let mut nested_dict = Dict::new();
- Self::walk_toml_table_to_dict(&new_prefix, inner_table, &mut nested_dict);
- dict.insert(key, FigmentValue::from(nested_dict));
- }
- _ => {
- dict.insert(key, Self::toml_to_figment_value(&value));
- }
- }
- }
- }
-
- fn insert_overridden_values_from_env(
- source: &Dict,
- target: &mut Dict,
- keys: Vec<String>,
- value: FigmentValue,
- ) {
- if keys.is_empty() {
- return;
- }
-
- // Detect array patterns (e.g., cluster.nodes.0.id)
- if let Some((idx_pos, array_index)) = Self::find_array_index(&keys) {
- Self::handle_array_override(source, target, &keys, idx_pos, array_index, value);
- } else {
- Self::handle_dict_override(source, target, &keys, value);
- }
- }
-
- fn find_array_index(keys: &[String]) -> Option<(usize, usize)> {
- keys.iter()
- .enumerate()
- .find_map(|(i, key)| key.parse::<usize>().ok().map(|idx| (i, idx)))
- }
-
- fn handle_array_override(
- source: &Dict,
- target: &mut Dict,
- keys: &[String],
- idx_pos: usize,
- array_index: usize,
- value: FigmentValue,
- ) {
- let path_to_array = &keys[..idx_pos];
- let remaining_path = &keys[idx_pos + 1..];
-
- // Navigate to array container
- let current_target = match Self::navigate_to_dict(
- target,
- &path_to_array[..path_to_array.len().saturating_sub(1)],
- ) {
- Some(dict) => dict,
- None => return,
- };
-
- let array_key = match path_to_array.last() {
- Some(key) => key.clone(),
- None => return,
- };
-
- // Copy existing array from source if needed
- if !current_target.contains_key(&array_key) {
- if let Some(existing_array) = Self::find_source_array(source, path_to_array, &array_key)
- {
- current_target.insert(array_key.clone(), existing_array);
- } else {
- current_target.insert(
- array_key.clone(),
- FigmentValue::Array(Tag::Default, Vec::new()),
- );
- }
- }
-
- // Update array element
- if let Some(FigmentValue::Array(_, arr)) = current_target.get_mut(&array_key) {
- // Ensure array is large enough
- while arr.len() <= array_index {
- arr.push(FigmentValue::Dict(Tag::Default, Dict::new()));
- }
-
- if remaining_path.is_empty() {
- arr[array_index] = value;
- } else if let FigmentValue::Dict(_, elem_dict) = &mut arr[array_index] {
- // TODO(hubcio): this is workaround done by Claude because this code is overly
- // complicated and I don't want to spend time on it.
- // For nested structures in arrays, check if we need to create intermediate dicts
- // Handle the "ports" case where it should be a nested structure
- if remaining_path.len() >= 2 && remaining_path[0] == "ports" {
- // Create the ports dict if it doesn't exist
- elem_dict
- .entry("ports".to_string())
- .or_insert_with(|| FigmentValue::Dict(Tag::Default, Dict::new()));
-
- if let Some(FigmentValue::Dict(_, ports_dict)) = elem_dict.get_mut("ports") {
- // Insert the specific port value (tcp, quic, http, websocket)
- ports_dict.insert(remaining_path[1].clone(), value);
- }
- } else {
- // Default behavior for other fields
- Self::insert_overridden_values_from_env(
- &Dict::new(),
- elem_dict,
- remaining_path.to_vec(),
- value,
- );
- }
- }
- }
- }
-
- fn handle_dict_override(
- source: &Dict,
- target: &mut Dict,
- keys: &[String],
- value: FigmentValue,
- ) {
- let mut current_source = source;
- let mut current_target = target;
- let mut combined_keys = Vec::new();
-
- for (i, key) in keys.iter().enumerate() {
- combined_keys.push(key.clone());
- let key_to_check = combined_keys.join("_");
-
- match current_source.get(&key_to_check) {
- Some(FigmentValue::Dict(_, inner_source_dict)) => {
- current_target
- .entry(key_to_check.clone())
- .or_insert_with(|| FigmentValue::Dict(Tag::Default, Dict::new()));
-
- if let Some(FigmentValue::Dict(_, inner_target)) =
- current_target.get_mut(&key_to_check)
- {
- current_source = inner_source_dict;
- current_target = inner_target;
- combined_keys.clear();
- } else {
- return;
- }
- }
- Some(_) => {
- current_target.insert(key_to_check, value);
- return;
- }
- None if i == keys.len() - 1 => {
- current_target.insert(key_to_check, value);
- return;
- }
- _ => continue,
- }
- }
- }
-
- fn navigate_to_dict<'a>(target: &'a mut Dict, path: &[String]) -> Option<&'a mut Dict> {
- if path.is_empty() {
- return Some(target);
- }
-
- let mut current = target;
- let mut combined_keys = Vec::new();
-
- for key in path {
- combined_keys.push(key.clone());
- let key_to_check = combined_keys.join("_");
-
- current
- .entry(key_to_check.clone())
- .or_insert_with(|| FigmentValue::Dict(Tag::Default, Dict::new()));
-
- match current.get_mut(&key_to_check) {
- Some(FigmentValue::Dict(_, inner)) => {
- current = inner;
- combined_keys.clear();
- }
- _ => return None,
- }
- }
-
- Some(current)
- }
-
- fn find_source_array(source: &Dict, path: &[String], array_key: &str) -> Option<FigmentValue> {
- if path.is_empty() {
- return None;
- }
-
- let mut current = source;
- let mut combined_keys = Vec::new();
-
- for key in &path[..path.len() - 1] {
- combined_keys.push(key.clone());
- let key_to_check = combined_keys.join("_");
-
- match current.get(&key_to_check) {
- Some(FigmentValue::Dict(_, inner)) => {
- current = inner;
- combined_keys.clear();
- }
- _ => return None,
- }
- }
-
- current.get(array_key).cloned()
- }
-
- fn toml_to_figment_value(toml_value: &TomlValue) -> FigmentValue {
- match toml_value {
- TomlValue::String(s) => FigmentValue::from(s.clone()),
- TomlValue::Integer(i) => FigmentValue::from(*i),
- TomlValue::Float(f) => FigmentValue::from(*f),
- TomlValue::Boolean(b) => FigmentValue::from(*b),
- TomlValue::Array(arr) => {
- let vec: Vec<FigmentValue> = arr.iter().map(Self::toml_to_figment_value).collect();
- FigmentValue::from(vec)
- }
- TomlValue::Table(tbl) => {
- let mut dict = figment::value::Dict::new();
- for (key, value) in tbl.iter() {
- dict.insert(key.clone(), Self::toml_to_figment_value(value));
- }
- FigmentValue::from(dict)
- }
- TomlValue::Datetime(_) => todo!("not implemented yet!"),
- }
- }
-
- fn try_parse_value(value: &str) -> FigmentValue {
- if value.starts_with('[') && value.ends_with(']') {
- let value = value.trim_start_matches('[').trim_end_matches(']');
- let values: Vec<FigmentValue> = value.split(',').map(Self::try_parse_value).collect();
- return FigmentValue::from(values);
- }
- if value == "true" {
- return FigmentValue::from(true);
- }
- if value == "false" {
- return FigmentValue::from(false);
- }
- // Try u64 first for most numeric values (ports, byte sizes, etc.)
- if let Ok(uint_val) = value.parse::<u64>() {
- return FigmentValue::from(uint_val);
- }
- // Fall back to i64 for signed integers
- if let Ok(int_val) = value.parse::<i64>() {
- return FigmentValue::from(int_val);
- }
- if let Ok(float_val) = value.parse::<f64>() {
- return FigmentValue::from(float_val);
- }
- FigmentValue::from(value)
- }
-}
-
-impl Provider for CustomEnvProvider {
- fn metadata(&self) -> Metadata {
- Metadata::named("iggy-server config")
- }
-
- fn data(&self) -> Result<FigmentMap<Profile, Dict>, Error> {
- let default_config = toml::to_string(&ServerConfig::default())
- .expect("Cannot serialize default ServerConfig. Something's terribly wrong.");
- let toml_value: TomlValue = toml::from_str(&default_config).unwrap();
- let mut source_dict = Dict::new();
- if let TomlValue::Table(table) = toml_value {
- Self::walk_toml_table_to_dict("", table, &mut source_dict);
- }
-
- let mut new_dict = Dict::new();
- for (key, mut value) in env::vars() {
- let env_key = key.to_uppercase();
- if !env_key.starts_with(self.prefix.as_str()) {
- continue;
- }
- let keys: Vec<String> = env_key[self.prefix.len()..]
- .split('_')
- .map(|k| k.to_lowercase())
- .collect();
- let env_var_value = Self::try_parse_value(&value);
- if SECRET_KEYS.contains(&env_key.as_str()) {
- value = "******".to_string();
- }
-
- println!("{env_key} value changed to: {value} from environment variable");
- Self::insert_overridden_values_from_env(
- &source_dict,
- &mut new_dict,
- keys.clone(),
- env_var_value.clone(),
- );
- }
- let mut data = FigmentMap::new();
- data.insert(Profile::default(), new_dict);
-
- Ok(data)
- }
-}
-
-pub fn resolve(config_provider_type: &str) -> Result<ConfigProviderKind, ConfigError> {
- match config_provider_type {
- DEFAULT_CONFIG_PROVIDER => {
- let path =
- env::var("IGGY_CONFIG_PATH").unwrap_or_else(|_| DEFAULT_CONFIG_PATH.to_string());
- Ok(ConfigProviderKind::File(FileConfigProvider::new(path)))
- }
- _ => Err(ConfigError::InvalidConfigurationProvider {
- provider_type: config_provider_type.to_string(),
- }),
- }
-}
-
-/// This does exactly the same as Figment does internally.
-fn file_exists<P: AsRef<Path>>(path: P) -> bool {
- let path = path.as_ref();
-
- if path.is_absolute() {
- return path.is_file();
- }
-
- let cwd = match std::env::current_dir() {
- Ok(dir) => dir,
- Err(_) => return false,
- };
-
- let mut current_dir = cwd.as_path();
- loop {
- let file_path = current_dir.join(path);
- if file_path.is_file() {
- return true;
- }
-
- current_dir = match current_dir.parent() {
- Some(parent) => parent,
- None => return false,
- };
- }
-}
-
-impl ConfigProvider for FileConfigProvider {
- async fn load_config(&self) -> Result<ServerConfig, ConfigError> {
- println!("Loading config from path: '{}'...", self.path);
-
- // Include the default configuration from server.toml
- let embedded_default_config = Toml::string(include_str!("../../../configs/server.toml"));
-
- // Start with the default configuration
- let mut config_builder = Figment::new().merge(embedded_default_config);
-
- // If the server.toml file exists, merge it into the configuration
- if file_exists(&self.path) {
- println!("Found configuration file at path: '{}'.", self.path);
- config_builder = config_builder.merge(Toml::file(&self.path));
- } else {
- println!(
- "Configuration file not found at path: '{}'. Using default configuration from embedded server.toml.",
- self.path
- );
- }
-
- // Merge environment variables into the configuration
- config_builder = config_builder.merge(CustomEnvProvider::new("IGGY_"));
-
- // Finally, attempt to extract the final configuration
- let config_result: Result<ServerConfig, figment::Error> = config_builder.extract();
-
- match config_result {
- Ok(config) => {
- println!("Config loaded successfully.");
- println!("Using Config: {config}");
- Ok(config)
- }
- Err(e) => {
- println!("Failed to load config: {e}");
- Err(ConfigError::CannotLoadConfiguration)
- }
- }
- }
-}
diff --git a/core/server/src/configs/mod.rs b/core/server/src/configs/mod.rs
index ff8003afa..8bee32c44 100644
--- a/core/server/src/configs/mod.rs
+++ b/core/server/src/configs/mod.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
@@ -18,7 +19,6 @@
pub mod cache_indexes;
pub mod cluster;
-pub mod config_provider;
pub mod defaults;
pub mod displays;
pub mod http;
diff --git a/core/server/src/configs/server.rs b/core/server/src/configs/server.rs
index 446fe78d3..927386aef 100644
--- a/core/server/src/configs/server.rs
+++ b/core/server/src/configs/server.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
@@ -15,26 +16,39 @@
* specific language governing permissions and limitations
* under the License.
*/
-
+use crate::IGGY_ROOT_PASSWORD_ENV;
use crate::configs::COMPONENT;
use crate::configs::cluster::ClusterConfig;
-use crate::configs::config_provider::ConfigProviderKind;
use crate::configs::http::HttpConfig;
use crate::configs::quic::QuicConfig;
use crate::configs::system::SystemConfig;
use crate::configs::tcp::TcpConfig;
use crate::configs::websocket::WebSocketConfig;
-use crate::server_error::ConfigError;
+use crate::server_error::ConfigurationError;
use derive_more::Display;
use err_trail::ErrContext;
-use iggy_common::IggyDuration;
-use iggy_common::Validatable;
+use figment::providers::{Format, Toml};
+use figment::value::Dict;
+use figment::{Metadata, Profile, Provider};
+use iggy_common::{ConfigProvider, Validatable};
+use iggy_common::{CustomEnvProvider, FileConfigProvider, IggyDuration};
use serde::{Deserialize, Serialize};
use serde_with::DisplayFromStr;
use serde_with::serde_as;
+use std::env;
use std::str::FromStr;
use std::sync::Arc;
+const DEFAULT_CONFIG_PATH: &str = "configs/server.toml";
+const SECRET_KEYS: [&str; 6] = [
+ IGGY_ROOT_PASSWORD_ENV,
+ "IGGY_DATA_MAINTENANCE_ARCHIVER_S3_KEY_SECRET",
+ "IGGY_HTTP_JWT_ENCODING_SECRET",
+ "IGGY_HTTP_JWT_DECODING_SECRET",
+ "IGGY_TCP_TLS_PASSWORD",
+ "IGGY_SYSTEM_ENCRYPTION_KEY",
+];
+
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ServerConfig {
pub data_maintenance: DataMaintenanceConfig,
@@ -136,13 +150,52 @@ impl FromStr for TelemetryTransport {
}
impl ServerConfig {
- pub async fn load(config_provider: &ConfigProviderKind) -> Result<ServerConfig, ConfigError> {
- let server_config = config_provider.load_config().await.with_error(|error| {
- format!("{COMPONENT} (error: {error}) - failed to load config provider config")
- })?;
+ pub async fn load() -> Result<ServerConfig, ConfigurationError> {
+ let config_path =
+ env::var("IGGY_CONFIG_PATH").unwrap_or_else(|_| DEFAULT_CONFIG_PATH.to_string());
+ let config_provider = ServerConfig::config_provider(&config_path);
+ let server_config: ServerConfig =
+ config_provider.load_config().await.with_error(|error| {
+ format!("{COMPONENT} (error: {error}) - failed to load config provider config")
+ })?;
server_config.validate().with_error(|error| {
format!("{COMPONENT} (error: {error}) - failed to validate server config")
})?;
Ok(server_config)
}
+
+ pub fn config_provider(config_path: &str) -> FileConfigProvider<ServerConfigEnvProvider> {
+ let default_config = Toml::string(include_str!("../../../configs/server.toml"));
+ FileConfigProvider::new(
+ config_path.to_string(),
+ ServerConfigEnvProvider::default(),
+ true,
+ Some(default_config),
+ )
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct ServerConfigEnvProvider {
+ provider: CustomEnvProvider<ServerConfig>,
+}
+
+impl Default for ServerConfigEnvProvider {
+ fn default() -> Self {
+ Self {
+ provider: CustomEnvProvider::new("IGGY_", &SECRET_KEYS),
+ }
+ }
+}
+
+impl Provider for ServerConfigEnvProvider {
+ fn metadata(&self) -> Metadata {
+ Metadata::named("iggy-server config")
+ }
+
+ fn data(&self) -> Result<figment::value::Map<Profile, Dict>, figment::Error> {
+ self.provider.deserialize().map_err(|_| {
+ figment::Error::from("Cannot deserialize environment variables for server config")
+ })
+ }
}
diff --git a/core/server/src/configs/validators.rs b/core/server/src/configs/validators.rs
index 83aff5858..460eb71d8 100644
--- a/core/server/src/configs/validators.rs
+++ b/core/server/src/configs/validators.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
@@ -25,18 +26,17 @@ use super::system::{CompressionConfig, MemoryPoolConfig, PartitionConfig};
use crate::configs::COMPONENT;
use crate::configs::server::{PersonalAccessTokenConfig, ServerConfig};
use crate::configs::system::SegmentConfig;
-use crate::server_error::ConfigError;
use crate::streaming::segments::*;
use err_trail::ErrContext;
-use iggy_common::CompressionAlgorithm;
use iggy_common::IggyExpiry;
use iggy_common::MaxTopicSize;
use iggy_common::Validatable;
+use iggy_common::{CompressionAlgorithm, ConfigurationError};
use std::thread::available_parallelism;
use tracing::error;
-impl Validatable<ConfigError> for ServerConfig {
- fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for ServerConfig {
+ fn validate(&self) -> Result<(), ConfigurationError> {
self.system.memory_pool.validate().with_error(|error| {
format!("{COMPONENT} (error: {error}) - failed to validate memory pool config")
})?;
@@ -67,29 +67,29 @@ impl Validatable<ConfigError> for ServerConfig {
let topic_size = match self.system.topic.max_size {
MaxTopicSize::Custom(size) => Ok(size.as_bytes_u64()),
MaxTopicSize::Unlimited => Ok(u64::MAX),
- MaxTopicSize::ServerDefault => Err(ConfigError::InvalidConfiguration),
+ MaxTopicSize::ServerDefault => Err(ConfigurationError::InvalidConfigurationValue),
}?;
if let IggyExpiry::ServerDefault = self.system.segment.message_expiry {
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if self.http.enabled
&& let IggyExpiry::ServerDefault = self.http.jwt.access_token_expiry
{
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if topic_size < self.system.segment.size.as_bytes_u64() {
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
Ok(())
}
}
-impl Validatable<ConfigError> for CompressionConfig {
- fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for CompressionConfig {
+ fn validate(&self) -> Result<(), ConfigurationError> {
let compression_alg = &self.default_algorithm;
if *compression_alg != CompressionAlgorithm::None {
// TODO(numinex): Change this message once server side compression is fully developed.
@@ -102,36 +102,36 @@ impl Validatable<ConfigError> for CompressionConfig {
}
}
-impl Validatable<ConfigError> for TelemetryConfig {
- fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for TelemetryConfig {
+ fn validate(&self) -> Result<(), ConfigurationError> {
if !self.enabled {
return Ok(());
}
if self.service_name.trim().is_empty() {
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if self.logs.endpoint.is_empty() {
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if self.traces.endpoint.is_empty() {
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
Ok(())
}
}
-impl Validatable<ConfigError> for PartitionConfig {
- fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for PartitionConfig {
+ fn validate(&self) -> Result<(), ConfigurationError> {
if self.messages_required_to_save < 32 {
eprintln!(
"Configured system.partition.messages_required_to_save {} is less than minimum {}",
self.messages_required_to_save, 32
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if !self.messages_required_to_save.is_multiple_of(32) {
@@ -139,7 +139,7 @@ impl Validatable<ConfigError> for PartitionConfig {
"Configured system.partition.messages_required_to_save {} is not a multiple of 32",
self.messages_required_to_save
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if self.size_of_messages_required_to_save < 512 {
@@ -147,7 +147,7 @@ impl Validatable<ConfigError> for PartitionConfig {
"Configured system.partition.size_of_messages_required_to_save {} is less than minimum {}",
self.size_of_messages_required_to_save, 512
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if !self
@@ -159,22 +159,22 @@ impl Validatable<ConfigError> for PartitionConfig {
"Configured system.partition.size_of_messages_required_to_save {} is not a multiple of 512 B",
self.size_of_messages_required_to_save
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
Ok(())
}
}
-impl Validatable<ConfigError> for SegmentConfig {
- fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for SegmentConfig {
+ fn validate(&self) -> Result<(), ConfigurationError> {
if self.size > SEGMENT_MAX_SIZE_BYTES {
eprintln!(
"Configured system.segment.size {} B is greater than maximum {} B",
self.size.as_bytes_u64(),
SEGMENT_MAX_SIZE_BYTES
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if !self.size.as_bytes_u64().is_multiple_of(512) {
@@ -182,25 +182,25 @@ impl Validatable<ConfigError> for SegmentConfig {
"Configured system.segment.size {} B is not a multiple of 512 B",
self.size.as_bytes_u64()
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
Ok(())
}
}
-impl Validatable<ConfigError> for MessageSaverConfig {
- fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for MessageSaverConfig {
+ fn validate(&self) -> Result<(), ConfigurationError> {
if self.enabled && self.interval.is_zero() {
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
Ok(())
}
}
-impl Validatable<ConfigError> for DataMaintenanceConfig {
- fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for DataMaintenanceConfig {
+ fn validate(&self) -> Result<(), ConfigurationError> {
self.messages.validate().with_error(|error| {
format!("{COMPONENT} (error: {error}) - failed to validate messages maintenance config")
})?;
@@ -208,37 +208,37 @@ impl Validatable<ConfigError> for DataMaintenanceConfig {
}
}
-impl Validatable<ConfigError> for MessagesMaintenanceConfig {
- fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for MessagesMaintenanceConfig {
+ fn validate(&self) -> Result<(), ConfigurationError> {
if self.cleaner_enabled && self.interval.is_zero() {
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
Ok(())
}
}
-impl Validatable<ConfigError> for PersonalAccessTokenConfig {
- fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for PersonalAccessTokenConfig {
+ fn validate(&self) -> Result<(), ConfigurationError> {
if self.max_tokens_per_user == 0 {
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if self.cleaner.enabled && self.cleaner.interval.is_zero() {
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
Ok(())
}
}
-impl Validatable<ConfigError> for MemoryPoolConfig {
- fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for MemoryPoolConfig {
+ fn validate(&self) -> Result<(), ConfigurationError> {
if self.enabled && self.size == 0 {
error!(
"Configured system.memory_pool.enabled is true and system.memory_pool.size is 0"
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
const MIN_POOL_SIZE: u64 = 512 * 1024 * 1024; // 512 MiB
@@ -253,7 +253,7 @@ impl Validatable<ConfigError> for MemoryPoolConfig {
MIN_POOL_SIZE,
MIN_POOL_SIZE / (1024 * 1024),
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if self.enabled && !self.size.as_bytes_u64().is_multiple_of(DEFAULT_PAGE_SIZE) {
@@ -262,7 +262,7 @@ impl Validatable<ConfigError> for MemoryPoolConfig {
self.size.as_bytes_u64(),
DEFAULT_PAGE_SIZE
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if self.enabled && self.bucket_capacity < MIN_BUCKET_CAPACITY {
@@ -270,7 +270,7 @@ impl Validatable<ConfigError> for MemoryPoolConfig {
"Configured system.memory_pool.buffers {} is less than minimum {}",
self.bucket_capacity, MIN_BUCKET_CAPACITY
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if self.enabled && !self.bucket_capacity.is_power_of_two() {
@@ -278,15 +278,15 @@ impl Validatable<ConfigError> for MemoryPoolConfig {
"Configured system.memory_pool.buffers {} is not a power of 2",
self.bucket_capacity
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
Ok(())
}
}
-impl Validatable<ConfigError> for ShardingConfig {
- fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for ShardingConfig {
+ fn validate(&self) -> Result<(), ConfigurationError> {
let available_cpus = available_parallelism()
.expect("Failed to get number of CPU cores")
.get();
@@ -296,13 +296,13 @@ impl Validatable<ConfigError> for ShardingConfig {
CpuAllocation::Count(count) => {
if *count == 0 {
eprintln!("Invalid sharding configuration: cpu_allocation count cannot be 0");
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if *count > available_cpus {
eprintln!(
"Invalid sharding configuration: cpu_allocation count {count} exceeds available CPU cores {available_cpus}"
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
Ok(())
}
@@ -311,13 +311,13 @@ impl Validatable<ConfigError> for ShardingConfig {
eprintln!(
"Invalid sharding configuration: cpu_allocation range {start}..{end} is invalid (start must be less than end)"
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
if *end > available_cpus {
eprintln!(
"Invalid sharding configuration: cpu_allocation range {start}..{end} exceeds available CPU cores (max: {available_cpus})"
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
Ok(())
}
@@ -325,8 +325,8 @@ impl Validatable<ConfigError> for ShardingConfig {
}
}
-impl Validatable<ConfigError> for ClusterConfig {
- fn validate(&self) -> Result<(), ConfigError> {
+impl Validatable<ConfigurationError> for ClusterConfig {
+ fn validate(&self) -> Result<(), ConfigurationError> {
if !self.enabled {
return Ok(());
}
@@ -334,13 +334,13 @@ impl Validatable<ConfigError> for ClusterConfig {
// Validate cluster name is not empty
if self.name.trim().is_empty() {
eprintln!("Invalid cluster configuration: cluster name cannot be empty");
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
// Validate current node name is not empty
if self.node.current.name.trim().is_empty() {
eprintln!("Invalid cluster configuration: current node name cannot be empty");
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
// Check for duplicate node names among other nodes
@@ -353,7 +353,7 @@ impl Validatable<ConfigError> for ClusterConfig {
"Invalid cluster configuration: duplicate node name '{}' found",
node.name
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
}
@@ -363,7 +363,7 @@ impl Validatable<ConfigError> for ClusterConfig {
// Validate node name is not empty
if node.name.trim().is_empty() {
eprintln!("Invalid cluster configuration: node name cannot be empty");
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
// Validate IP is not empty
@@ -372,7 +372,7 @@ impl Validatable<ConfigError> for ClusterConfig {
"Invalid cluster configuration: IP cannot be empty for node '{}'",
node.name
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
// Validate transport ports if provided
@@ -390,7 +390,7 @@ impl Validatable<ConfigError> for ClusterConfig {
"Invalid cluster configuration: {} port cannot be 0 for node '{}'",
name, node.name
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
// Check for port conflicts across nodes on the same IP
@@ -400,7 +400,7 @@ impl Validatable<ConfigError> for ClusterConfig {
"Invalid cluster configuration: port conflict - {}:{} is already used",
node.ip, port
);
- return Err(ConfigError::InvalidConfiguration);
+ return Err(ConfigurationError::InvalidConfigurationValue);
}
}
}
diff --git a/core/server/src/main.rs b/core/server/src/main.rs
index 7283d40df..44a2357d3 100644
--- a/core/server/src/main.rs
+++ b/core/server/src/main.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
@@ -28,7 +29,6 @@ use server::bootstrap::{
create_directories, create_shard_connections, create_shard_executor, load_config, load_streams,
load_users, resolve_persister, update_system_info,
};
-use server::configs::config_provider::{self};
use server::configs::sharding::CpuAllocation;
use server::diagnostics::{print_io_uring_permission_info, print_locked_memory_limit_info};
use server::io::fs_utils;
@@ -99,8 +99,7 @@ fn main() -> Result<(), ServerError> {
// FIRST DISCRETE LOADING STEP.
// Load config and create directories.
// Remove `local_data` directory if run with `--fresh` flag.
- let config_provider = config_provider::resolve(&args.config_provider)?;
- let config = load_config(&config_provider).await.with_error(|error| {
+ let config = load_config().await.with_error(|error| {
format!("{COMPONENT} (error: {error}) - failed to load config during bootstrap")
})?;
if args.fresh {
diff --git a/core/server/src/server_error.rs b/core/server/src/server_error.rs
index 02822a1d0..4cc01f414 100644
--- a/core/server/src/server_error.rs
+++ b/core/server/src/server_error.rs
@@ -1,4 +1,5 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
@@ -22,7 +23,7 @@ use std::array::TryFromSliceError;
use std::io;
error_set!(
- ServerError := ConfigError || ArchiverError || ConnectionError || LogError || CompatError || QuicError
+ ServerError := ConfigurationError || ArchiverError || ConnectionError || LogError || CompatError || QuicError
IoError := {
#[display("IO error")]
@@ -35,18 +36,8 @@ error_set!(
ReadToEndError(ReadError)
}
- ConfigError := {
- #[display("Invalid configuration provider: {}", provider_type)]
- InvalidConfigurationProvider { provider_type: String },
-
- #[display("Cannot load configuration")]
- CannotLoadConfiguration,
-
- #[display("Invalid configuration")]
- InvalidConfiguration,
-
- #[display("Cache config validation failure")]
- CacheConfigValidationFailure,
+ ConfigurationError := {
+ ConfigurationError(iggy_common::ConfigurationError),
}
ArchiverError := {