diff --git a/proxy/src/app.rs b/proxy/src/app.rs index e83fb1858..094170b18 100644 --- a/proxy/src/app.rs +++ b/proxy/src/app.rs @@ -1,7 +1,9 @@ -use config::{self, Config}; +use config::{self, Config, Env}; +use convert::TryFrom; use logging; pub fn init() -> Result { logging::init(); - Config::load_from_env() + let config_strings = Env; + Config::try_from(&config_strings) } diff --git a/proxy/src/config.rs b/proxy/src/config.rs index da48be59a..50022b98e 100644 --- a/proxy/src/config.rs +++ b/proxy/src/config.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::env; use std::net::SocketAddr; use std::path::PathBuf; @@ -6,6 +7,8 @@ use std::time::Duration; use url::{Host, HostAndPort, Url}; +use convert::TryFrom; + // TODO: // // * Make struct fields private. @@ -96,14 +99,29 @@ pub enum UrlError { FragmentNotAllowed, } +/// The strings used to build a configuration. +pub trait Strings { + /// Retrieves the value for the key `key`. + /// + /// `key` must be one of the `ENV_` values below. + fn get(&self, key: &str) -> Result, Error>; +} + +/// An implementation of `Strings` that reads the values from environment variables. +pub struct Env; + +pub struct TestEnv { + values: HashMap<&'static str, String> +} + // Environment variables to look at when loading the configuration const ENV_EVENT_BUFFER_CAPACITY: &str = "CONDUIT_PROXY_EVENT_BUFFER_CAPACITY"; -const ENV_METRICS_FLUSH_INTERVAL_SECS: &str = "CONDUIT_PROXY_METRICS_FLUSH_INTERVAL_SECS"; +pub const ENV_METRICS_FLUSH_INTERVAL_SECS: &str = "CONDUIT_PROXY_METRICS_FLUSH_INTERVAL_SECS"; const ENV_REPORT_TIMEOUT_SECS: &str = "CONDUIT_PROXY_REPORT_TIMEOUT_SECS"; -const ENV_PRIVATE_LISTENER: &str = "CONDUIT_PROXY_PRIVATE_LISTENER"; -const ENV_PRIVATE_FORWARD: &str = "CONDUIT_PROXY_PRIVATE_FORWARD"; -const ENV_PUBLIC_LISTENER: &str = "CONDUIT_PROXY_PUBLIC_LISTENER"; -const ENV_CONTROL_LISTENER: &str = "CONDUIT_PROXY_CONTROL_LISTENER"; +pub const ENV_PRIVATE_LISTENER: &str = "CONDUIT_PROXY_PRIVATE_LISTENER"; +pub const ENV_PRIVATE_FORWARD: &str = "CONDUIT_PROXY_PRIVATE_FORWARD"; +pub const ENV_PUBLIC_LISTENER: &str = "CONDUIT_PROXY_PUBLIC_LISTENER"; +pub const ENV_CONTROL_LISTENER: &str = "CONDUIT_PROXY_CONTROL_LISTENER"; const ENV_PRIVATE_CONNECT_TIMEOUT: &str = "CONDUIT_PROXY_PRIVATE_CONNECT_TIMEOUT"; const ENV_PUBLIC_CONNECT_TIMEOUT: &str = "CONDUIT_PROXY_PUBLIC_CONNECT_TIMEOUT"; @@ -112,7 +130,7 @@ pub const ENV_NODE_NAME: &str = "CONDUIT_PROXY_NODE_NAME"; pub const ENV_POD_NAME: &str = "CONDUIT_PROXY_POD_NAME"; pub const ENV_POD_NAMESPACE: &str = "CONDUIT_PROXY_POD_NAMESPACE"; -const ENV_CONTROL_URL: &str = "CONDUIT_PROXY_CONTROL_URL"; +pub const ENV_CONTROL_URL: &str = "CONDUIT_PROXY_CONTROL_URL"; const ENV_RESOLV_CONF: &str = "CONDUIT_RESOLV_CONF"; // Default values for various configuration fields @@ -127,24 +145,25 @@ const DEFAULT_RESOLV_CONF: &str = "/etc/resolv.conf"; // ===== impl Config ===== -impl Config { +impl<'a> TryFrom<&'a Strings> for Config { + type Err = Error; /// Load a `Config` by reading ENV variables. - pub fn load_from_env() -> Result { + fn try_from(strings: &Strings) -> Result { // Parse all the environment variables. `env_var` and `env_var_parse` // will log any errors so defer returning any errors until all of them // have been parsed. - let private_listener_addr = env_var_parse(ENV_PRIVATE_LISTENER, str::parse); - let public_listener_addr = env_var_parse(ENV_PUBLIC_LISTENER, str::parse); - let control_listener_addr = env_var_parse(ENV_CONTROL_LISTENER, str::parse); - let private_forward = env_var_parse(ENV_PRIVATE_FORWARD, str::parse); - let public_connect_timeout = env_var_parse(ENV_PUBLIC_CONNECT_TIMEOUT, parse_number); - let private_connect_timeout = env_var_parse(ENV_PRIVATE_CONNECT_TIMEOUT, parse_number); - let resolv_conf_path = env_var(ENV_RESOLV_CONF); - let control_host_and_port = env_var_parse(ENV_CONTROL_URL, parse_url); - let event_buffer_capacity = env_var_parse(ENV_EVENT_BUFFER_CAPACITY, parse_number); + let private_listener_addr = parse(strings, ENV_PRIVATE_LISTENER, str::parse); + let public_listener_addr = parse(strings, ENV_PUBLIC_LISTENER, str::parse); + let control_listener_addr = parse(strings, ENV_CONTROL_LISTENER, str::parse); + let private_forward = parse(strings, ENV_PRIVATE_FORWARD, str::parse); + let public_connect_timeout = parse(strings, ENV_PUBLIC_CONNECT_TIMEOUT, parse_number); + let private_connect_timeout = parse(strings, ENV_PRIVATE_CONNECT_TIMEOUT, parse_number); + let resolv_conf_path = strings.get(ENV_RESOLV_CONF); + let control_host_and_port = parse(strings, ENV_CONTROL_URL, parse_url); + let event_buffer_capacity = parse(strings, ENV_EVENT_BUFFER_CAPACITY, parse_number); let metrics_flush_interval_secs = - env_var_parse(ENV_METRICS_FLUSH_INTERVAL_SECS, parse_number); - let report_timeout = env_var_parse(ENV_REPORT_TIMEOUT_SECS, parse_number); + parse(strings, ENV_METRICS_FLUSH_INTERVAL_SECS, parse_number); + let report_timeout = parse(strings, ENV_REPORT_TIMEOUT_SECS, parse_number); Ok(Config { private_listener: Listener { @@ -207,6 +226,43 @@ impl From for SocketAddr { } } +// ===== impl Env ===== + +impl Strings for Env { + fn get(&self, key: &str) -> Result, Error> { + match env::var(key) { + Ok(value) => Ok(Some(value)), + Err(env::VarError::NotPresent) => Ok(None), + Err(env::VarError::NotUnicode(_)) => { + error!("{} is not encoded in Unicode", key); + Err(Error::InvalidEnvVar) + } + } + } +} + +// ===== impl TestEnv ===== + +impl TestEnv { + pub fn new() -> Self { + Self { + values: Default::default(), + } + } + + pub fn put(&mut self, key: &'static str, value: String) { + self.values.insert(key, value); + } +} + +impl Strings for TestEnv { + fn get(&self, key: &str) -> Result, Error> { + Ok(self.values.get(key).cloned()) + } +} + +// ===== Parsing ===== + fn parse_number(s: &str) -> Result where T: FromStr { s.parse().map_err(|_| ParseError::NotANumber) } @@ -232,20 +288,9 @@ fn parse_url(s: &str) -> Result { }) } -fn env_var(name: &str) -> Result, Error> { - match env::var(name) { - Ok(value) => Ok(Some(value)), - Err(env::VarError::NotPresent) => Ok(None), - Err(env::VarError::NotUnicode(_)) => { - error!("{} is not encoded in Unicode", name); - Err(Error::InvalidEnvVar) - } - } -} - -fn env_var_parse(name: &str, parse: Parse) -> Result, Error> +fn parse(strings: &Strings, name: &str, parse: Parse) -> Result, Error> where Parse: FnOnce(&str) -> Result { - match env_var(name)? { + match strings.get(name)? { Some(ref s) => { let r = parse(s).map_err(|parse_error| { error!("{} is not valid: {:?}", name, parse_error); diff --git a/proxy/src/lib.rs b/proxy/src/lib.rs index 0f003d9cf..2e4561385 100644 --- a/proxy/src/lib.rs +++ b/proxy/src/lib.rs @@ -60,7 +60,7 @@ mod bind; pub mod config; mod connection; pub mod control; -mod convert; +pub mod convert; mod ctx; mod dns; mod inbound; diff --git a/proxy/tests/support/proxy.rs b/proxy/tests/support/proxy.rs index 5141ee872..5c8552135 100644 --- a/proxy/tests/support/proxy.rs +++ b/proxy/tests/support/proxy.rs @@ -1,5 +1,7 @@ use support::*; +use support::conduit_proxy::convert::TryFrom; + pub fn new() -> Proxy { Proxy::new() } @@ -59,36 +61,27 @@ impl Proxy { } fn run(proxy: Proxy) -> Listening { + use self::conduit_proxy::config; + let controller = proxy.controller.expect("proxy controller missing"); let inbound = proxy.inbound; let outbound = proxy.outbound; - let mut config = conduit_proxy::config::Config::load_from_env().unwrap(); - - config.control_host_and_port = { - let control_url: url::Url = format!("tcp://{}", controller.addr).parse().unwrap(); - url::HostAndPort { - host: control_url.host().unwrap().to_owned(), - port: control_url.port().unwrap(), - } - }; - - config.private_listener = conduit_proxy::config::Listener { - addr: "tcp://127.0.0.1:0".parse().unwrap(), - }; - + let mut env = config::TestEnv::new(); + env.put(config::ENV_CONTROL_URL, format!("tcp://{}", controller.addr)); + env.put(config::ENV_PRIVATE_LISTENER, "tcp://127.0.0.1:0".to_owned()); if let Some(ref inbound) = inbound { - config.private_forward = format!("tcp://{}", inbound.addr).parse().ok(); + env.put(config::ENV_PRIVATE_FORWARD, format!("tcp://{}", inbound.addr)); } + env.put(config::ENV_PUBLIC_LISTENER, "tcp://127.0.0.1:0".to_owned()); + env.put(config::ENV_CONTROL_LISTENER, "tcp://127.0.0.1:0".to_owned()); - config.public_listener = conduit_proxy::config::Listener { - addr: "tcp://127.0.0.1:0".parse().unwrap(), - }; - - config.control_listener = conduit_proxy::config::Listener { - addr: "tcp://127.0.0.1:0".parse().unwrap(), - }; + let mut config = config::Config::try_from(&env).unwrap(); + // TODO: We currently can't use `config::ENV_METRICS_FLUSH_INTERVAL_SECS` + // because we need to be able to set the flush interval to a fraction of a + // second. We should change config::ENV_METRICS_FLUSH_INTERVAL_SECS so that + // it can support this. if let Some(dur) = proxy.metrics_flush_interval { config.metrics_flush_interval = dur; }