From d99a97b80aaebfb8a81630e3b510b5f51ade6835 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 16 Mar 2022 13:39:23 +0100 Subject: [PATCH 01/10] added OpaConfig and tests --- src/lib.rs | 1 + src/opa.rs | 245 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 src/opa.rs diff --git a/src/lib.rs b/src/lib.rs index ac41f5048..1a31a9592 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod label_selector; pub mod labels; pub mod logging; pub mod namespace; +pub mod opa; pub mod pod_utils; pub mod product_config_utils; pub mod role_utils; diff --git a/src/opa.rs b/src/opa.rs new file mode 100644 index 000000000..217932733 --- /dev/null +++ b/src/opa.rs @@ -0,0 +1,245 @@ +//! This module offers common access to the [`OpaConfig`] which can be used in operators +//! to specify a name for a [`k8s_openapi::api::core::v1::ConfigMap`] and a package name +//! for OPA rules. +//! +//! Additionally several methods are provided to build an URL to query the OPA data API. +//! +use kube::ResourceExt; +use schemars::{self, JsonSchema}; +use serde::{Deserialize, Serialize}; + +const OPA_API: &str = "v1/data/"; + +#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OpaConfig { + pub config_map_name: String, + pub package: Option, +} + +impl OpaConfig { + /// Returns the full qualified OPA data API url up to the package. If [`OpaConfig`] has + /// no `package` set, will default to the cluster `resource` name. + /// + /// # Example + /// + /// http://localhost:8080/v1/data/ + /// + /// # Arguments + /// * `resource` - The cluster resource + /// * `opa_base_url` - The base url to OPA e.g. http://localhost:8081 + pub fn full_package_url(&self, resource: &T, opa_base_url: &str) -> String + where + T: ResourceExt, + { + if opa_base_url.ends_with('/') { + format!("{}{}", opa_base_url, self.package_url(resource)) + } else { + format!("{}/{}", opa_base_url, self.package_url(resource)) + } + } + + /// Returns the full qualified OPA data API url up to the rule. If [`OpaConfig`] has + /// no `package` set, will default to the cluster `resource` name. + /// + /// # Example + /// + /// http://localhost:8080/v1/data// + /// + /// # Arguments + /// * `resource` - The cluster resource. + /// * `opa_base_url` - The base url to OPA e.g. http://localhost:8081. + /// * `rule` - The rule name. Defaults to `allow`. + pub fn full_rule_url(&self, resource: &T, opa_base_url: &str, rule: Option<&str>) -> String + where + T: ResourceExt, + { + if opa_base_url.ends_with('/') { + format!("{}{}", opa_base_url, self.rule_url(resource, rule)) + } else { + format!("{}/{}", opa_base_url, self.rule_url(resource, rule)) + } + } + + /// Returns the OPA data API url up to the package. If [`OpaConfig`] has + /// no `package` set, will default to the cluster `resource` name. + /// + /// This may be used if the OPA base url is contained in an ENV variable. + /// + /// # Example + /// + /// v1/data/ + /// + /// # Arguments + /// * `resource` - The cluster resource. + pub fn package_url(&self, resource: &T) -> String + where + T: ResourceExt, + { + let package_name = match &self.package { + Some(p) => p.to_string(), + None => resource.name(), + }; + + format!("{}/{}", OPA_API, package_name) + } + + /// Returns the OPA data API url up to the rule. If [`OpaConfig`] has + /// no `package` set, will default to the cluster `resource` name. + /// + /// This may be used if the OPA base url is contained in an ENV variable. + /// + /// # Example + /// + /// v1/data// + /// + /// # Arguments + /// * `resource` - The cluster resource. + /// * `rule` - The rule name. Defaults to `allow`. + pub fn rule_url(&self, resource: &T, rule: Option<&str>) -> String + where + T: ResourceExt, + { + format!("{}/{}", self.package_url(resource), rule.unwrap_or("allow")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use kube::CustomResource; + use schemars::{self, JsonSchema}; + use serde::{Deserialize, Serialize}; + + const CLUSTER_NAME: &str = "simple-cluster"; + const PACKAGE_NAME: &str = "my-package"; + const RULE_DEFAULT: &str = "allow"; + const RULE_NAME: &str = "test-rule"; + const OPA_BASE_URL_WITH_SLASH: &str = "http://opa:8081/"; + const OPA_BASE_URL_WITHOUT_SLASH: &str = "http://opa:8081"; + + #[test] + fn test_package_url_with_package_name() { + let cluster = build_test_cluster(); + let opa_config = build_opa_config(Some(PACKAGE_NAME)); + + assert_eq!( + opa_config.package_url(&cluster), + format!("{}/{}", OPA_API, PACKAGE_NAME) + ) + } + + #[test] + fn test_package_url_without_package_name() { + let cluster = build_test_cluster(); + let opa_config = build_opa_config(None); + + assert_eq!( + opa_config.package_url(&cluster), + format!("{}/{}", OPA_API, CLUSTER_NAME) + ) + } + + #[test] + fn test_rule_url_with_package_name() { + let cluster = build_test_cluster(); + let opa_config = build_opa_config(Some(PACKAGE_NAME)); + + assert_eq!( + opa_config.rule_url(&cluster, None), + format!("{}/{}/{}", OPA_API, PACKAGE_NAME, RULE_DEFAULT) + ); + + assert_eq!( + opa_config.rule_url(&cluster, Some(RULE_NAME)), + format!("{}/{}/{}", OPA_API, PACKAGE_NAME, RULE_NAME) + ); + } + + #[test] + fn test_rule_url_without_package_name() { + let cluster = build_test_cluster(); + let opa_config = build_opa_config(None); + + assert_eq!( + opa_config.rule_url(&cluster, None), + format!("{}/{}/{}", OPA_API, CLUSTER_NAME, RULE_DEFAULT) + ); + + assert_eq!( + opa_config.rule_url(&cluster, Some(RULE_NAME)), + format!("{}/{}/{}", OPA_API, CLUSTER_NAME, RULE_NAME) + ); + } + + #[test] + fn test_full_package_url() { + let cluster = build_test_cluster(); + let opa_config = build_opa_config(None); + + assert_eq!( + opa_config.full_package_url(&cluster, OPA_BASE_URL_WITH_SLASH), + format!("{}{}/{}", OPA_BASE_URL_WITH_SLASH, OPA_API, CLUSTER_NAME) + ); + + let opa_config = build_opa_config(Some(PACKAGE_NAME)); + + assert_eq!( + opa_config.full_package_url(&cluster, OPA_BASE_URL_WITHOUT_SLASH), + format!( + "{}/{}/{}", + OPA_BASE_URL_WITHOUT_SLASH, OPA_API, PACKAGE_NAME + ) + ); + } + + #[test] + fn test_full_rule_url() { + let cluster = build_test_cluster(); + let opa_config = build_opa_config(None); + + assert_eq!( + opa_config.full_rule_url(&cluster, OPA_BASE_URL_WITHOUT_SLASH, None), + format!( + "{}/{}/{}/{}", + OPA_BASE_URL_WITHOUT_SLASH, OPA_API, CLUSTER_NAME, RULE_DEFAULT + ) + ); + + assert_eq!( + opa_config.full_rule_url(&cluster, OPA_BASE_URL_WITHOUT_SLASH, Some(RULE_NAME)), + format!( + "{}/{}/{}/{}", + OPA_BASE_URL_WITHOUT_SLASH, OPA_API, CLUSTER_NAME, RULE_NAME + ) + ); + } + + #[derive(Clone, CustomResource, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] + #[kube(group = "test", version = "v1", kind = "TestCluster", namespaced)] + pub struct ClusterSpec { + test: u8, + } + + fn build_test_cluster() -> TestCluster { + serde_yaml::from_str(&format!( + " + apiVersion: test/v1 + kind: TestCluster + metadata: + name: {} + spec: + test: 100 + ", + CLUSTER_NAME + )) + .unwrap() + } + + fn build_opa_config(package: Option<&str>) -> OpaConfig { + OpaConfig { + config_map_name: "opa".to_string(), + package: package.map(|p| p.to_string()), + } + } +} From 3f0420227290f96c9d956d4c12da3ab1e0dabfcf Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 16 Mar 2022 13:47:23 +0100 Subject: [PATCH 02/10] adapted changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b45e8f79c..69d7a281d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,16 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Common `OpaConfig` to specify a config map and package name ([#357]). + ### Changed - Split up the builder module into submodules. This is not breaking yet due to reexports. Deprecation warning has been added for `operator-rs` `0.15.0` ([#348]). [#348]: https://github.com/stackabletech/operator-rs/pull/348 +[#357]: https://github.com/stackabletech/operator-rs/pull/357 ## [0.14.1] - 2022.03.15 From 9ed0a47a15167dfb8ffa313b6bcfc577a44295d5 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 16 Mar 2022 16:28:59 +0100 Subject: [PATCH 03/10] added methods to extract the full opa url from the provided configmap name --- src/error.rs | 3 ++ src/opa.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 100 insertions(+), 14 deletions(-) diff --git a/src/error.rs b/src/error.rs index 4399c5a1b..f878c43e7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -61,6 +61,9 @@ pub enum Error { #[error("Error converting CRD byte array to UTF-8")] CrdFromUtf8Error(#[source] std::string::FromUtf8Error), + + #[error("Missing OPA connect string in configmap [{configmap_name}]")] + MissingOpaConnectString { configmap_name: String }, } pub type OperatorResult = std::result::Result; diff --git a/src/opa.rs b/src/opa.rs index 217932733..1940a7359 100644 --- a/src/opa.rs +++ b/src/opa.rs @@ -4,6 +4,10 @@ //! //! Additionally several methods are provided to build an URL to query the OPA data API. //! +use crate::client::Client; +use crate::error; +use crate::error::OperatorResult; +use k8s_openapi::api::core::v1::ConfigMap; use kube::ResourceExt; use schemars::{self, JsonSchema}; use serde::{Deserialize, Serialize}; @@ -18,6 +22,48 @@ pub struct OpaConfig { } impl OpaConfig { + /// Returns the OPA data API url up to the package. If [`OpaConfig`] has + /// no `package` set, will default to the cluster `resource` name. + /// + /// This may be used if the OPA base url is contained in an ENV variable. + /// + /// # Example + /// + /// v1/data/ + /// + /// # Arguments + /// * `resource` - The cluster resource. + pub fn package_url(&self, resource: &T) -> String + where + T: ResourceExt, + { + let package_name = match &self.package { + Some(p) => p.to_string(), + None => resource.name(), + }; + + format!("{}/{}", OPA_API, package_name) + } + + /// Returns the OPA data API url up to the rule. If [`OpaConfig`] has + /// no `package` set, will default to the cluster `resource` name. + /// + /// This may be used if the OPA base url is contained in an ENV variable. + /// + /// # Example + /// + /// v1/data// + /// + /// # Arguments + /// * `resource` - The cluster resource. + /// * `rule` - The rule name. Defaults to `allow`. + pub fn rule_url(&self, resource: &T, rule: Option<&str>) -> String + where + T: ResourceExt, + { + format!("{}/{}", self.package_url(resource), rule.unwrap_or("allow")) + } + /// Returns the full qualified OPA data API url up to the package. If [`OpaConfig`] has /// no `package` set, will default to the cluster `resource` name. /// @@ -61,46 +107,83 @@ impl OpaConfig { } } - /// Returns the OPA data API url up to the package. If [`OpaConfig`] has + /// Returns the full qualified OPA data API url up to the package. If [`OpaConfig`] has /// no `package` set, will default to the cluster `resource` name. /// - /// This may be used if the OPA base url is contained in an ENV variable. + /// In contrast to `full_package_url`, this queries the OPA base url from the provided + /// `config_map_name` in the [`OpaConfig`]. /// /// # Example /// - /// v1/data/ + /// http://localhost:8080/v1/data/ /// /// # Arguments + /// * `client` - The kubernetes client. /// * `resource` - The cluster resource. - pub fn package_url(&self, resource: &T) -> String + pub async fn full_package_url_from_config_map( + &self, + client: &Client, + resource: &T, + ) -> OperatorResult where T: ResourceExt, { - let package_name = match &self.package { - Some(p) => p.to_string(), - None => resource.name(), - }; + let opa_base_url = self + .base_url_from_config_map(client, resource.namespace().as_deref()) + .await?; - format!("{}/{}", OPA_API, package_name) + Ok(self.full_package_url(resource, &opa_base_url)) } - /// Returns the OPA data API url up to the rule. If [`OpaConfig`] has + /// Returns the full qualified OPA data API url up to the rule. If [`OpaConfig`] has /// no `package` set, will default to the cluster `resource` name. /// - /// This may be used if the OPA base url is contained in an ENV variable. + /// In contrast to `full_rule_url`, this queries the OPA base url from the provided + /// `config_map_name` in the [`OpaConfig`]. /// /// # Example /// - /// v1/data// + /// http://localhost:8080/v1/data// /// /// # Arguments + /// * `client` - The kubernetes client. /// * `resource` - The cluster resource. /// * `rule` - The rule name. Defaults to `allow`. - pub fn rule_url(&self, resource: &T, rule: Option<&str>) -> String + pub async fn full_rule_url_from_config_map( + &self, + client: &Client, + resource: &T, + rule: Option<&str>, + ) -> OperatorResult where T: ResourceExt, { - format!("{}/{}", self.package_url(resource), rule.unwrap_or("allow")) + let opa_base_url = self + .base_url_from_config_map(client, resource.namespace().as_deref()) + .await?; + + Ok(self.full_rule_url(resource, &opa_base_url, rule)) + } + + /// Returns the OPA base url defined in the [`k8s_openapi::api::core::v1::ConfigMap`] + /// from `config_map_name` in the [`OpaConfig`]. + /// + /// # Arguments + /// * `client` - The kubernetes client. + /// * `namespace` - The namespace of the config map. + async fn base_url_from_config_map( + &self, + client: &Client, + namespace: Option<&str>, + ) -> OperatorResult { + Ok(client + .get::(&self.config_map_name, namespace) + .await? + .data + .and_then(|mut data| data.remove("OPA")) + .ok_or(error::Error::MissingOpaConnectString { + configmap_name: self.config_map_name.clone(), + })?) } } From f3d5588b1cea803cd71ca197627264173b8729c5 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 16 Mar 2022 16:49:47 +0100 Subject: [PATCH 04/10] added module example --- src/opa.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/opa.rs b/src/opa.rs index 1940a7359..18d8baa74 100644 --- a/src/opa.rs +++ b/src/opa.rs @@ -4,6 +4,54 @@ //! //! Additionally several methods are provided to build an URL to query the OPA data API. //! +//! # Example +//! ```rust +//! use serde::{Deserialize, Serialize}; +//! use stackable_operator::kube::CustomResource; +//! use stackable_operator::opa::OpaConfig; +//! use stackable_operator::schemars::{self, JsonSchema}; +//! +//! #[derive(Clone, CustomResource, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] +//! #[kube( +//! group = "test.stackable.tech", +//! version = "v1alpha1", +//! kind = "TestCluster", +//! plural = "testclusters", +//! shortname = "test", +//! namespaced, +//! crates( +//! kube_core = "stackable_operator::kube::core", +//! k8s_openapi = "stackable_operator::k8s_openapi", +//! schemars = "stackable_operator::schemars" +//! ) +//! )] +//! #[serde(rename_all = "camelCase")] +//! pub struct TestClusterSpec { +//! opa: Option +//! } +//! +//! fn main() { +//! let cluster: TestCluster = serde_yaml::from_str( +//! " +//! apiVersion: test.stackable.tech/v1alpha1 +//! kind: TestCluster +//! metadata: +//! name: simple-test +//! spec: +//! opa: +//! configMapName: simple-opa +//! package: test +//! ", +//! ).unwrap(); +//! +//! let opa_config: &OpaConfig = cluster.spec.opa.as_ref().unwrap(); +//! +//! assert_eq!(opa_config.package_url(&cluster), "v1/data/test".to_string()); +//! assert_eq!(opa_config.full_package_url(&cluster, "http://localhost:8081"), "http://localhost:8081/v1/data/test".to_string()); +//! assert_eq!(opa_config.rule_url(&cluster, Some("myrule")), "v1/data/test/myrule".to_string()); +//! assert_eq!(opa_config.full_rule_url(&cluster, "http://localhost:8081", Some("myrule")), "http://localhost:8081/v1/data/test/myrule".to_string()); +//! } +//! ``` use crate::client::Client; use crate::error; use crate::error::OperatorResult; From 8fcd9b6340f8c8ec4d1130263423b2dd3d9cd0ec Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 16 Mar 2022 16:50:35 +0100 Subject: [PATCH 05/10] adapted module example --- src/opa.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/opa.rs b/src/opa.rs index 18d8baa74..78d9e556a 100644 --- a/src/opa.rs +++ b/src/opa.rs @@ -19,11 +19,6 @@ //! plural = "testclusters", //! shortname = "test", //! namespaced, -//! crates( -//! kube_core = "stackable_operator::kube::core", -//! k8s_openapi = "stackable_operator::k8s_openapi", -//! schemars = "stackable_operator::schemars" -//! ) //! )] //! #[serde(rename_all = "camelCase")] //! pub struct TestClusterSpec { From 0026e089aa435d6e1c59cf6d458c8076c19881b7 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 16 Mar 2022 17:12:23 +0100 Subject: [PATCH 06/10] fixed doc test --- src/opa.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/opa.rs b/src/opa.rs index 78d9e556a..ea172475d 100644 --- a/src/opa.rs +++ b/src/opa.rs @@ -55,7 +55,7 @@ use kube::ResourceExt; use schemars::{self, JsonSchema}; use serde::{Deserialize, Serialize}; -const OPA_API: &str = "v1/data/"; +const OPA_API: &str = "v1/data"; #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] From 71992f1e2615eac9e448ab67c970421fe8650f5f Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 16 Mar 2022 17:17:48 +0100 Subject: [PATCH 07/10] fixed clippy --- src/opa.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/opa.rs b/src/opa.rs index ea172475d..b2ca4acf9 100644 --- a/src/opa.rs +++ b/src/opa.rs @@ -25,8 +25,7 @@ //! opa: Option //! } //! -//! fn main() { -//! let cluster: TestCluster = serde_yaml::from_str( +//! let cluster: TestCluster = serde_yaml::from_str( //! " //! apiVersion: test.stackable.tech/v1alpha1 //! kind: TestCluster @@ -39,13 +38,12 @@ //! ", //! ).unwrap(); //! -//! let opa_config: &OpaConfig = cluster.spec.opa.as_ref().unwrap(); +//! let opa_config: &OpaConfig = cluster.spec.opa.as_ref().unwrap(); //! -//! assert_eq!(opa_config.package_url(&cluster), "v1/data/test".to_string()); -//! assert_eq!(opa_config.full_package_url(&cluster, "http://localhost:8081"), "http://localhost:8081/v1/data/test".to_string()); -//! assert_eq!(opa_config.rule_url(&cluster, Some("myrule")), "v1/data/test/myrule".to_string()); -//! assert_eq!(opa_config.full_rule_url(&cluster, "http://localhost:8081", Some("myrule")), "http://localhost:8081/v1/data/test/myrule".to_string()); -//! } +//! assert_eq!(opa_config.package_url(&cluster), "v1/data/test".to_string()); +//! assert_eq!(opa_config.full_package_url(&cluster, "http://localhost:8081"), "http://localhost:8081/v1/data/test".to_string()); +//! assert_eq!(opa_config.rule_url(&cluster, Some("myrule")), "v1/data/test/myrule".to_string()); +//! assert_eq!(opa_config.full_rule_url(&cluster, "http://localhost:8081", Some("myrule")), "http://localhost:8081/v1/data/test/myrule".to_string()); //! ``` use crate::client::Client; use crate::error; From fde3f2af5c9eb06df3e0e51e77253cabdd620382 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 17 Mar 2022 12:41:21 +0100 Subject: [PATCH 08/10] introduced OpaApiVersion enum to be prepared for api changes. merged package_url and rule_url methods to document_url. --- src/opa.rs | 239 ++++++++++++++++++++--------------------------------- 1 file changed, 90 insertions(+), 149 deletions(-) diff --git a/src/opa.rs b/src/opa.rs index b2ca4acf9..b0f13ebb5 100644 --- a/src/opa.rs +++ b/src/opa.rs @@ -8,7 +8,7 @@ //! ```rust //! use serde::{Deserialize, Serialize}; //! use stackable_operator::kube::CustomResource; -//! use stackable_operator::opa::OpaConfig; +//! use stackable_operator::opa::{OpaApiVersion, OpaConfig}; //! use stackable_operator::schemars::{self, JsonSchema}; //! //! #[derive(Clone, CustomResource, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] @@ -40,10 +40,8 @@ //! //! let opa_config: &OpaConfig = cluster.spec.opa.as_ref().unwrap(); //! -//! assert_eq!(opa_config.package_url(&cluster), "v1/data/test".to_string()); -//! assert_eq!(opa_config.full_package_url(&cluster, "http://localhost:8081"), "http://localhost:8081/v1/data/test".to_string()); -//! assert_eq!(opa_config.rule_url(&cluster, Some("myrule")), "v1/data/test/myrule".to_string()); -//! assert_eq!(opa_config.full_rule_url(&cluster, "http://localhost:8081", Some("myrule")), "http://localhost:8081/v1/data/test/myrule".to_string()); +//! assert_eq!(opa_config.document_url(&cluster, Some("allow"), OpaApiVersion::V1), "v1/data/test/allow".to_string()); +//! assert_eq!(opa_config.full_document_url(&cluster, "http://localhost:8081", None, OpaApiVersion::V1), "http://localhost:8081/v1/data/test".to_string()); //! ``` use crate::client::Client; use crate::error; @@ -53,7 +51,20 @@ use kube::ResourceExt; use schemars::{self, JsonSchema}; use serde::{Deserialize, Serialize}; -const OPA_API: &str = "v1/data"; +/// Indicates the OPA api version. This is required to choose the correct +/// path when constructing the OPA urls to query. +pub enum OpaApiVersion { + V1, +} + +impl OpaApiVersion { + /// Returns the OPA data API path for the selected version + pub fn get_data_api(&self) -> &'static str { + match self { + Self::V1 => "v1/data", + } + } +} #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -63,18 +74,26 @@ pub struct OpaConfig { } impl OpaConfig { - /// Returns the OPA data API url up to the package. If [`OpaConfig`] has - /// no `package` set, will default to the cluster `resource` name. + /// Returns the OPA data API url. If [`OpaConfig`] has no `package` set, + /// will default to the cluster `resource` name. /// /// This may be used if the OPA base url is contained in an ENV variable. /// /// # Example /// - /// v1/data/ + /// * `v1/data/` + /// * `v1/data//` /// /// # Arguments /// * `resource` - The cluster resource. - pub fn package_url(&self, resource: &T) -> String + /// * `rule` - The rule name. Defaults to `allow`. + /// * `api_version` - The [`OpaApiVersion`] to extract the data API path. + pub fn document_url( + &self, + resource: &T, + rule: Option<&str>, + api_version: OpaApiVersion, + ) -> String where T: ResourceExt, { @@ -83,118 +102,76 @@ impl OpaConfig { None => resource.name(), }; - format!("{}/{}", OPA_API, package_name) - } + let mut document_url = format!("{}/{}", api_version.get_data_api(), package_name); - /// Returns the OPA data API url up to the rule. If [`OpaConfig`] has - /// no `package` set, will default to the cluster `resource` name. - /// - /// This may be used if the OPA base url is contained in an ENV variable. - /// - /// # Example - /// - /// v1/data// - /// - /// # Arguments - /// * `resource` - The cluster resource. - /// * `rule` - The rule name. Defaults to `allow`. - pub fn rule_url(&self, resource: &T, rule: Option<&str>) -> String - where - T: ResourceExt, - { - format!("{}/{}", self.package_url(resource), rule.unwrap_or("allow")) + if let Some(document_rule) = rule { + document_url.push('/'); + document_url.push_str(document_rule); + } + + document_url } - /// Returns the full qualified OPA data API url up to the package. If [`OpaConfig`] has - /// no `package` set, will default to the cluster `resource` name. + /// Returns the full qualified OPA data API url. If [`OpaConfig`] has no `package` set, + /// will default to the cluster `resource` name. /// /// # Example /// - /// http://localhost:8080/v1/data/ + /// * `http://localhost:8081/v1/data/` + /// * `http://localhost:8081/v1/data//` /// /// # Arguments /// * `resource` - The cluster resource /// * `opa_base_url` - The base url to OPA e.g. http://localhost:8081 - pub fn full_package_url(&self, resource: &T, opa_base_url: &str) -> String - where - T: ResourceExt, - { - if opa_base_url.ends_with('/') { - format!("{}{}", opa_base_url, self.package_url(resource)) - } else { - format!("{}/{}", opa_base_url, self.package_url(resource)) - } - } - - /// Returns the full qualified OPA data API url up to the rule. If [`OpaConfig`] has - /// no `package` set, will default to the cluster `resource` name. - /// - /// # Example - /// - /// http://localhost:8080/v1/data// - /// - /// # Arguments - /// * `resource` - The cluster resource. - /// * `opa_base_url` - The base url to OPA e.g. http://localhost:8081. - /// * `rule` - The rule name. Defaults to `allow`. - pub fn full_rule_url(&self, resource: &T, opa_base_url: &str, rule: Option<&str>) -> String + /// * `rule` - The rule name. + /// * `api_version` - The [`OpaApiVersion`] to extract the data API path. + pub fn full_document_url( + &self, + resource: &T, + opa_base_url: &str, + rule: Option<&str>, + api_version: OpaApiVersion, + ) -> String where T: ResourceExt, { if opa_base_url.ends_with('/') { - format!("{}{}", opa_base_url, self.rule_url(resource, rule)) + format!( + "{}{}", + opa_base_url, + self.document_url(resource, rule, api_version) + ) } else { - format!("{}/{}", opa_base_url, self.rule_url(resource, rule)) + format!( + "{}/{}", + opa_base_url, + self.document_url(resource, rule, api_version) + ) } } /// Returns the full qualified OPA data API url up to the package. If [`OpaConfig`] has /// no `package` set, will default to the cluster `resource` name. /// - /// In contrast to `full_package_url`, this queries the OPA base url from the provided - /// `config_map_name` in the [`OpaConfig`]. - /// - /// # Example - /// - /// http://localhost:8080/v1/data/ - /// - /// # Arguments - /// * `client` - The kubernetes client. - /// * `resource` - The cluster resource. - pub async fn full_package_url_from_config_map( - &self, - client: &Client, - resource: &T, - ) -> OperatorResult - where - T: ResourceExt, - { - let opa_base_url = self - .base_url_from_config_map(client, resource.namespace().as_deref()) - .await?; - - Ok(self.full_package_url(resource, &opa_base_url)) - } - - /// Returns the full qualified OPA data API url up to the rule. If [`OpaConfig`] has - /// no `package` set, will default to the cluster `resource` name. - /// - /// In contrast to `full_rule_url`, this queries the OPA base url from the provided + /// In contrast to `full_document_url`, this extracts the OPA base url from the provided /// `config_map_name` in the [`OpaConfig`]. /// /// # Example /// - /// http://localhost:8080/v1/data// + /// * `http://localhost:8081/v1/data/` + /// * `http://localhost:8081/v1/data//` /// /// # Arguments /// * `client` - The kubernetes client. /// * `resource` - The cluster resource. - /// * `rule` - The rule name. Defaults to `allow`. - pub async fn full_rule_url_from_config_map( + /// * `rule` - The rule name. + /// * `api_version` - The [`OpaApiVersion`] to extract the data API path. + pub async fn full_document_url_from_config_map( &self, client: &Client, resource: &T, rule: Option<&str>, + api_version: OpaApiVersion, ) -> OperatorResult where T: ResourceExt, @@ -203,7 +180,7 @@ impl OpaConfig { .base_url_from_config_map(client, resource.namespace().as_deref()) .await?; - Ok(self.full_rule_url(resource, &opa_base_url, rule)) + Ok(self.full_document_url(resource, &opa_base_url, rule, api_version)) } /// Returns the OPA base url defined in the [`k8s_openapi::api::core::v1::ConfigMap`] @@ -237,104 +214,68 @@ mod tests { const CLUSTER_NAME: &str = "simple-cluster"; const PACKAGE_NAME: &str = "my-package"; - const RULE_DEFAULT: &str = "allow"; - const RULE_NAME: &str = "test-rule"; + const RULE_NAME: &str = "allow"; const OPA_BASE_URL_WITH_SLASH: &str = "http://opa:8081/"; const OPA_BASE_URL_WITHOUT_SLASH: &str = "http://opa:8081"; - #[test] - fn test_package_url_with_package_name() { - let cluster = build_test_cluster(); - let opa_config = build_opa_config(Some(PACKAGE_NAME)); - - assert_eq!( - opa_config.package_url(&cluster), - format!("{}/{}", OPA_API, PACKAGE_NAME) - ) - } + const V1: OpaApiVersion = OpaApiVersion::V1; #[test] - fn test_package_url_without_package_name() { - let cluster = build_test_cluster(); - let opa_config = build_opa_config(None); - - assert_eq!( - opa_config.package_url(&cluster), - format!("{}/{}", OPA_API, CLUSTER_NAME) - ) - } - - #[test] - fn test_rule_url_with_package_name() { + fn test_document_url_with_package_name() { let cluster = build_test_cluster(); let opa_config = build_opa_config(Some(PACKAGE_NAME)); assert_eq!( - opa_config.rule_url(&cluster, None), - format!("{}/{}/{}", OPA_API, PACKAGE_NAME, RULE_DEFAULT) + opa_config.document_url(&cluster, None, V1), + format!("{}/{}", V1.get_data_api(), PACKAGE_NAME) ); assert_eq!( - opa_config.rule_url(&cluster, Some(RULE_NAME)), - format!("{}/{}/{}", OPA_API, PACKAGE_NAME, RULE_NAME) + opa_config.document_url(&cluster, Some(RULE_NAME), V1), + format!("{}/{}/{}", V1.get_data_api(), PACKAGE_NAME, RULE_NAME) ); } #[test] - fn test_rule_url_without_package_name() { + fn test_document_url_without_package_name() { let cluster = build_test_cluster(); let opa_config = build_opa_config(None); assert_eq!( - opa_config.rule_url(&cluster, None), - format!("{}/{}/{}", OPA_API, CLUSTER_NAME, RULE_DEFAULT) + opa_config.document_url(&cluster, None, V1), + format!("{}/{}", V1.get_data_api(), CLUSTER_NAME) ); assert_eq!( - opa_config.rule_url(&cluster, Some(RULE_NAME)), - format!("{}/{}/{}", OPA_API, CLUSTER_NAME, RULE_NAME) + opa_config.document_url(&cluster, Some(RULE_NAME), V1), + format!("{}/{}/{}", V1.get_data_api(), CLUSTER_NAME, RULE_NAME) ); } #[test] - fn test_full_package_url() { + fn test_full_document_url() { let cluster = build_test_cluster(); let opa_config = build_opa_config(None); assert_eq!( - opa_config.full_package_url(&cluster, OPA_BASE_URL_WITH_SLASH), - format!("{}{}/{}", OPA_BASE_URL_WITH_SLASH, OPA_API, CLUSTER_NAME) - ); - - let opa_config = build_opa_config(Some(PACKAGE_NAME)); - - assert_eq!( - opa_config.full_package_url(&cluster, OPA_BASE_URL_WITHOUT_SLASH), + opa_config.full_document_url(&cluster, OPA_BASE_URL_WITH_SLASH, None, V1), format!( "{}/{}/{}", - OPA_BASE_URL_WITHOUT_SLASH, OPA_API, PACKAGE_NAME + OPA_BASE_URL_WITHOUT_SLASH, + V1.get_data_api(), + CLUSTER_NAME ) ); - } - - #[test] - fn test_full_rule_url() { - let cluster = build_test_cluster(); - let opa_config = build_opa_config(None); - assert_eq!( - opa_config.full_rule_url(&cluster, OPA_BASE_URL_WITHOUT_SLASH, None), - format!( - "{}/{}/{}/{}", - OPA_BASE_URL_WITHOUT_SLASH, OPA_API, CLUSTER_NAME, RULE_DEFAULT - ) - ); + let opa_config = build_opa_config(Some(PACKAGE_NAME)); assert_eq!( - opa_config.full_rule_url(&cluster, OPA_BASE_URL_WITHOUT_SLASH, Some(RULE_NAME)), + opa_config.full_document_url(&cluster, OPA_BASE_URL_WITHOUT_SLASH, None, V1), format!( - "{}/{}/{}/{}", - OPA_BASE_URL_WITHOUT_SLASH, OPA_API, CLUSTER_NAME, RULE_NAME + "{}/{}/{}", + OPA_BASE_URL_WITHOUT_SLASH, + V1.get_data_api(), + PACKAGE_NAME ) ); } From 387ea6415685fb5e80cf192f95a2338af1d1a7a9 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 17 Mar 2022 14:31:27 +0100 Subject: [PATCH 09/10] added comment for rule parameter --- src/opa.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/opa.rs b/src/opa.rs index b0f13ebb5..87c1ac2f9 100644 --- a/src/opa.rs +++ b/src/opa.rs @@ -77,6 +77,9 @@ impl OpaConfig { /// Returns the OPA data API url. If [`OpaConfig`] has no `package` set, /// will default to the cluster `resource` name. /// + /// The rule is optional and will be appended to the `` part if + /// provided as can be seen in the examples below. + /// /// This may be used if the OPA base url is contained in an ENV variable. /// /// # Example @@ -86,7 +89,7 @@ impl OpaConfig { /// /// # Arguments /// * `resource` - The cluster resource. - /// * `rule` - The rule name. Defaults to `allow`. + /// * `rule` - The rule name. Can be omitted. /// * `api_version` - The [`OpaApiVersion`] to extract the data API path. pub fn document_url( &self, @@ -115,6 +118,9 @@ impl OpaConfig { /// Returns the full qualified OPA data API url. If [`OpaConfig`] has no `package` set, /// will default to the cluster `resource` name. /// + /// The rule is optional and will be appended to the `` part if + /// provided as can be seen in the examples below. + /// /// # Example /// /// * `http://localhost:8081/v1/data/` @@ -123,7 +129,7 @@ impl OpaConfig { /// # Arguments /// * `resource` - The cluster resource /// * `opa_base_url` - The base url to OPA e.g. http://localhost:8081 - /// * `rule` - The rule name. + /// * `rule` - The rule name. Can be omitted. /// * `api_version` - The [`OpaApiVersion`] to extract the data API path. pub fn full_document_url( &self, @@ -153,6 +159,9 @@ impl OpaConfig { /// Returns the full qualified OPA data API url up to the package. If [`OpaConfig`] has /// no `package` set, will default to the cluster `resource` name. /// + /// The rule is optional and will be appended to the `` part if + /// provided as can be seen in the examples below. + /// /// In contrast to `full_document_url`, this extracts the OPA base url from the provided /// `config_map_name` in the [`OpaConfig`]. /// @@ -164,7 +173,7 @@ impl OpaConfig { /// # Arguments /// * `client` - The kubernetes client. /// * `resource` - The cluster resource. - /// * `rule` - The rule name. + /// * `rule` - The rule name. Can be omitted. /// * `api_version` - The [`OpaApiVersion`] to extract the data API path. pub async fn full_document_url_from_config_map( &self, From 3e89667711acd950262f123d511548774d978132 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Thu, 17 Mar 2022 14:33:13 +0100 Subject: [PATCH 10/10] changed api -> API in comments --- src/opa.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/opa.rs b/src/opa.rs index 87c1ac2f9..5a6add274 100644 --- a/src/opa.rs +++ b/src/opa.rs @@ -51,7 +51,7 @@ use kube::ResourceExt; use schemars::{self, JsonSchema}; use serde::{Deserialize, Serialize}; -/// Indicates the OPA api version. This is required to choose the correct +/// Indicates the OPA API version. This is required to choose the correct /// path when constructing the OPA urls to query. pub enum OpaApiVersion { V1,