Skip to content

Commit

Permalink
feat(config): add multiple file config sources (#1907)
Browse files Browse the repository at this point in the history
  • Loading branch information
gurinderu committed Nov 16, 2023
1 parent ed65f0d commit 8bba3ea
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 80 deletions.
2 changes: 1 addition & 1 deletion crates/peer-metrics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fluence-app-service = { workspace = true }
fluence-libp2p = { workspace = true }
particle-execution = { workspace = true }

tokio = { workspace = true }
tokio = { workspace = true, features = ["macros", "tracing"] }
tokio-stream = { workspace = true }
futures = { workspace = true }
serde = { version = "1.0.192", features = ["derive"] }
Expand Down
1 change: 0 additions & 1 deletion crates/server-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ fluence-keypair = { workspace = true }
log = "0.4.20"
toml = "0.7.3"


libp2p = { workspace = true }
libp2p-metrics = { workspace = true }
libp2p-connection-limits = { workspace = true }
Expand Down
13 changes: 10 additions & 3 deletions crates/server-config/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,14 +359,21 @@ pub(crate) struct DerivedArgs {

#[arg(
short('c'),
long,
long("config"),
id = "CONFIG_FILE",
help_heading = "Node configuration",
help = "TOML configuration file",
long_help = "TOML configuration file. If not specified, the default configuration is used. \
If specified, the default configuration is merged with the specified one. \
The argument can by used multiple times. \
The last configuration overrides the previous ones.",
value_name = "PATH",
display_order = 15
num_args(1..),
value_delimiter(','),
display_order = 15,
)]
pub(crate) config: Option<PathBuf>,
pub(crate) configs: Option<Vec<PathBuf>>,
#[arg(
short('d'),
long,
Expand Down
9 changes: 0 additions & 9 deletions crates/server-config/src/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ pub fn default_connection_idle_timeout() -> Duration {
pub fn default_max_established_per_peer_limit() -> Option<u32> {
Some(5)
}

pub fn default_auto_particle_ttl() -> Duration {
Duration::from_secs(200)
}

pub fn default_bootstrap_nodes() -> Vec<Multiaddr> {
vec![]
}
Expand Down Expand Up @@ -164,10 +159,6 @@ pub fn default_execution_timeout() -> Duration {
Duration::from_secs(20)
}

pub fn default_autodeploy_retry_attempts() -> u16 {
5
}

pub fn default_processing_timeout() -> Duration {
Duration::from_secs(120)
}
Expand Down
31 changes: 0 additions & 31 deletions crates/server-config/src/node_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,6 @@ pub struct UnresolvedNodeConfig {
#[serde(default)]
pub builtins_key_pair: Option<KeypairConfig>,

/// Particle ttl for autodeploy
#[serde(default = "default_auto_particle_ttl")]
#[serde(with = "humantime_serde")]
pub autodeploy_particle_ttl: Duration,

/// Configure the number of ping attempts to check the readiness of the vm pool.
/// Total wait time is the autodeploy_particle_ttl times the number of attempts.
#[serde(default = "default_autodeploy_retry_attempts")]
pub autodeploy_retry_attempts: u16,

/// Affects builtins autodeploy. If set to true, then all builtins should be recreated and their state is cleaned up.
#[serde(default)]
pub force_builtins_redeploy: bool,

#[serde(flatten)]
pub transport_config: TransportConfig,

Expand Down Expand Up @@ -188,10 +174,6 @@ impl UnresolvedNodeConfig {
allow_local_addresses: self.allow_local_addresses,
particle_execution_timeout: self.particle_execution_timeout,
management_peer_id: self.management_peer_id,

autodeploy_particle_ttl: self.autodeploy_particle_ttl,
autodeploy_retry_attempts: self.autodeploy_retry_attempts,
force_builtins_redeploy: self.force_builtins_redeploy,
transport_config: self.transport_config,
listen_config: self.listen_config,
allowed_binaries,
Expand Down Expand Up @@ -305,16 +287,6 @@ pub struct NodeConfig {
#[derivative(Debug = "ignore")]
pub builtins_key_pair: KeyPair,

/// Particle ttl for autodeploy
pub autodeploy_particle_ttl: Duration,

/// Configure the number of ping attempts to check the readiness of the vm pool.
/// Total wait time is the autodeploy_particle_ttl times the number of attempts.
pub autodeploy_retry_attempts: u16,

/// Affects builtins autodeploy. If set to true, then all builtins should be recreated and their state is cleaned up.
pub force_builtins_redeploy: bool,

pub transport_config: TransportConfig,

pub listen_config: ListenConfig,
Expand Down Expand Up @@ -452,9 +424,6 @@ pub struct ListenConfig {
/// For ws connections
#[serde(default = "default_websocket_port")]
pub websocket_port: u16,

#[serde(default)]
pub listen_multiaddrs: Vec<Multiaddr>,
}

#[derive(Clone, Deserialize, Serialize, Debug, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
Expand Down
192 changes: 161 additions & 31 deletions crates/server-config/src/resolved_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ use std::path::PathBuf;
use std::str::FromStr;

use clap::{Args, Command, FromArgMatches};
use config::{Config, Environment, File, FileFormat};
use config::{Config, Environment, File, FileFormat, FileSourceFile};
use libp2p::core::{multiaddr::Protocol, Multiaddr};
use serde::{Deserialize, Serialize};

use crate::args;
use crate::args::DerivedArgs;
use crate::dir_config::{ResolvedDirConfig, UnresolvedDirConfig};
use crate::node_config::{NodeConfig, UnresolvedNodeConfig};

Expand Down Expand Up @@ -171,6 +172,23 @@ pub struct ConfigData {
pub description: String,
}

/// Hierarchically loads the configuration using args and envs.
/// The source order is:
/// - Load and parse Config.toml from cwd, if exists
/// - Load and parse files provided by FLUENCE_CONFIG env var
/// - Load and parse files provided by --config arg
/// - Load config values from env vars
/// - Load config values from args (throw error on conflicts with env vars)
/// On each stage the values override the previous ones.
///
/// # Arguments
///
/// - `data`: Optional `ConfigData` to customize the configuration.
///
/// # Returns
///
/// Returns a Result containing the unresolved configuration or an Eyre error.
///
pub fn load_config(data: Option<ConfigData>) -> eyre::Result<UnresolvedConfig> {
let raw_args = std::env::args_os().collect::<Vec<_>>();
load_config_with_args(raw_args, data)
Expand All @@ -180,31 +198,18 @@ pub fn load_config_with_args(
raw_args: Vec<OsString>,
data: Option<ConfigData>,
) -> eyre::Result<UnresolvedConfig> {
let command = Command::new("Fluence peer");
let command = if let Some(data) = data {
command
.version(&data.version)
.author(&data.authors)
.about(data.description)
.override_usage(format!("{} [FLAGS] [OPTIONS]", data.binary_name))
} else {
command
};

let raw_cli_config = args::DerivedArgs::augment_args(command);
let matches = raw_cli_config.get_matches_from(raw_args);
let cli_config = args::DerivedArgs::from_arg_matches(&matches)?;

let file_source = cli_config
.config
.clone()
.or_else(|| std::env::var_os("FLUENCE_CONFIG").map(PathBuf::from))
.map(|path| File::from(path).format(FileFormat::Toml))
.unwrap_or(
File::with_name("Config.toml")
.required(false)
.format(FileFormat::Toml),
);
let arg_source = process_args(raw_args, data)?;

let arg_config_sources: Vec<File<FileSourceFile, FileFormat>> = arg_source
.configs
.iter()
.flat_map(|paths| {
paths
.iter()
.map(|path| File::from(path.clone()).format(FileFormat::Toml))
.collect::<Vec<File<FileSourceFile, FileFormat>>>()
})
.collect();

let env_source = Environment::with_prefix("FLUENCE")
.try_parsing(true)
Expand All @@ -217,17 +222,57 @@ pub fn load_config_with_args(
.with_list_parse_key("listen_config.listen_multiaddrs")
.with_list_parse_key("system_services.enable");

let config = Config::builder()
.add_source(file_source)
.add_source(env_source)
.add_source(cli_config)
.build()?;
let env_config_sources: Vec<File<FileSourceFile, FileFormat>> =
std::env::var_os("FLUENCE_CONFIG")
.and_then(|str| str.into_string().ok())
.map(|str| {
str.trim()
.split(",")
.map(PathBuf::from)
.map(|path| File::from(path.clone()).format(FileFormat::Toml))
.collect()
})
.unwrap_or_default();

let mut config_builder = Config::builder().add_source(
File::with_name("Config.toml")
.required(false)
.format(FileFormat::Toml),
);

for source in env_config_sources {
config_builder = config_builder.add_source(source)
}

for source in arg_config_sources {
config_builder = config_builder.add_source(source)
}
config_builder = config_builder.add_source(env_source).add_source(arg_source);
let config = config_builder.build()?;

let config: UnresolvedConfig = config.try_deserialize()?;

Ok(config)
}

fn process_args(raw_args: Vec<OsString>, data: Option<ConfigData>) -> eyre::Result<DerivedArgs> {
let command = Command::new("Fluence peer");
let command = if let Some(data) = data {
command
.version(&data.version)
.author(&data.authors)
.about(data.description)
.override_usage(format!("{} [FLAGS] [OPTIONS]", data.binary_name))
} else {
command
};

let raw_cli_config = args::DerivedArgs::augment_args(command);
let matches = raw_cli_config.get_matches_from(raw_args);
let arg_source = args::DerivedArgs::from_arg_matches(&matches)?;
Ok(arg_source)
}

#[cfg(test)]
mod tests {
use std::io::Write;
Expand Down Expand Up @@ -763,4 +808,89 @@ mod tests {
);
});
}

#[test]
fn load_multiple_configs() {
let mut file = NamedTempFile::new().expect("Could not create temp file");
write!(
file,
r#"
[protocol_config]
upgrade_timeout = "60s"
"#
)
.expect("Could not write in file");

let path = file.path().display().to_string();

let mut file2 = NamedTempFile::new().expect("Could not create temp file");
write!(
file2,
r#"
allowed_binaries = [
"/usr/bin/curl",
"/usr/bin/ipfs",
"/usr/bin/glaze",
"/usr/bin/bitcoin-cli"
]
websocket_port = 1234
"#
)
.expect("Could not write in file");

let path2 = file2.path().display().to_string();

let mut file3 = NamedTempFile::new().expect("Could not create temp file");
write!(
file3,
r#"
websocket_port = 666
"#
)
.expect("Could not write in file");

let path3 = file3.path().display().to_string();

let mut file4 = NamedTempFile::new().expect("Could not create temp file");
write!(
file4,
r#"
aquavm_pool_size = 160
"#
)
.expect("Could not write in file");

let path4 = file4.path().display().to_string();

let args = vec![
OsString::from("nox"),
OsString::from("--config"),
OsString::from(path3.to_string()),
OsString::from("--config"),
OsString::from(path4.to_string()),
];

temp_env::with_var(
"FLUENCE_CONFIG",
Some(format!("{},{}", path, path2)),
|| {
let config = load_config_with_args(args, None).expect("Could not load config");
assert_eq!(
config.node_config.protocol_config.upgrade_timeout,
Duration::from_secs(60)
);
assert_eq!(
config.node_config.allowed_binaries,
vec![
"/usr/bin/curl",
"/usr/bin/ipfs",
"/usr/bin/glaze",
"/usr/bin/bitcoin-cli"
]
);
assert_eq!(config.node_config.listen_config.websocket_port, 666);
assert_eq!(config.node_config.aquavm_pool_size, 160);
},
);
}
}
8 changes: 4 additions & 4 deletions nox/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,6 @@ fn main() -> eyre::Result<()> {
.build()
.expect("Could not make tokio runtime")
.block_on(async {
if let Some(true) = config.print_config {
log::info!("Loaded config: {:#?}", config);
}

let resolver_config = config.clone().resolve()?;

let key_pair = resolver_config.node_config.root_key_pair.clone();
Expand All @@ -123,6 +119,10 @@ fn main() -> eyre::Result<()> {
.with(tracing_layer(&config.tracing, peer_id, VERSION)?)
.init();

if let Some(true) = config.print_config {
log::info!("Loaded config: {:#?}", config);
}

log::info!("node public key = {}", base64_key_pair);
log::info!("node server peer id = {}", peer_id);

Expand Down

0 comments on commit 8bba3ea

Please sign in to comment.