Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 36 additions & 31 deletions packages/configuration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
pub mod v1;

use std::collections::HashMap;
use std::env;
use std::sync::Arc;
use std::{env, fs};

use camino::Utf8PathBuf;
use derive_more::Constructor;
Expand All @@ -20,6 +20,19 @@ use torrust_tracker_located_error::{DynError, LocatedError};
/// The maximum number of returned peers for a torrent.
pub const TORRENT_PEERS_LIMIT: usize = 74;

// Environment variables

/// The whole `tracker.toml` file content. It has priority over the config file.
/// Even if the file is not on the default path.
const ENV_VAR_CONFIG: &str = "TORRUST_TRACKER_CONFIG";

/// The `tracker.toml` file location.
pub const ENV_VAR_PATH_CONFIG: &str = "TORRUST_TRACKER_PATH_CONFIG";

/// Env var to overwrite API admin token.
/// Deprecated: use `TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN`.
const ENV_VAR_API_ADMIN_TOKEN: &str = "TORRUST_TRACKER_API_ADMIN_TOKEN";

pub type Configuration = v1::Configuration;
pub type UdpTracker = v1::udp_tracker::UdpTracker;
pub type HttpTracker = v1::http_tracker::HttpTracker;
Expand All @@ -38,7 +51,8 @@ pub struct TrackerPolicy {
/// Information required for loading config
#[derive(Debug, Default, Clone)]
pub struct Info {
tracker_toml: String,
config_toml: Option<String>,
config_toml_path: String,
api_admin_token: Option<String>,
}

Expand All @@ -50,39 +64,30 @@ impl Info {
/// Will return `Err` if unable to obtain a configuration.
///
#[allow(clippy::needless_pass_by_value)]
pub fn new(
env_var_config: String,
env_var_path_config: String,
default_path_config: String,
env_var_api_admin_token: String,
) -> Result<Self, Error> {
let tracker_toml = if let Ok(tracker_toml) = env::var(&env_var_config) {
println!("Loading configuration from env var {env_var_config} ...");

tracker_toml
pub fn new(default_config_toml_path: String) -> Result<Self, Error> {
let env_var_config_toml = ENV_VAR_CONFIG.to_string();
let env_var_config_toml_path = ENV_VAR_PATH_CONFIG.to_string();
let env_var_api_admin_token = ENV_VAR_API_ADMIN_TOKEN.to_string();

let config_toml = if let Ok(config_toml) = env::var(env_var_config_toml) {
println!("Loading configuration from environment variable {config_toml} ...");
Some(config_toml)
} else {
None
};

let config_toml_path = if let Ok(config_toml_path) = env::var(env_var_config_toml_path) {
println!("Loading configuration from file: `{config_toml_path}` ...");
config_toml_path
} else {
let config_path = if let Ok(config_path) = env::var(env_var_path_config) {
println!("Loading configuration file: `{config_path}` ...");

config_path
} else {
println!("Loading default configuration file: `{default_path_config}` ...");

default_path_config
};

fs::read_to_string(config_path)
.map_err(|e| Error::UnableToLoadFromConfigFile {
source: (Arc::new(e) as DynError).into(),
})?
.parse()
.map_err(|_e: std::convert::Infallible| Error::Infallible)?
println!("Loading configuration from default configuration file: `{default_config_toml_path}` ...");
default_config_toml_path
};
let api_admin_token = env::var(env_var_api_admin_token).ok();

Ok(Self {
tracker_toml,
api_admin_token,
config_toml,
config_toml_path,
api_admin_token: env::var(env_var_api_admin_token).ok(),
})
}
}
Expand Down
71 changes: 59 additions & 12 deletions packages/configuration/src/v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ use self::tracker_api::HttpApi;
use self::udp_tracker::UdpTracker;
use crate::{Error, Info};

/// Prefix for env vars that overwrite configuration options.
const CONFIG_OVERRIDE_PREFIX: &str = "TORRUST_TRACKER_CONFIG_OVERRIDE_";
/// Path separator in env var names for nested values in configuration.
const CONFIG_OVERRIDE_SEPARATOR: &str = "__";

/// Core configuration for the tracker.
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
pub struct Configuration {
Expand Down Expand Up @@ -315,9 +320,16 @@ impl Configuration {
///
/// Will return `Err` if the environment variable does not exist or has a bad configuration.
pub fn load(info: &Info) -> Result<Configuration, Error> {
let figment = Figment::from(Serialized::defaults(Configuration::default()))
.merge(Toml::string(&info.tracker_toml))
.merge(Env::prefixed("TORRUST_TRACKER__").split("__"));
let figment = if let Some(config_toml) = &info.config_toml {
// Config in env var has priority over config file path
Figment::from(Serialized::defaults(Configuration::default()))
.merge(Toml::string(config_toml))
.merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR))
} else {
Figment::from(Serialized::defaults(Configuration::default()))
.merge(Toml::file(&info.config_toml_path))
.merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR))
};

let mut config: Configuration = figment.extract()?;

Expand Down Expand Up @@ -449,11 +461,14 @@ mod tests {

#[test]
fn configuration_should_use_the_default_values_when_an_empty_configuration_is_provided_by_the_user() {
figment::Jail::expect_with(|_jail| {
figment::Jail::expect_with(|jail| {
jail.create_file("tracker.toml", "")?;

let empty_configuration = String::new();

let info = Info {
tracker_toml: empty_configuration,
config_toml: Some(empty_configuration),
config_toml_path: "tracker.toml".to_string(),
api_admin_token: None,
};

Expand All @@ -466,28 +481,59 @@ mod tests {
}

#[test]
fn configuration_should_be_loaded_from_a_toml_config_file() {
fn default_configuration_could_be_overwritten_from_a_single_env_var_with_toml_contents() {
figment::Jail::expect_with(|_jail| {
let config_toml = r#"
db_path = "OVERWRITTEN DEFAULT DB PATH"
"#
.to_string();

let info = Info {
tracker_toml: default_config_toml(),
config_toml: Some(config_toml),
config_toml_path: String::new(),
api_admin_token: None,
};

let configuration = Configuration::load(&info).expect("Could not load configuration from file");

assert_eq!(configuration, Configuration::default());
assert_eq!(configuration.core.db_path, "OVERWRITTEN DEFAULT DB PATH".to_string());

Ok(())
});
}

#[test]
fn default_configuration_could_be_overwritten_from_a_toml_config_file() {
figment::Jail::expect_with(|jail| {
jail.create_file(
"tracker.toml",
r#"
db_path = "OVERWRITTEN DEFAULT DB PATH"
"#,
)?;

let info = Info {
config_toml: None,
config_toml_path: "tracker.toml".to_string(),
api_admin_token: None,
};

let configuration = Configuration::load(&info).expect("Could not load configuration from file");

assert_eq!(configuration.core.db_path, "OVERWRITTEN DEFAULT DB PATH".to_string());

Ok(())
});
}

#[test]
fn configuration_should_allow_to_overwrite_the_default_tracker_api_token_for_admin_with_env_var() {
fn configuration_should_allow_to_overwrite_the_default_tracker_api_token_for_admin_with_an_env_var() {
figment::Jail::expect_with(|jail| {
jail.set_env("TORRUST_TRACKER__HTTP_API__ACCESS_TOKENS__ADMIN", "NewToken");
jail.set_env("TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN", "NewToken");

let info = Info {
tracker_toml: default_config_toml(),
config_toml: Some(default_config_toml()),
config_toml_path: String::new(),
api_admin_token: None,
};

Expand All @@ -506,7 +552,8 @@ mod tests {
fn configuration_should_allow_to_overwrite_the_default_tracker_api_token_for_admin_with_the_deprecated_env_var_name() {
figment::Jail::expect_with(|_jail| {
let info = Info {
tracker_toml: default_config_toml(),
config_toml: Some(default_config_toml()),
config_toml_path: String::new(),
api_admin_token: Some("NewToken".to_owned()),
};

Expand Down
22 changes: 2 additions & 20 deletions src/bootstrap/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,6 @@

use torrust_tracker_configuration::{Configuration, Info};

// Environment variables

/// The whole `tracker.toml` file content. It has priority over the config file.
/// Even if the file is not on the default path.
const ENV_VAR_CONFIG: &str = "TORRUST_TRACKER_CONFIG";
const ENV_VAR_API_ADMIN_TOKEN: &str = "TORRUST_TRACKER_API_ADMIN_TOKEN";

/// The `tracker.toml` file location.
pub const ENV_VAR_PATH_CONFIG: &str = "TORRUST_TRACKER_PATH_CONFIG";

// Default values
pub const DEFAULT_PATH_CONFIG: &str = "./share/default/config/tracker.development.sqlite3.toml";

/// It loads the application configuration from the environment.
Expand All @@ -34,15 +23,8 @@ pub const DEFAULT_PATH_CONFIG: &str = "./share/default/config/tracker.developmen
/// `./tracker.toml` file or the env var `TORRUST_TRACKER_CONFIG`.
#[must_use]
pub fn initialize_configuration() -> Configuration {
let info = Info::new(
ENV_VAR_CONFIG.to_string(),
ENV_VAR_PATH_CONFIG.to_string(),
DEFAULT_PATH_CONFIG.to_string(),
ENV_VAR_API_ADMIN_TOKEN.to_string(),
)
.unwrap();

Configuration::load(&info).unwrap()
let info = Info::new(DEFAULT_PATH_CONFIG.to_string()).expect("info to load configuration is not valid");
Configuration::load(&info).expect("configuration should be loaded from provided info")
}

#[cfg(test)]
Expand Down