diff --git a/cincinnati/src/plugins/catalog.rs b/cincinnati/src/plugins/catalog.rs index 1780e9cdf..22ca0915c 100644 --- a/cincinnati/src/plugins/catalog.rs +++ b/cincinnati/src/plugins/catalog.rs @@ -7,7 +7,7 @@ use super::internal::channel_filter::ChannelFilterPlugin; use super::internal::edge_add_remove::EdgeAddRemovePlugin; use super::internal::metadata_fetch_quay::QuayMetadataFetchPlugin; use super::internal::node_remove::NodeRemovePlugin; -use super::{Plugin, PluginIO}; +use crate::plugins::BoxedPlugin; use failure::Fallible; use std::fmt::Debug; @@ -17,7 +17,7 @@ static CONFIG_PLUGIN_NAME_KEY: &str = "name"; /// Settings for a plugin. pub trait PluginSettings: Debug + Send { /// Build the corresponding plugin for this configuration. - fn build_plugin(&self) -> Fallible>>; + fn build_plugin(&self) -> Fallible; } /// Validate configuration for a plugin and fill in defaults. diff --git a/cincinnati/src/plugins/internal/channel_filter.rs b/cincinnati/src/plugins/internal/channel_filter.rs index 7729b02e4..ab5d7ca52 100644 --- a/cincinnati/src/plugins/internal/channel_filter.rs +++ b/cincinnati/src/plugins/internal/channel_filter.rs @@ -3,7 +3,7 @@ //! and the value must match the regex specified at CHANNEL_VALIDATION_REGEX_STR use crate::plugins::{ - InternalIO, InternalPlugin, InternalPluginWrapper, Plugin, PluginIO, PluginSettings, + BoxedPlugin, InternalIO, InternalPlugin, InternalPluginWrapper, PluginSettings, }; use failure::Fallible; @@ -21,7 +21,7 @@ pub struct ChannelFilterPlugin { } impl PluginSettings for ChannelFilterPlugin { - fn build_plugin(&self) -> Fallible>> { + fn build_plugin(&self) -> Fallible { Ok(Box::new(InternalPluginWrapper(self.clone()))) } } diff --git a/cincinnati/src/plugins/internal/edge_add_remove.rs b/cincinnati/src/plugins/internal/edge_add_remove.rs index ae426e4fc..c0c765d5b 100644 --- a/cincinnati/src/plugins/internal/edge_add_remove.rs +++ b/cincinnati/src/plugins/internal/edge_add_remove.rs @@ -2,7 +2,7 @@ use crate as cincinnati; use crate::plugins::{ - InternalIO, InternalPlugin, InternalPluginWrapper, Plugin, PluginIO, PluginSettings, + BoxedPlugin, InternalIO, InternalPlugin, InternalPluginWrapper, PluginSettings, }; use crate::ReleaseId; use failure::Fallible; @@ -33,7 +33,7 @@ impl InternalPlugin for EdgeAddRemovePlugin { } impl PluginSettings for EdgeAddRemovePlugin { - fn build_plugin(&self) -> Fallible>> { + fn build_plugin(&self) -> Fallible { Ok(Box::new(InternalPluginWrapper(self.clone()))) } } diff --git a/cincinnati/src/plugins/internal/metadata_fetch_quay.rs b/cincinnati/src/plugins/internal/metadata_fetch_quay.rs index 7559fd630..ba917363d 100644 --- a/cincinnati/src/plugins/internal/metadata_fetch_quay.rs +++ b/cincinnati/src/plugins/internal/metadata_fetch_quay.rs @@ -10,7 +10,7 @@ extern crate tokio; use self::futures::future::Future; use crate::plugins::{ - InternalIO, InternalPlugin, InternalPluginWrapper, Plugin, PluginIO, PluginSettings, + BoxedPlugin, InternalIO, InternalPlugin, InternalPluginWrapper, PluginSettings, }; use crate::ReleaseId; use failure::{Fallible, ResultExt}; @@ -50,7 +50,7 @@ pub struct QuayMetadataFetchPlugin { } impl PluginSettings for QuayMetadataSettings { - fn build_plugin(&self) -> Fallible>> { + fn build_plugin(&self) -> Fallible { let cfg = self.clone(); let plugin = QuayMetadataFetchPlugin::try_new( cfg.repository, diff --git a/cincinnati/src/plugins/internal/node_remove.rs b/cincinnati/src/plugins/internal/node_remove.rs index 771ffd0ef..15767a24b 100644 --- a/cincinnati/src/plugins/internal/node_remove.rs +++ b/cincinnati/src/plugins/internal/node_remove.rs @@ -1,7 +1,7 @@ //! This plugin removes releases according to its metadata use crate::plugins::{ - InternalIO, InternalPlugin, InternalPluginWrapper, Plugin, PluginIO, PluginSettings, + BoxedPlugin, InternalIO, InternalPlugin, InternalPluginWrapper, PluginSettings, }; use failure::Fallible; @@ -15,7 +15,7 @@ pub struct NodeRemovePlugin { } impl PluginSettings for NodeRemovePlugin { - fn build_plugin(&self) -> Fallible>> { + fn build_plugin(&self) -> Fallible { Ok(Box::new(InternalPluginWrapper(self.clone()))) } } diff --git a/cincinnati/src/plugins/mod.rs b/cincinnati/src/plugins/mod.rs index 1cbe896f9..a9adf0a1f 100644 --- a/cincinnati/src/plugins/mod.rs +++ b/cincinnati/src/plugins/mod.rs @@ -17,6 +17,18 @@ use std::collections::HashMap; use std::fmt::Debug; use try_from::{TryFrom, TryInto}; +/// Type for storing policy plugins in applications. +pub type BoxedPlugin = Box + Sync + Send>; + +// NOTE(lucab): this abuses `Debug`, because `PartialEq` is not object-safe and +// thus cannot be required on the underlying trait. It is a crude hack, but +// only meant to be used by test assertions. +impl PartialEq for BoxedPlugin { + fn eq(&self, other: &Self) -> bool { + format!("{:?}", self) == format!("{:?}", other) + } +} + /// Enum for the two IO variants used by InternalPlugin and ExternalPlugin respectively #[derive(Debug)] pub enum PluginIO { diff --git a/commons/src/config.rs b/commons/src/config.rs index e7d8c0147..bb91575c4 100644 --- a/commons/src/config.rs +++ b/commons/src/config.rs @@ -13,12 +13,12 @@ macro_rules! assign_if_some { }}; } -/// Merge configuration options into runtime settings. +/// Try to merge configuration options into runtime settings. /// -/// This consumes a generic configuration object, merging its options +/// This consumes a generic configuration object, trying to merge its options /// into runtime settings. It only overlays populated values from config, /// leaving unset ones preserved as-is from existing settings. pub trait MergeOptions { /// MergeOptions values from `options` into current settings. - fn merge(&mut self, options: T); + fn try_merge(&mut self, options: T) -> failure::Fallible<()>; } diff --git a/graph-builder/src/config/cli.rs b/graph-builder/src/config/cli.rs index 8df961828..ea52b8eb3 100644 --- a/graph-builder/src/config/cli.rs +++ b/graph-builder/src/config/cli.rs @@ -3,6 +3,7 @@ use super::options; use super::AppSettings; use commons::MergeOptions; +use failure::Fallible; /// CLI configuration flags, top-level. #[derive(Debug, StructOpt)] @@ -34,22 +35,24 @@ pub struct CliOptions { } impl MergeOptions for AppSettings { - fn merge(&mut self, opts: CliOptions) { + fn try_merge(&mut self, opts: CliOptions) -> Fallible<()> { self.verbosity = match opts.verbosity { 0 => self.verbosity, 1 => log::LevelFilter::Info, 2 => log::LevelFilter::Debug, _ => log::LevelFilter::Trace, }; - self.merge(Some(opts.service)); - self.merge(Some(opts.status)); - self.merge(Some(opts.upstream_registry)); + self.try_merge(Some(opts.service))?; + self.try_merge(Some(opts.status))?; + self.try_merge(Some(opts.upstream_registry))?; // TODO(lucab): drop this when plugins are configurable. assign_if_some!( self.disable_quay_api_metadata, opts.disable_quay_api_metadata ); + + Ok(()) } } @@ -87,7 +90,7 @@ mod tests { let cli = CliOptions::from_iter_safe(args).unwrap(); assert_eq!(cli.upstream_registry.repository, Some(repo.to_string())); - settings.merge(cli); + settings.try_merge(cli).unwrap(); assert_eq!(settings.repository, repo.to_string()); } @@ -102,14 +105,14 @@ mod tests { let file_opts: FileOptions = toml::from_str(toml_verbosity).unwrap(); assert_eq!(file_opts.verbosity, Some(log::LevelFilter::Trace)); - settings.merge(Some(file_opts)); + settings.try_merge(Some(file_opts)).unwrap(); assert_eq!(settings.verbosity, log::LevelFilter::Trace); let args = vec!["argv0", "-vv"]; let cli_opts = CliOptions::from_iter_safe(args).unwrap(); assert_eq!(cli_opts.verbosity, 2); - settings.merge(cli_opts); + settings.try_merge(cli_opts).unwrap(); assert_eq!(settings.verbosity, log::LevelFilter::Debug); } } diff --git a/graph-builder/src/config/file.rs b/graph-builder/src/config/file.rs index 208cc9af7..631d7b27f 100644 --- a/graph-builder/src/config/file.rs +++ b/graph-builder/src/config/file.rs @@ -48,13 +48,14 @@ impl FileOptions { } impl MergeOptions> for AppSettings { - fn merge(&mut self, opts: Option) { + fn try_merge(&mut self, opts: Option) -> Fallible<()>{ if let Some(file) = opts { assign_if_some!(self.verbosity, file.verbosity); - self.merge(file.service); - self.merge(file.status); - self.merge(file.upstream); + self.try_merge(file.service)?; + self.try_merge(file.status)?; + self.try_merge(file.upstream)?; } + Ok(()) } } @@ -69,10 +70,11 @@ pub struct UpstreamOptions { } impl MergeOptions> for AppSettings { - fn merge(&mut self, opts: Option) { + fn try_merge(&mut self, opts: Option) -> Fallible<()> { if let Some(upstream) = opts { - self.merge(upstream.registry); + self.try_merge(upstream.registry)?; } + Ok(()) } } @@ -105,7 +107,7 @@ mod tests { let toml_input = "status.port = 2222"; let file_opts: FileOptions = toml::from_str(toml_input).unwrap(); - settings.merge(Some(file_opts)); + settings.try_merge(Some(file_opts)).unwrap(); assert_eq!(settings.status_port, 2222); } diff --git a/graph-builder/src/config/options.rs b/graph-builder/src/config/options.rs index 5221cbe5b..8f89424d2 100644 --- a/graph-builder/src/config/options.rs +++ b/graph-builder/src/config/options.rs @@ -2,6 +2,7 @@ use super::AppSettings; use commons::{parse_params_set, parse_path_prefix, MergeOptions}; +use failure::Fallible; use std::collections::HashSet; use std::net::IpAddr; use std::path::PathBuf; @@ -77,7 +78,7 @@ pub struct DockerRegistryOptions { } impl MergeOptions> for AppSettings { - fn merge(&mut self, opts: Option) { + fn try_merge(&mut self, opts: Option) -> Fallible<()> { if let Some(service) = opts { assign_if_some!(self.address, service.address); assign_if_some!(self.port, service.port); @@ -86,20 +87,22 @@ impl MergeOptions> for AppSettings { self.mandatory_client_parameters.extend(params); } } + Ok(()) } } impl MergeOptions> for AppSettings { - fn merge(&mut self, opts: Option) { + fn try_merge(&mut self, opts: Option) -> Fallible<()> { if let Some(status) = opts { assign_if_some!(self.status_address, status.address); assign_if_some!(self.status_port, status.port); } + Ok(()) } } impl MergeOptions> for AppSettings { - fn merge(&mut self, opts: Option) { + fn try_merge(&mut self, opts: Option) -> Fallible<()> { if let Some(registry) = opts { assign_if_some!(self.pause_secs, registry.pause_secs); assign_if_some!(self.registry, registry.url); @@ -107,6 +110,7 @@ impl MergeOptions> for AppSettings { assign_if_some!(self.credentials_path, registry.credentials_path); assign_if_some!(self.manifestref_key, registry.manifestref_key); } + Ok(()) } } diff --git a/graph-builder/src/config/settings.rs b/graph-builder/src/config/settings.rs index 8ab74491b..34abd8797 100644 --- a/graph-builder/src/config/settings.rs +++ b/graph-builder/src/config/settings.rs @@ -76,8 +76,8 @@ impl AppSettings { // Combine options into a single config. let mut cfg = defaults; - cfg.merge(cli_opts); - cfg.merge(file_opts); + cfg.try_merge(cli_opts)?; + cfg.try_merge(file_opts)?; // Validate and convert to settings. Self::try_validate(cfg) diff --git a/policy-engine/src/config/cli.rs b/policy-engine/src/config/cli.rs index 30091ef9f..436e0fc06 100644 --- a/policy-engine/src/config/cli.rs +++ b/policy-engine/src/config/cli.rs @@ -3,6 +3,7 @@ use super::options; use super::AppSettings; use commons::MergeOptions; +use failure::Fallible; /// CLI configuration flags, top-level. #[derive(Debug, StructOpt)] @@ -33,16 +34,19 @@ pub struct CliOptions { } impl MergeOptions for AppSettings { - fn merge(&mut self, opts: CliOptions) { + fn try_merge(&mut self, opts: CliOptions) -> Fallible<()> { self.verbosity = match opts.verbosity { 0 => self.verbosity, 1 => log::LevelFilter::Info, 2 => log::LevelFilter::Debug, _ => log::LevelFilter::Trace, }; - self.merge(Some(opts.service)); - self.merge(Some(opts.status)); - self.merge(Some(opts.upstream_cincinnati)); + + self.try_merge(Some(opts.service))?; + self.try_merge(Some(opts.status))?; + self.try_merge(Some(opts.upstream_cincinnati))?; + + Ok(()) } } @@ -84,13 +88,14 @@ mod tests { let cli = CliOptions::from_iter_safe(args).unwrap(); assert_eq!(cli.upstream_cincinnati.url, Some(up_url.clone())); - settings.merge(cli); + settings.try_merge(cli).unwrap(); assert_eq!(settings.upstream, up_url); } #[test] fn cli_override_toml() { use crate::config::file::FileOptions; + use commons::MergeOptions; let mut settings = AppSettings::default(); assert_eq!(settings.verbosity, log::LevelFilter::Warn); @@ -99,14 +104,14 @@ mod tests { let file_opts: FileOptions = toml::from_str(toml_verbosity).unwrap(); assert_eq!(file_opts.verbosity, Some(log::LevelFilter::Trace)); - settings.merge(Some(file_opts)); + settings.try_merge(Some(file_opts)).unwrap(); assert_eq!(settings.verbosity, log::LevelFilter::Trace); let args = vec!["argv0", "-vv"]; let cli_opts = CliOptions::from_iter_safe(args).unwrap(); assert_eq!(cli_opts.verbosity, 2); - settings.merge(cli_opts); + settings.try_merge(cli_opts).unwrap(); assert_eq!(settings.verbosity, log::LevelFilter::Debug); } } diff --git a/policy-engine/src/config/file.rs b/policy-engine/src/config/file.rs index 3bf5b4c70..29fc337ff 100644 --- a/policy-engine/src/config/file.rs +++ b/policy-engine/src/config/file.rs @@ -18,6 +18,9 @@ pub struct FileOptions { /// Upstream options. pub upstream: Option, + /// Policy plugins options. + pub policy: Option>, + /// Web frontend options. pub service: Option, @@ -49,13 +52,27 @@ impl FileOptions { } impl MergeOptions> for AppSettings { - fn merge(&mut self, opts: Option) { + fn try_merge(&mut self, opts: Option) -> failure::Fallible<()> { if let Some(file) = opts { assign_if_some!(self.verbosity, file.verbosity); - self.merge(file.service); - self.merge(file.status); - self.merge(file.upstream); + self.try_merge(file.policy)?; + self.try_merge(file.service)?; + self.try_merge(file.status)?; + self.try_merge(file.upstream)?; + } + Ok(()) + } +} + +impl MergeOptions>> for AppSettings { + fn try_merge(&mut self, opts: Option>) -> Fallible<()> { + if let Some(policies) = opts { + for conf in policies { + let plugin = cincinnati::plugins::deserialize_config(conf)?; + self.policies.push(plugin); + } } + Ok(()) } } @@ -70,16 +87,19 @@ pub struct UpstreamOptions { } impl MergeOptions> for AppSettings { - fn merge(&mut self, opts: Option) { + fn try_merge(&mut self, opts: Option) -> Fallible<()> { if let Some(upstream) = opts { - self.merge(upstream.cincinnati); + self.try_merge(upstream.cincinnati)?; } + Ok(()) } } #[cfg(test)] mod tests { - use super::*; + use super::FileOptions; + use crate::config::AppSettings; + use commons::MergeOptions; #[test] fn toml_basic() { @@ -99,7 +119,7 @@ mod tests { let toml_input = "status.port = 2222"; let file_opts: FileOptions = toml::from_str(toml_input).unwrap(); - settings.merge(Some(file_opts)); + settings.try_merge(Some(file_opts)).unwrap(); assert_eq!(settings.status_port, 2222); } @@ -142,4 +162,40 @@ mod tests { let ups_url = ups.url.unwrap(); assert_eq!(ups_url, input_url); } + + #[test] + fn toml_basic_policy() { + use cincinnati::plugins::internal::channel_filter::ChannelFilterPlugin; + use cincinnati::plugins::{BoxedPlugin, InternalPluginWrapper}; + + let expected: Vec = + vec![Box::new(InternalPluginWrapper(ChannelFilterPlugin { + key_prefix: String::from("io.openshift.upgrades.graph"), + key_suffix: String::from("release.channels"), + }))]; + let mut settings = AppSettings::default(); + + let opts = { + use std::io::Write; + + let sample_config = r#" + [[policy]] + name = "channel-filter" + key_prefix = "io.openshift.upgrades.graph" + key_suffix = "release.channels" + "#; + + let mut config_file = tempfile::NamedTempFile::new().unwrap(); + config_file + .write_fmt(format_args!("{}", sample_config)) + .unwrap(); + crate::config::FileOptions::read_filepath(config_file.path()).unwrap() + }; + assert!(opts.policy.is_some()); + settings.try_merge(Some(opts)).unwrap(); + assert_eq!(settings.policies.len(), 1); + + let policies = settings.policy_plugins().unwrap(); + assert_eq!(policies, expected); + } } diff --git a/policy-engine/src/config/mod.rs b/policy-engine/src/config/mod.rs index 5eec97576..2d7b346f6 100644 --- a/policy-engine/src/config/mod.rs +++ b/policy-engine/src/config/mod.rs @@ -11,5 +11,8 @@ mod file; mod options; mod settings; +#[cfg(test)] +pub(crate) use self::file::FileOptions; + pub use self::settings::AppSettings; pub use self::settings::DEFAULT_UPSTREAM_URL; diff --git a/policy-engine/src/config/options.rs b/policy-engine/src/config/options.rs index 267172aee..3a05ecca9 100644 --- a/policy-engine/src/config/options.rs +++ b/policy-engine/src/config/options.rs @@ -2,6 +2,7 @@ use super::AppSettings; use commons::{parse_params_set, parse_path_prefix, MergeOptions}; +use failure::Fallible; use std::collections::HashSet; use std::net::IpAddr; @@ -18,11 +19,12 @@ pub struct StatusOptions { } impl MergeOptions> for AppSettings { - fn merge(&mut self, opts: Option) { + fn try_merge(&mut self, opts: Option) -> Fallible<()> { if let Some(status) = opts { assign_if_some!(self.status_address, status.address); assign_if_some!(self.status_port, status.port); } + Ok(()) } } @@ -50,7 +52,7 @@ pub struct ServiceOptions { } impl MergeOptions> for AppSettings { - fn merge(&mut self, opts: Option) { + fn try_merge(&mut self, opts: Option) -> Fallible<()> { if let Some(service) = opts { assign_if_some!(self.address, service.address); assign_if_some!(self.port, service.port); @@ -59,6 +61,7 @@ impl MergeOptions> for AppSettings { self.mandatory_client_parameters.extend(params); } } + Ok(()) } } @@ -72,10 +75,11 @@ pub struct UpCincinnatiOptions { } impl MergeOptions> for AppSettings { - fn merge(&mut self, opts: Option) { + fn try_merge(&mut self, opts: Option) -> Fallible<()> { if let Some(up) = opts { assign_if_some!(self.upstream, up.url); } + Ok(()) } } diff --git a/policy-engine/src/config/settings.rs b/policy-engine/src/config/settings.rs index c8952addf..84ac51059 100644 --- a/policy-engine/src/config/settings.rs +++ b/policy-engine/src/config/settings.rs @@ -1,7 +1,7 @@ //! Application settings for policy-engine. use super::{cli, file}; -use commons::MergeOptions; +use cincinnati::plugins::{BoxedPlugin, PluginSettings}; use failure::Fallible; use hyper::Uri; use std::collections::HashSet; @@ -41,6 +41,9 @@ pub struct AppSettings { /// Endpoints namespace for the main service. pub path_prefix: String, + /// Policy plugins configuration. + pub policies: Vec>, + /// Required client parameters for the main service. pub mandatory_client_parameters: HashSet, } @@ -49,6 +52,8 @@ impl AppSettings { /// Lookup all optional configs, merge them with defaults, and /// transform into valid runtime settings. pub fn assemble() -> Fallible { + use commons::MergeOptions; + let defaults = Self::default(); // Source options. @@ -60,13 +65,29 @@ impl AppSettings { // Combine options into a single config. let mut cfg = defaults; - cfg.merge(cli_opts); - cfg.merge(file_opts); + cfg.try_merge(cli_opts)?; + cfg.try_merge(file_opts)?; // Validate and convert to settings. Self::try_validate(cfg) } + /// Validate and return policy plugins. + pub fn policy_plugins(&self) -> Fallible> { + let mut configured_plugins = Vec::with_capacity(self.policies.len()); + for conf in &self.policies { + let plugin = conf.build_plugin()?; + configured_plugins.push(plugin); + } + + // TODO(lucab): drop this as soon as all config-maps are in place (prod & staging). + if configured_plugins.is_empty() { + configured_plugins = default_openshift_plugins(); + } + + Ok(configured_plugins) + } + /// Validate and build runtime settings. fn try_validate(self) -> Fallible { if self.address == self.status_address && self.port == self.status_port { @@ -76,3 +97,15 @@ impl AppSettings { Ok(self) } } + +fn default_openshift_plugins() -> Vec { + // TODO(lucab): drop this as soon as all config-maps are in place (prod & staging). + use cincinnati::plugins::internal::channel_filter::ChannelFilterPlugin; + use cincinnati::plugins::internal::metadata_fetch_quay::DEFAULT_QUAY_LABEL_FILTER; + use cincinnati::plugins::InternalPluginWrapper; + + vec![Box::new(InternalPluginWrapper(ChannelFilterPlugin { + key_prefix: String::from(DEFAULT_QUAY_LABEL_FILTER), + key_suffix: String::from("release.channels"), + }))] +} diff --git a/policy-engine/src/graph.rs b/policy-engine/src/graph.rs index 063742bd3..9eefcb74c 100644 --- a/policy-engine/src/graph.rs +++ b/policy-engine/src/graph.rs @@ -3,10 +3,7 @@ use crate::AppState; use actix_web::http::header::{self, HeaderValue}; use actix_web::{HttpRequest, HttpResponse}; -use cincinnati::plugins::internal::channel_filter::ChannelFilterPlugin; -use cincinnati::plugins::internal::metadata_fetch_quay::DEFAULT_QUAY_LABEL_FILTER; -use cincinnati::plugins::InternalPluginWrapper; -use cincinnati::{plugins, Graph, CONTENT_TYPE}; +use cincinnati::{Graph, CONTENT_TYPE}; use commons::{self, GraphError}; use failure::Fallible; use futures::{future, Future, Stream}; @@ -64,16 +61,6 @@ pub(crate) fn index(req: HttpRequest) -> Box>> = { - // TODO(steveeJ): actually make this vec configurable - vec![Box::new(InternalPluginWrapper(ChannelFilterPlugin { - // TODO(steveej): make this configurable - key_prefix: String::from(DEFAULT_QUAY_LABEL_FILTER), - key_suffix: String::from("release.channels"), - }))] - }; - // TODO(steveeJ): take another look at the actix-web docs for a method that // provides this parameters split. let plugin_params = req @@ -98,6 +85,12 @@ pub(crate) fn index(req: HttpRequest) -> Box() + .expect(commons::MISSING_APPSTATE_PANIC_MSG) + .plugins + .clone(); + // Assemble a request for the upstream Cincinnati service. let ups_req = match Request::get( &req.app_data::() @@ -137,7 +130,6 @@ pub(crate) fn index(req: HttpRequest) -> Box Runtime { let _ = env_logger::try_init_from_env(env_logger::Env::default()); Runtime::new().unwrap() } + // Source policy plugins from TOML configuration file. + fn openshift_policy_plugins() -> Fallible> { + use commons::MergeOptions; + + let mut settings = crate::config::AppSettings::default(); + let opts = { + use std::io::Write; + + let sample_config = r#" + [[policy]] + name = "channel-filter" + key_prefix = "io.openshift.upgrades.graph" + key_suffix = "release.channels" + "#; + + let mut config_file = tempfile::NamedTempFile::new().unwrap(); + config_file + .write_fmt(format_args!("{}", sample_config)) + .unwrap(); + crate::config::FileOptions::read_filepath(config_file.path()).unwrap() + }; + + settings.try_merge(Some(opts))?; + let plugins = settings.policy_plugins()?; + Ok(plugins) + } + #[test] fn missing_content_type() { let mut rt = common_init(); @@ -246,9 +268,12 @@ mod tests { use std::str::FromStr; let mut rt = common_init(); + + let policies = openshift_policy_plugins()?; let mandatory_params = vec!["channel".to_string()].into_iter().collect(); let state = AppState { mandatory_params, + plugins: std::sync::Arc::new(policies), upstream: hyper::Uri::from_str(&mockito::server_url())?, ..Default::default() }; @@ -321,9 +346,11 @@ mod tests { .create(); // prepare and run the policy-engine test-service + let policies = openshift_policy_plugins()?; let app = actix_web::App::new() .register_data(actix_web::web::Data::new(AppState { mandatory_params: mandatory_params.iter().map(|s| s.to_string()).collect(), + plugins: std::sync::Arc::new(policies), upstream: hyper::Uri::from_str(&mockito::server_url())?, ..Default::default() })) diff --git a/policy-engine/src/main.rs b/policy-engine/src/main.rs index 37ca942d0..ef1142397 100644 --- a/policy-engine/src/main.rs +++ b/policy-engine/src/main.rs @@ -36,13 +36,16 @@ mod metrics; mod openapi; use actix_web::{App, HttpServer}; +use cincinnati::plugins::BoxedPlugin; use failure::Error; use std::collections::HashSet; +use std::sync::Arc; fn main() -> Result<(), Error> { let sys = actix::System::new("policy-engine"); let settings = config::AppSettings::assemble()?; + let plugins = Arc::new(settings.policy_plugins()?); env_logger::Builder::from_default_env() .filter(Some(module_path!()), settings.verbosity) @@ -64,6 +67,7 @@ fn main() -> Result<(), Error> { mandatory_params: settings.mandatory_client_parameters.clone(), upstream: settings.upstream.clone(), path_prefix: settings.path_prefix.clone(), + plugins: Arc::clone(&plugins), }; HttpServer::new(move || { @@ -95,11 +99,14 @@ struct AppState { pub upstream: hyper::Uri, /// Common namespace for API endpoints. pub path_prefix: String, + /// Policy plugins. + pub plugins: Arc>, } impl Default for AppState { fn default() -> Self { Self { + plugins: Arc::new(vec![]), mandatory_params: HashSet::new(), upstream: hyper::Uri::from_static(config::DEFAULT_UPSTREAM_URL), path_prefix: String::new(), diff --git a/policy-engine/src/openapi.rs b/policy-engine/src/openapi.rs index 10f777f60..009a8270a 100644 --- a/policy-engine/src/openapi.rs +++ b/policy-engine/src/openapi.rs @@ -159,6 +159,7 @@ mod tests { let data = actix_web::web::Data::new(AppState { mandatory_params: mandatory_params.clone(), path_prefix: path_prefix.clone(), + plugins: std::sync::Arc::new(vec![]), upstream: Default::default(), }); let resource =