From 6a3b80594d40d07b89bb26f62e64fd3f04c0dc09 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Thu, 30 Nov 2023 11:45:39 +0100 Subject: [PATCH 1/8] initial implementation --- src/crd.rs | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/crd.rs b/src/crd.rs index dbdb6cd7d..927d859bf 100644 --- a/src/crd.rs +++ b/src/crd.rs @@ -10,6 +10,9 @@ use std::fs::File; use std::io::Write; use std::path::Path; +const DOCS_HOME_URL_PLACEHOLDER: &str = "DOCS_BASE_URL_PLACEHOLDER"; +const DOCS_HOME_BASE_URL: &str = "https://docs.stackable.tech/home"; + /// A reference to a product cluster (for example, a `ZookeeperCluster`) /// /// `namespace`'s defaulting only applies when retrieved via [`ClusterRef::namespace_relative_from`] @@ -69,41 +72,66 @@ pub trait HasApplication { fn get_application_name() -> &'static str; } +/// Takes an operator version and returns a docs version +fn docs_version(operator_version: &str) -> String { + if operator_version == "0.0.0-dev" { + return "nightly".to_owned(); + } else { + operator_version + .split('.') + .take(2) + .collect::>() + .join(".") + } +} + +/// Given an operator version like 0.0.0-dev or 23.1.1, generate a docs home +/// component base URL like `https://docs.stackable.tech/home/nightly/` or +/// `https://docs.stackable.tech/home/23.1/`. +fn docs_home_versioned_base_url(operator_version: &str) -> String { + format!("{}/{}", DOCS_HOME_BASE_URL, docs_version(operator_version)) +} + /// This trait can be implemented to allow automatic handling /// (e.g. creation) of `CustomResourceDefinition`s in Kubernetes. pub trait CustomResourceExt: kube::CustomResourceExt { /// Generates a YAML CustomResourceDefinition and writes it to a `Write`. /// /// The generated YAML string is an explicit document with leading dashes (`---`). - fn generate_yaml_schema(mut writer: W) -> OperatorResult<()> + fn generate_yaml_schema(mut writer: W, operator_version: &str) -> OperatorResult<()> where W: Write, { - yaml::serialize_to_explicit_document(&mut writer, &Self::crd()) + let mut buffer = Vec::new(); + yaml::serialize_to_explicit_document(&mut buffer, &Self::crd())?; + let intermediate_crd = String::from_utf8(buffer).unwrap(); + let docs_home_url = docs_home_versioned_base_url(operator_version); + let replaced_crd = intermediate_crd.replace(DOCS_HOME_URL_PLACEHOLDER, &docs_home_url); + Ok(writer.write_all(replaced_crd.as_bytes())?) } /// Generates a YAML CustomResourceDefinition and writes it to the specified file. /// /// The written YAML string is an explicit document with leading dashes (`---`). - fn write_yaml_schema>(path: P) -> OperatorResult<()> { + fn write_yaml_schema>(path: P, operator_version: &str) -> OperatorResult<()> { let writer = File::create(path)?; - Self::generate_yaml_schema(writer) + Self::generate_yaml_schema(writer, operator_version) } /// Generates a YAML CustomResourceDefinition and prints it to stdout. /// /// The printed YAML string is an explicit document with leading dashes (`---`). - fn print_yaml_schema() -> OperatorResult<()> { + fn print_yaml_schema(operator_version: &str) -> OperatorResult<()> { let writer = std::io::stdout(); - Self::generate_yaml_schema(writer) + Self::generate_yaml_schema(writer, operator_version) } /// Returns the YAML schema of this CustomResourceDefinition as a string. /// /// The written YAML string is an explicit document with leading dashes (`---`). - fn yaml_schema() -> OperatorResult { + fn yaml_schema(operator_version: &str) -> OperatorResult { let mut writer = Vec::new(); - Self::generate_yaml_schema(&mut writer)?; + Self::generate_yaml_schema(&mut writer, operator_version)?; String::from_utf8(writer).map_err(Error::CrdFromUtf8Error) } } From db75e06b65f3294a984dccdb46e13504e7cded51 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Thu, 30 Nov 2023 11:50:32 +0100 Subject: [PATCH 2/8] adapted docs --- src/commons/affinity.rs | 2 +- src/commons/cluster_operation.rs | 2 +- src/commons/opa.rs | 6 +++--- src/commons/pdb.rs | 2 +- src/commons/product_image_selection.rs | 2 +- src/commons/s3.rs | 2 +- src/product_logging/spec.rs | 2 +- src/role_utils.rs | 16 ++++------------ 8 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/commons/affinity.rs b/src/commons/affinity.rs index 8848f7615..d3b1699ff 100644 --- a/src/commons/affinity.rs +++ b/src/commons/affinity.rs @@ -19,7 +19,7 @@ use crate::{ pub const TOPOLOGY_KEY_HOSTNAME: &str = "kubernetes.io/hostname"; /// These configuration settings control -/// [Pod placement](https://docs.stackable.tech/home/nightly/concepts/operations/pod_placement). +/// [Pod placement](DOCS_BASE_URL_PLACEHOLDER/concepts/operations/pod_placement). #[derive(Clone, Debug, Default, Deserialize, Fragment, JsonSchema, PartialEq, Serialize)] #[fragment(path_overrides(fragment = "crate::config::fragment"))] #[fragment_attrs( diff --git a/src/commons/cluster_operation.rs b/src/commons/cluster_operation.rs index 33d626cfb..a3731401a 100644 --- a/src/commons/cluster_operation.rs +++ b/src/commons/cluster_operation.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// [Cluster operations](https://docs.stackable.tech/home/nightly/concepts/operations/cluster_operations) +/// [Cluster operations](DOCS_BASE_URL_PLACEHOLDER/concepts/operations/cluster_operations) /// properties, allow stopping the product instance as well as pausing reconciliation. #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/src/commons/opa.rs b/src/commons/opa.rs index 65a4ae749..81f45f3d1 100644 --- a/src/commons/opa.rs +++ b/src/commons/opa.rs @@ -73,14 +73,14 @@ impl OpaApiVersion { } } -/// Configure the OPA stacklet [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) +/// Configure the OPA stacklet [discovery ConfigMap](DOCS_BASE_URL_PLACEHOLDER/concepts/service_discovery) /// and the name of the Rego package containing your authorization rules. -/// Consult the [OPA authorization documentation](https://docs.stackable.tech/home/nightly/concepts/opa) +/// Consult the [OPA authorization documentation](DOCS_BASE_URL_PLACEHOLDER/concepts/opa) /// to learn how to deploy Rego authorization rules with OPA. #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct OpaConfig { - /// The [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) + /// The [discovery ConfigMap](DOCS_BASE_URL_PLACEHOLDER/concepts/service_discovery) /// for the OPA stacklet that should be used for authorization requests. pub config_map_name: String, /// The name of the Rego package containing the Rego rules for the product. diff --git a/src/commons/pdb.rs b/src/commons/pdb.rs index 27cecf689..80f2ae0be 100644 --- a/src/commons/pdb.rs +++ b/src/commons/pdb.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; /// 2. The allowed number of Pods to be unavailable (`maxUnavailable`) /// /// Learn more in the -/// [allowed Pod disruptions documentation](https://docs.stackable.tech/home/nightly/concepts/operations/pod_disruptions). +/// [allowed Pod disruptions documentation](DOCS_BASE_URL_PLACEHOLDER/concepts/operations/pod_disruptions). #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct PdbConfig { diff --git a/src/commons/product_image_selection.rs b/src/commons/product_image_selection.rs index 879752875..870b746cf 100644 --- a/src/commons/product_image_selection.rs +++ b/src/commons/product_image_selection.rs @@ -13,7 +13,7 @@ pub const STACKABLE_DOCKER_REPO: &str = "docker.stackable.tech/stackable"; /// You can also configure a custom image registry to pull from, as well as completely custom /// images. /// -/// Consult the [Product image selection documentation](https://docs.stackable.tech/home/nightly/concepts/product_image_selection) +/// Consult the [Product image selection documentation](DOCS_BASE_URL_PLACEHOLDER/concepts/product_image_selection) /// for details. #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/src/commons/s3.rs b/src/commons/s3.rs index d9a20516f..900cd8d75 100644 --- a/src/commons/s3.rs +++ b/src/commons/s3.rs @@ -90,7 +90,7 @@ impl InlinedS3BucketSpec { /// An S3 bucket definition, it can either be a reference to an explicit S3Bucket object, /// or it can be an inline defintion of a bucket. Read the -/// [S3 resources concept documentation](https://docs.stackable.tech/home/nightly/concepts/s3) +/// [S3 resources concept documentation](DOCS_BASE_URL_PLACEHOLDER/concepts/s3) /// to learn more. #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/src/product_logging/spec.rs b/src/product_logging/spec.rs index 4026a4e93..071e31d9a 100644 --- a/src/product_logging/spec.rs +++ b/src/product_logging/spec.rs @@ -68,7 +68,7 @@ use serde::{Deserialize, Serialize}; rename_all = "camelCase", ), // We don't want Rust code in public API descriptions - schemars(description = "Logging configuration, learn more in the [logging concept documentation](https://docs.stackable.tech/home/nightly/concepts/logging).") + schemars(description = "Logging configuration, learn more in the [logging concept documentation](DOCS_BASE_URL_PLACEHOLDER/concepts/logging).") )] pub struct Logging where diff --git a/src/role_utils.rs b/src/role_utils.rs index 55179cb22..a096a5912 100644 --- a/src/role_utils.rs +++ b/src/role_utils.rs @@ -114,16 +114,8 @@ pub struct CommonConfiguration { #[schemars(default = "config_schema_default")] pub config: T, /// The `configOverrides` can be used to configure properties in product config files - /// that are not exposed in the CRD. For example, for a HdfsCluster: - /// - /// ```yaml - /// configOverrides: # on role level - /// core-site.xml: # the name of the configuration file - /// fs.trash.interval: "5" # the name of the property and the value to set - /// ``` - /// - /// Read the - /// [config overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#config-overrides) + /// that are not exposed in the CRD. Read the + /// [config overrides documentation](DOCS_BASE_URL_PLACEHOLDER/concepts/overrides#config-overrides) /// and consult the operator specific usage guide documentation for details on the /// available config files and settings for the specific product. #[serde(default)] @@ -131,7 +123,7 @@ pub struct CommonConfiguration { /// `envOverrides` configure environment variables to be set in the Pods. /// It is a map from strings to strings - environment variables and the value to set. /// Read the - /// [environment variable overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#env-overrides) + /// [environment variable overrides documentation](DOCS_BASE_URL_PLACEHOLDER/concepts/overrides#env-overrides) /// for more information and consult the operator specific usage guide to find out about /// the product specific environment variables that are available. #[serde(default)] @@ -144,7 +136,7 @@ pub struct CommonConfiguration { /// [PodTemplateSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#podtemplatespec-v1-core) /// to override any property that can be set on a Kubernetes Pod. /// Read the - /// [Pod overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#pod-overrides) + /// [Pod overrides documentation](DOCS_BASE_URL_PLACEHOLDER/concepts/overrides#pod-overrides) /// for more information. #[serde(default)] #[schemars(schema_with = "pod_overrides_schema")] From 75f57686ecbd6215aa6868e84d05903b6b1cb2e9 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Thu, 30 Nov 2023 11:58:14 +0100 Subject: [PATCH 3/8] Added changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83f8346f8..0517280e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- BREAKING: The `CustomResourceExt` functions now take the Operator version as an argument. + It replaces `DOCS_BASE_URL_PLACEHOLDER` in doc strings with a link to URL base, so + `DOCS_BASE_URL_PLACEHOLDER/druid/` turns into `https://docs.stackable.tech/home/nightly/druid/` + in the nightly operator ([#689]). + +[#689]: https://github.com/stackabletech/operator-rs/pull/689 + ## [0.56.2] - 2023-11-23 ### Added From 9877412ae8e29d6dbd7829876ff41092e2d5e849 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Thu, 30 Nov 2023 12:00:58 +0100 Subject: [PATCH 4/8] Fixed lint; added comment --- src/crd.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/crd.rs b/src/crd.rs index 927d859bf..aad9b1aed 100644 --- a/src/crd.rs +++ b/src/crd.rs @@ -75,8 +75,9 @@ pub trait HasApplication { /// Takes an operator version and returns a docs version fn docs_version(operator_version: &str) -> String { if operator_version == "0.0.0-dev" { - return "nightly".to_owned(); + "nightly".to_owned() } else { + // Take the major.minor component of the semantic version operator_version .split('.') .take(2) From 19887634b9be1f5ff05332a5f78fe917b7ebc1fc Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Thu, 30 Nov 2023 12:09:01 +0100 Subject: [PATCH 5/8] Fixed doc-test --- src/cli.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 375909c98..127fb5aa9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -17,6 +17,8 @@ //! use stackable_operator::{CustomResourceExt, cli}; //! use stackable_operator::error::OperatorResult; //! +//! const OPERATOR_VERSION: &str = "23.1.1"; +//! //! #[derive(Clone, CustomResource, Debug, JsonSchema, Serialize, Deserialize)] //! #[kube( //! group = "foo.stackable.tech", @@ -56,8 +58,8 @@ //! //! match opts.command { //! cli::Command::Crd => { -//! FooCluster::print_yaml_schema()?; -//! BarCluster::print_yaml_schema()?; +//! FooCluster::print_yaml_schema(OPERATOR_VERSION)?; +//! BarCluster::print_yaml_schema(OPERATOR_VERSION)?; //! }, //! cli::Command::Run { .. } => { //! // Run the operator From c2e51470e67021fc567ff3728e90fbc71be4293f Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Thu, 30 Nov 2023 12:52:23 +0100 Subject: [PATCH 6/8] Update src/crd.rs Co-authored-by: Sebastian Bernauer --- src/crd.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/crd.rs b/src/crd.rs index aad9b1aed..e7200e8a2 100644 --- a/src/crd.rs +++ b/src/crd.rs @@ -105,10 +105,15 @@ pub trait CustomResourceExt: kube::CustomResourceExt { { let mut buffer = Vec::new(); yaml::serialize_to_explicit_document(&mut buffer, &Self::crd())?; - let intermediate_crd = String::from_utf8(buffer).unwrap(); - let docs_home_url = docs_home_versioned_base_url(operator_version); - let replaced_crd = intermediate_crd.replace(DOCS_HOME_URL_PLACEHOLDER, &docs_home_url); - Ok(writer.write_all(replaced_crd.as_bytes())?) + + let yaml_schema = String::from_utf8(buffer) + .map_err(Error::CrdFromUtf8Error)? + .replace( + DOCS_HOME_URL_PLACEHOLDER, + &docs_home_versioned_base_url(operator_version), + ); + + Ok(writer.write_all(yaml_schema.as_bytes())?) } /// Generates a YAML CustomResourceDefinition and writes it to the specified file. From 668a2a8bd19b71d5e3115ace6fc7ea7fee5e1676 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Thu, 30 Nov 2023 13:18:50 +0100 Subject: [PATCH 7/8] use semver --- Cargo.toml | 1 + src/crd.rs | 22 +++++++++++----------- src/error.rs | 6 ++++++ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index abd652f9b..f789559e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ product-config = { git = "https://github.com/stackabletech/product-config.git", rand = "0.8.5" regex = "1.9.3" schemars = "0.8.12" +semver = "1.0" serde = { version = "1.0.184", features = ["derive"] } serde_json = "1.0.104" serde_yaml = "0.9.25" diff --git a/src/crd.rs b/src/crd.rs index e7200e8a2..9192e3617 100644 --- a/src/crd.rs +++ b/src/crd.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use derivative::Derivative; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use semver::Version; use crate::error::{Error, OperatorResult}; use crate::yaml; @@ -73,24 +74,23 @@ pub trait HasApplication { } /// Takes an operator version and returns a docs version -fn docs_version(operator_version: &str) -> String { +fn docs_version(operator_version: &str) -> OperatorResult { if operator_version == "0.0.0-dev" { - "nightly".to_owned() + Ok("nightly".to_owned()) } else { - // Take the major.minor component of the semantic version - operator_version - .split('.') - .take(2) - .collect::>() - .join(".") + let v = Version::parse(operator_version).map_err(|err| Error::InvalidSemverVersion { + source: err, + version: operator_version.to_owned(), + })?; + Ok(format!("{}.{}", v.major, v.minor)) } } /// Given an operator version like 0.0.0-dev or 23.1.1, generate a docs home /// component base URL like `https://docs.stackable.tech/home/nightly/` or /// `https://docs.stackable.tech/home/23.1/`. -fn docs_home_versioned_base_url(operator_version: &str) -> String { - format!("{}/{}", DOCS_HOME_BASE_URL, docs_version(operator_version)) +fn docs_home_versioned_base_url(operator_version: &str) -> OperatorResult { + Ok(format!("{}/{}", DOCS_HOME_BASE_URL, docs_version(operator_version)?)) } /// This trait can be implemented to allow automatic handling @@ -110,7 +110,7 @@ pub trait CustomResourceExt: kube::CustomResourceExt { .map_err(Error::CrdFromUtf8Error)? .replace( DOCS_HOME_URL_PLACEHOLDER, - &docs_home_versioned_base_url(operator_version), + &docs_home_versioned_base_url(operator_version)?, ); Ok(writer.write_all(yaml_schema.as_bytes())?) diff --git a/src/error.rs b/src/error.rs index de802a3b3..3067bdf93 100644 --- a/src/error.rs +++ b/src/error.rs @@ -111,6 +111,12 @@ pub enum Error { container_name: String, violation: String, }, + + #[error("Cannot parse version [{version}] as a semantic version.")] + InvalidSemverVersion { + source: semver::Error, + version: String, + } } pub type OperatorResult = std::result::Result; From 07fb5a41d6cbf6e764e5985fa1e4e66645ea68d5 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Thu, 30 Nov 2023 13:20:46 +0100 Subject: [PATCH 8/8] cargo fmt --- src/crd.rs | 8 ++++++-- src/error.rs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/crd.rs b/src/crd.rs index 9192e3617..b2638c51e 100644 --- a/src/crd.rs +++ b/src/crd.rs @@ -2,8 +2,8 @@ use std::marker::PhantomData; use derivative::Derivative; use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use semver::Version; +use serde::{Deserialize, Serialize}; use crate::error::{Error, OperatorResult}; use crate::yaml; @@ -90,7 +90,11 @@ fn docs_version(operator_version: &str) -> OperatorResult { /// component base URL like `https://docs.stackable.tech/home/nightly/` or /// `https://docs.stackable.tech/home/23.1/`. fn docs_home_versioned_base_url(operator_version: &str) -> OperatorResult { - Ok(format!("{}/{}", DOCS_HOME_BASE_URL, docs_version(operator_version)?)) + Ok(format!( + "{}/{}", + DOCS_HOME_BASE_URL, + docs_version(operator_version)? + )) } /// This trait can be implemented to allow automatic handling diff --git a/src/error.rs b/src/error.rs index 3067bdf93..1496b2797 100644 --- a/src/error.rs +++ b/src/error.rs @@ -116,7 +116,7 @@ pub enum Error { InvalidSemverVersion { source: semver::Error, version: String, - } + }, } pub type OperatorResult = std::result::Result;