From 2da90a9668355797065e63e7ee3af2fa7bccb22e Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 7 Oct 2024 16:52:57 +0200 Subject: [PATCH 1/5] feat: Add stackable-shared crate This crate initially contains code to be able to use our custom YAML serialization across workspace members. Previously, to get access to the specialized helper functions and traits one needed to import the complete stackable-operator crate. That's why this commit moves that piece of code into a shared place, which is way lighter to be imported by other workspace members. It additionally reworks the helpers to be slightly more generic. It still contains the docs URL replacer, which replaces the placeholder with the correct Stackable doc URL in doc comments and thus in OpenAPI schema descriptions. --- CHANGELOG.md | 1 + crates/stackable-shared/CHANGELOG.md | 5 + crates/stackable-shared/Cargo.toml | 17 +++ crates/stackable-shared/src/lib.rs | 4 + crates/stackable-shared/src/yaml/mod.rs | 186 ++++++++++++++++++++++++ 5 files changed, 213 insertions(+) create mode 100644 crates/stackable-shared/CHANGELOG.md create mode 100644 crates/stackable-shared/Cargo.toml create mode 100644 crates/stackable-shared/src/lib.rs create mode 100644 crates/stackable-shared/src/yaml/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 96a6709b5..96c05352b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Please see the relevant crate changelogs: - [stackable-certs](./crates/stackable-certs/CHANGELOG.md) - [stackable-operator](./crates/stackable-operator/CHANGELOG.md) - [stackable-operator-derive](./crates/stackable-operator-derive/CHANGELOG.md) +- [stackable-shared](./crates/stackable-shared/CHANGELOG.md) - [stackable-telemetry](./crates/stackable-telemetry/CHANGELOG.md) - [stackable-versioned](./crates/stackable-versioned/CHANGELOG.md) - [stackable-webhook](./crates/stackable-webhook/CHANGELOG.md) diff --git a/crates/stackable-shared/CHANGELOG.md b/crates/stackable-shared/CHANGELOG.md new file mode 100644 index 000000000..7a1e21ce4 --- /dev/null +++ b/crates/stackable-shared/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.0.1] diff --git a/crates/stackable-shared/Cargo.toml b/crates/stackable-shared/Cargo.toml new file mode 100644 index 000000000..906b4ec72 --- /dev/null +++ b/crates/stackable-shared/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "stackable-shared" +version = "0.0.1" +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true + +[dependencies] +kube.workspace = true +semver.workspace = true +serde.workspace = true +serde_yaml.workspace = true +snafu.workspace = true + +[dev-dependencies] +k8s-openapi.workspace = true diff --git a/crates/stackable-shared/src/lib.rs b/crates/stackable-shared/src/lib.rs new file mode 100644 index 000000000..6d04d57f5 --- /dev/null +++ b/crates/stackable-shared/src/lib.rs @@ -0,0 +1,4 @@ +//! This crate contains various shared helpers and utilities used across other crates in this +//! workspace. + +pub mod yaml; diff --git a/crates/stackable-shared/src/yaml/mod.rs b/crates/stackable-shared/src/yaml/mod.rs new file mode 100644 index 000000000..35733d5b0 --- /dev/null +++ b/crates/stackable-shared/src/yaml/mod.rs @@ -0,0 +1,186 @@ +//! Utility functions for processing data in the YAML file format +use std::{io::Write, path::Path, str::FromStr}; + +use semver::Version; +use snafu::{ResultExt, Snafu}; + +const STACKABLE_DOCS_HOME_URL_PLACEHOLDER: &str = "DOCS_BASE_URL_PLACEHOLDER"; +const STACKABLE_DOCS_HOME_BASE_URL: &str = "https://docs.stackable.tech/home"; + +type Result = std::result::Result; + +/// Represents every error which can be encountered during YAML serialization. +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("failed to serialize YAML"))] + SerializeYaml { source: serde_yaml::Error }, + + #[snafu(display("failed to write YAML document separator"))] + WriteDocumentSeparator { source: std::io::Error }, + + #[snafu(display("failed to write YAML to file"))] + WriteToFile { source: std::io::Error }, + + #[snafu(display("failed to write YAML to stdout"))] + WriteToStdout { source: std::io::Error }, + + #[snafu(display("failed to parse {input:?} as semantic version"))] + ParseSemanticVersion { + source: semver::Error, + input: String, + }, + + #[snafu(display("failed to parse bytes as valid UTF-8 string"))] + ParseUtf8Bytes { source: std::string::FromUtf8Error }, +} + +pub(crate) struct DocUrlReplacer<'a>(&'a str); + +impl<'a> DocUrlReplacer<'a> { + pub(crate) fn new(operator_version: &'a str) -> Self { + Self(operator_version) + } + + fn replace(&self, input: &str) -> Result { + let docs_version = match self.0 { + "0.0.0-dev" => "nightly".to_owned(), + ver => { + let v = Version::from_str(ver).context(ParseSemanticVersionSnafu { input })?; + format!("{major}.{minor}", major = v.major, minor = v.minor) + } + }; + + Ok(input.replace( + STACKABLE_DOCS_HOME_URL_PLACEHOLDER, + &format!("{STACKABLE_DOCS_HOME_BASE_URL}/{docs_version}"), + )) + } +} + +/// Provides configurable options during YAML serialization. +/// +/// For most people the default implementation [`SerializeOptions::default()`] is sufficient as it +/// enables explicit document and singleton map serialization. +pub struct SerializeOptions { + /// Adds leading triple dashes (`---`) to the output string. + pub explicit_document: bool, + + /// Serialize enum variants as YAML maps using the variant name as the key. + pub singleton_map: bool, +} + +impl Default for SerializeOptions { + fn default() -> Self { + Self { + explicit_document: true, + singleton_map: true, + } + } +} + +/// Serializes any type `T` which is [serializable](serde::Serialize) as YAML using the provided +/// [`SerializeOptions`]. +/// +/// It additionally replaces the documentation URL placeholder with the correct value based on the +/// provided `operator_version`. +pub trait YamlSchema: Sized + serde::Serialize { + /// Generates the YAML schema of `self` using the provided [`SerializeOptions`]. + fn generate_yaml_schema( + &self, + operator_version: &str, + options: SerializeOptions, + ) -> Result { + let mut buffer = Vec::new(); + + serialize(&self, &mut buffer, options)?; + + let yaml_string = String::from_utf8(buffer).context(ParseUtf8BytesSnafu)?; + + let replacer = DocUrlReplacer::new(operator_version); + let yaml_string = replacer.replace(&yaml_string)?; + + Ok(yaml_string) + } + + /// Generates and write the YAML schema of `self` to a file at `path` using the provided + /// [`SerializeOptions`]. + fn write_yaml_schema>( + &self, + path: P, + operator_version: &str, + options: SerializeOptions, + ) -> Result<()> { + let schema = self.generate_yaml_schema(operator_version, options)?; + std::fs::write(path, schema).context(WriteToFileSnafu) + } + + /// Generates and prints the YAML schema of `self` to stdout at `path` using the provided + /// [`SerializeOptions`]. + fn print_yaml_schema(&self, operator_version: &str, options: SerializeOptions) -> Result<()> { + let schema = self.generate_yaml_schema(operator_version, options)?; + + let mut writer = std::io::stdout(); + writer + .write_all(schema.as_bytes()) + .context(WriteToStdoutSnafu) + } +} + +impl YamlSchema for T where T: serde::ser::Serialize {} + +/// Provides YAML schema generation and output capabilities for Kubernetes custom resources. +pub trait CustomResourceExt: kube::CustomResourceExt { + /// Generates the YAML schema of a `CustomResourceDefinition` and writes it to the specified + /// file at `path`. + /// + /// It additionally replaces the documentation URL placeholder with the correct value based on + /// the provided `operator_version`. The written YAML string is an explicit document with + /// leading dashes (`---`). + fn write_yaml_schema>(path: P, operator_version: &str) -> Result<()> { + Self::crd().write_yaml_schema(path, operator_version, SerializeOptions::default()) + } + + /// Generates the YAML schema of a `CustomResourceDefinition` and prints it to [stdout]. + /// + /// It additionally replaces the documentation URL placeholder with the correct value based on + /// the provided `operator_version`. The written YAML string is an explicit document with + /// leading dashes (`---`). + /// + /// [stdout]: std::io::stdout + fn print_yaml_schema(operator_version: &str) -> Result<()> { + Self::crd().print_yaml_schema(operator_version, SerializeOptions::default()) + } + + /// Generates the YAML schema of a `CustomResourceDefinition` and returns it as a [`String`]. + fn yaml_schema(operator_version: &str) -> Result { + Self::crd().generate_yaml_schema(operator_version, SerializeOptions::default()) + } +} + +impl CustomResourceExt for T where T: kube::CustomResourceExt {} + +/// Serializes the given data structure and writes it to a [`Writer`](Write). +pub fn serialize(value: &T, mut writer: W, options: SerializeOptions) -> Result<()> +where + T: serde::Serialize, + W: std::io::Write, +{ + if options.explicit_document { + writer + .write_all(b"---\n") + .context(WriteDocumentSeparatorSnafu)?; + } + + let mut serializer = serde_yaml::Serializer::new(writer); + + if options.singleton_map { + serde_yaml::with::singleton_map_recursive::serialize(value, &mut serializer) + .context(SerializeYamlSnafu)?; + } else { + value + .serialize(&mut serializer) + .context(SerializeYamlSnafu)?; + } + + Ok(()) +} From 209a7cc7ea0460f06ae95440e01ddaadf43b403b Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 7 Oct 2024 16:59:02 +0200 Subject: [PATCH 2/5] chore: Use shared YAML code --- Cargo.lock | 13 +++ crates/stackable-operator/Cargo.toml | 3 +- crates/stackable-operator/src/crd.rs | 110 ---------------------- crates/stackable-operator/src/helm/mod.rs | 4 +- crates/stackable-operator/src/lib.rs | 5 +- crates/stackable-operator/src/yaml.rs | 72 -------------- 6 files changed, 20 insertions(+), 187 deletions(-) delete mode 100644 crates/stackable-operator/src/yaml.rs diff --git a/Cargo.lock b/Cargo.lock index 3612a8a51..e2ccf7531 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3028,6 +3028,7 @@ dependencies = [ "serde_yaml", "snafu 0.8.4", "stackable-operator-derive", + "stackable-shared", "strum", "tempfile", "time", @@ -3050,6 +3051,18 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "stackable-shared" +version = "0.0.1" +dependencies = [ + "k8s-openapi", + "kube", + "semver", + "serde", + "serde_yaml", + "snafu 0.8.4", +] + [[package]] name = "stackable-telemetry" version = "0.2.0" diff --git a/crates/stackable-operator/Cargo.toml b/crates/stackable-operator/Cargo.toml index b98460395..0196fb8b6 100644 --- a/crates/stackable-operator/Cargo.toml +++ b/crates/stackable-operator/Cargo.toml @@ -11,7 +11,8 @@ repository.workspace = true time = ["dep:time"] [dependencies] -stackable-operator-derive = { path = "../stackable-operator-derive" } +stackable-operator-derive = { path = "../stackable-operator-derive", version = "0.3.1" } +stackable-shared = { path = "../stackable-shared", version = "0.0.1" } chrono.workspace = true clap.workspace = true diff --git a/crates/stackable-operator/src/crd.rs b/crates/stackable-operator/src/crd.rs index d0610a478..666e84ef6 100644 --- a/crates/stackable-operator/src/crd.rs +++ b/crates/stackable-operator/src/crd.rs @@ -2,40 +2,7 @@ use std::marker::PhantomData; use derivative::Derivative; use schemars::JsonSchema; -use semver::Version; use serde::{Deserialize, Serialize}; -use snafu::{ResultExt, Snafu}; - -use crate::yaml; -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"; - -type Result = std::result::Result; - -#[derive(Debug, Snafu)] -pub enum Error { - #[snafu(display("cannot parse version {version:?} as a semantic version"))] - InvalidSemverVersion { - source: semver::Error, - version: String, - }, - - #[snafu(display("error converting CRD byte array to UTF-8"))] - ConvertByteArrayToUtf8 { source: std::string::FromUtf8Error }, - - #[snafu(display("failed to serialize YAML"))] - SerializeYaml { source: yaml::Error }, - - #[snafu(display("failed to write YAML"))] - WriteYamlSchema { source: std::io::Error }, - - #[snafu(display("failed to create YAML file"))] - CreateYamlFile { source: std::io::Error }, -} /// A reference to a product cluster (for example, a `ZookeeperCluster`) /// @@ -96,83 +63,6 @@ pub trait HasApplication { fn get_application_name() -> &'static str; } -/// Takes an operator version and returns a docs version -fn docs_version(operator_version: &str) -> Result { - if operator_version == "0.0.0-dev" { - Ok("nightly".to_owned()) - } else { - let v = Version::parse(operator_version).context(InvalidSemverVersionSnafu { - 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) -> Result { - Ok(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, operator_version: &str) -> Result<()> - where - W: Write, - { - let mut buffer = Vec::new(); - yaml::serialize_to_explicit_document(&mut buffer, &Self::crd()) - .context(SerializeYamlSnafu)?; - - let yaml_schema = String::from_utf8(buffer) - .context(ConvertByteArrayToUtf8Snafu)? - .replace( - DOCS_HOME_URL_PLACEHOLDER, - &docs_home_versioned_base_url(operator_version)?, - ); - - writer - .write_all(yaml_schema.as_bytes()) - .context(WriteYamlSchemaSnafu) - } - - /// 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, operator_version: &str) -> Result<()> { - let writer = File::create(path).context(CreateYamlFileSnafu)?; - 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(operator_version: &str) -> Result<()> { - let writer = std::io::stdout(); - 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(operator_version: &str) -> Result { - let mut writer = Vec::new(); - Self::generate_yaml_schema(&mut writer, operator_version)?; - String::from_utf8(writer).context(ConvertByteArrayToUtf8Snafu) - } -} - -impl CustomResourceExt for T where T: kube::CustomResourceExt {} - #[cfg(test)] mod tests { use k8s_openapi::api::core::v1::ConfigMap; diff --git a/crates/stackable-operator/src/helm/mod.rs b/crates/stackable-operator/src/helm/mod.rs index f5bf85c0b..f987c8c71 100644 --- a/crates/stackable-operator/src/helm/mod.rs +++ b/crates/stackable-operator/src/helm/mod.rs @@ -79,7 +79,7 @@ mod tests { use rstest::rstest; - use crate::yaml::serialize_to_explicit_document; + use stackable_shared::yaml::{serialize as ser, SerializeOptions}; use super::*; @@ -102,7 +102,7 @@ mod tests { let expected = std::fs::read_to_string("fixtures/helm/output.yaml").unwrap(); let mut output = Vec::new(); - serialize_to_explicit_document(&mut output, &values).unwrap(); + ser(&values, &mut output, SerializeOptions::default()).unwrap(); assert_eq!(std::str::from_utf8(&output).unwrap(), expected); } diff --git a/crates/stackable-operator/src/lib.rs b/crates/stackable-operator/src/lib.rs index 6c748bb6e..3bd87243d 100644 --- a/crates/stackable-operator/src/lib.rs +++ b/crates/stackable-operator/src/lib.rs @@ -20,10 +20,11 @@ pub mod status; pub mod time; pub mod utils; pub mod validation; -pub mod yaml; -pub use crate::crd::CustomResourceExt; +// Internal re-exports +pub use stackable_shared::yaml::CustomResourceExt; +// External re-exports pub use ::k8s_openapi; pub use ::kube; pub use ::schemars; diff --git a/crates/stackable-operator/src/yaml.rs b/crates/stackable-operator/src/yaml.rs deleted file mode 100644 index bea00be7b..000000000 --- a/crates/stackable-operator/src/yaml.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Utility functions for processing data in the YAML file format -use std::io::Write; - -use serde::ser; -use snafu::{ResultExt, Snafu}; - -type Result = std::result::Result; - -#[derive(Debug, Snafu)] -pub enum Error { - #[snafu(display("failed to serialize YAML"))] - SerializeYaml { source: serde_yaml::Error }, - - #[snafu(display("failed to write YAML document separator"))] - WriteDocumentSeparator { source: std::io::Error }, -} - -/// Serializes the given data structure as an explicit YAML document and writes it to a [`Write`]. -/// -/// Enums are serialized as a YAML map containing one entry in which the key identifies the variant -/// name. -/// -/// # Example -/// -/// ``` -/// use serde::Serialize; -/// use stackable_operator::yaml; -/// -/// #[derive(Serialize)] -/// #[serde(rename_all = "camelCase")] -/// enum Connection { -/// Inline(String), -/// Reference(String), -/// } -/// -/// #[derive(Serialize)] -/// struct Spec { -/// connection: Connection, -/// } -/// -/// let value = Spec { -/// connection: Connection::Inline("http://localhost".into()), -/// }; -/// -/// let mut buf = Vec::new(); -/// yaml::serialize_to_explicit_document(&mut buf, &value).unwrap(); -/// let actual_yaml = std::str::from_utf8(&buf).unwrap(); -/// -/// let expected_yaml = "--- -/// connection: -/// inline: http://localhost -/// "; -/// -/// assert_eq!(expected_yaml, actual_yaml); -/// ``` -/// -/// # Errors -/// -/// Serialization can fail if `T`'s implementation of `Serialize` decides to return an error. -pub fn serialize_to_explicit_document(mut writer: W, value: &T) -> Result<()> -where - T: ser::Serialize, - W: Write, -{ - writer - .write_all(b"---\n") - .context(WriteDocumentSeparatorSnafu)?; - let mut serializer = serde_yaml::Serializer::new(writer); - serde_yaml::with::singleton_map_recursive::serialize(value, &mut serializer) - .context(SerializeYamlSnafu)?; - Ok(()) -} From 1b35add0c69d0a18dfc65c170af6e798d247b18a Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 8 Oct 2024 08:42:08 +0200 Subject: [PATCH 3/5] chore: Split CRD and YAML specific code --- crates/stackable-operator/src/cli.rs | 13 +++-- crates/stackable-operator/src/lib.rs | 6 +- crates/stackable-shared/src/crd.rs | 56 +++++++++++++++++++ crates/stackable-shared/src/lib.rs | 1 + .../src/{yaml/mod.rs => yaml.rs} | 31 ---------- 5 files changed, 69 insertions(+), 38 deletions(-) create mode 100644 crates/stackable-shared/src/crd.rs rename crates/stackable-shared/src/{yaml/mod.rs => yaml.rs} (76%) diff --git a/crates/stackable-operator/src/cli.rs b/crates/stackable-operator/src/cli.rs index 10bdc18fc..022502564 100644 --- a/crates/stackable-operator/src/cli.rs +++ b/crates/stackable-operator/src/cli.rs @@ -14,7 +14,7 @@ //! use kube::CustomResource; //! use schemars::JsonSchema; //! use serde::{Deserialize, Serialize}; -//! use stackable_operator::{CustomResourceExt, cli, crd}; +//! use stackable_operator::{CustomResourceExt, cli, shared::crd}; //! //! const OPERATOR_VERSION: &str = "23.1.1"; //! @@ -106,16 +106,17 @@ //! ``` //! //! -use crate::logging::TracingTarget; -use crate::namespace::WatchNamespace; -use clap::Args; -use product_config::ProductConfigManager; -use snafu::{ResultExt, Snafu}; use std::{ ffi::OsStr, path::{Path, PathBuf}, }; +use clap::Args; +use product_config::ProductConfigManager; +use snafu::{ResultExt, Snafu}; + +use crate::{logging::TracingTarget, namespace::WatchNamespace}; + pub const AUTHOR: &str = "Stackable GmbH - info@stackable.tech"; type Result = std::result::Result; diff --git a/crates/stackable-operator/src/lib.rs b/crates/stackable-operator/src/lib.rs index 3bd87243d..50567fb88 100644 --- a/crates/stackable-operator/src/lib.rs +++ b/crates/stackable-operator/src/lib.rs @@ -22,7 +22,11 @@ pub mod utils; pub mod validation; // Internal re-exports -pub use stackable_shared::yaml::CustomResourceExt; +pub use stackable_shared::{crd::CustomResourceExt, yaml::YamlSchema}; + +pub mod shared { + pub use stackable_shared::*; +} // External re-exports pub use ::k8s_openapi; diff --git a/crates/stackable-shared/src/crd.rs b/crates/stackable-shared/src/crd.rs new file mode 100644 index 000000000..4e40b38da --- /dev/null +++ b/crates/stackable-shared/src/crd.rs @@ -0,0 +1,56 @@ +use std::path::Path; + +use snafu::{ResultExt, Snafu}; + +use crate::yaml::{SerializeOptions, YamlSchema}; + +pub type Result = std::result::Result; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("failed to write CRD YAML schema to file"))] + WriteToFile { source: crate::yaml::Error }, + + #[snafu(display("failed to write CRD YAML schema to stdout"))] + WriteToStdout { source: crate::yaml::Error }, + + #[snafu(display("failed to generate CRD YAML schema"))] + GenerateSchema { source: crate::yaml::Error }, +} + +/// Provides YAML schema generation and output capabilities for Kubernetes custom resources. +pub trait CustomResourceExt: kube::CustomResourceExt { + /// Generates the YAML schema of a `CustomResourceDefinition` and writes it to the specified + /// file at `path`. + /// + /// It additionally replaces the documentation URL placeholder with the correct value based on + /// the provided `operator_version`. The written YAML string is an explicit document with + /// leading dashes (`---`). + fn write_yaml_schema>(path: P, operator_version: &str) -> Result<()> { + Self::crd() + .write_yaml_schema(path, operator_version, SerializeOptions::default()) + .context(WriteToFileSnafu) + } + + /// Generates the YAML schema of a `CustomResourceDefinition` and prints it to [stdout]. + /// + /// It additionally replaces the documentation URL placeholder with the correct value based on + /// the provided `operator_version`. The written YAML string is an explicit document with + /// leading dashes (`---`). + /// + /// [stdout]: std::io::stdout + fn print_yaml_schema(operator_version: &str) -> Result<()> { + Self::crd() + .print_yaml_schema(operator_version, SerializeOptions::default()) + .context(WriteToStdoutSnafu) + } + + /// Generates the YAML schema of a `CustomResourceDefinition` and returns it as a [`String`]. + fn yaml_schema(operator_version: &str) -> Result { + Self::crd() + .generate_yaml_schema(operator_version, SerializeOptions::default()) + .context(GenerateSchemaSnafu) + } +} + +impl CustomResourceExt for T where T: kube::CustomResourceExt {} diff --git a/crates/stackable-shared/src/lib.rs b/crates/stackable-shared/src/lib.rs index 6d04d57f5..ea8e41a91 100644 --- a/crates/stackable-shared/src/lib.rs +++ b/crates/stackable-shared/src/lib.rs @@ -1,4 +1,5 @@ //! This crate contains various shared helpers and utilities used across other crates in this //! workspace. +pub mod crd; pub mod yaml; diff --git a/crates/stackable-shared/src/yaml/mod.rs b/crates/stackable-shared/src/yaml.rs similarity index 76% rename from crates/stackable-shared/src/yaml/mod.rs rename to crates/stackable-shared/src/yaml.rs index 35733d5b0..a2c0a7d13 100644 --- a/crates/stackable-shared/src/yaml/mod.rs +++ b/crates/stackable-shared/src/yaml.rs @@ -128,37 +128,6 @@ pub trait YamlSchema: Sized + serde::Serialize { impl YamlSchema for T where T: serde::ser::Serialize {} -/// Provides YAML schema generation and output capabilities for Kubernetes custom resources. -pub trait CustomResourceExt: kube::CustomResourceExt { - /// Generates the YAML schema of a `CustomResourceDefinition` and writes it to the specified - /// file at `path`. - /// - /// It additionally replaces the documentation URL placeholder with the correct value based on - /// the provided `operator_version`. The written YAML string is an explicit document with - /// leading dashes (`---`). - fn write_yaml_schema>(path: P, operator_version: &str) -> Result<()> { - Self::crd().write_yaml_schema(path, operator_version, SerializeOptions::default()) - } - - /// Generates the YAML schema of a `CustomResourceDefinition` and prints it to [stdout]. - /// - /// It additionally replaces the documentation URL placeholder with the correct value based on - /// the provided `operator_version`. The written YAML string is an explicit document with - /// leading dashes (`---`). - /// - /// [stdout]: std::io::stdout - fn print_yaml_schema(operator_version: &str) -> Result<()> { - Self::crd().print_yaml_schema(operator_version, SerializeOptions::default()) - } - - /// Generates the YAML schema of a `CustomResourceDefinition` and returns it as a [`String`]. - fn yaml_schema(operator_version: &str) -> Result { - Self::crd().generate_yaml_schema(operator_version, SerializeOptions::default()) - } -} - -impl CustomResourceExt for T where T: kube::CustomResourceExt {} - /// Serializes the given data structure and writes it to a [`Writer`](Write). pub fn serialize(value: &T, mut writer: W, options: SerializeOptions) -> Result<()> where From 2fc74738658596755b6eafd5a261ad490f447d12 Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 8 Oct 2024 08:56:09 +0200 Subject: [PATCH 4/5] chore: Update changelogs --- crates/stackable-operator/CHANGELOG.md | 17 +++++++++++++++++ crates/stackable-shared/CHANGELOG.md | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index e1775db1c..7404e2561 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Re-export the `YamlSchema` trait and the `stackable-shared` crate as the `shared` module ([#883]). + +### Changed + +- BREAKING: The `CustomResourceExt` trait is now re-exported from the `stackable-shared` crate. The + trait functions use the same parameters but return a different error type ([#883]). + +### Removed + +- BREAKING: The `CustomResourceExt` trait doesn't provide a `generate_yaml_schema` function any + more. Instead, use the high-level functions to write the schema to a file, write it to stdout or + use it as a `String`. + +[#883]: https://github.com/stackabletech/operator-rs/pull/883 + ## [0.78.0] - 2024-09-30 ### Added diff --git a/crates/stackable-shared/CHANGELOG.md b/crates/stackable-shared/CHANGELOG.md index 7a1e21ce4..8890b44be 100644 --- a/crates/stackable-shared/CHANGELOG.md +++ b/crates/stackable-shared/CHANGELOG.md @@ -3,3 +3,9 @@ All notable changes to this project will be documented in this file. ## [0.0.1] + +### Added + +- Add YAML and CRD helper functions and traits ([#883]). + +[#883]: https://github.com/stackabletech/operator-rs/pull/883 From df719d548f550fd4156204fa8fd9cfc4035047d8 Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 8 Oct 2024 14:07:05 +0200 Subject: [PATCH 5/5] chore: Parse SemVer when creating DocUrlReplacer --- crates/stackable-shared/src/yaml.rs | 40 ++++++++++++++++++----------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/crates/stackable-shared/src/yaml.rs b/crates/stackable-shared/src/yaml.rs index a2c0a7d13..04ef83770 100644 --- a/crates/stackable-shared/src/yaml.rs +++ b/crates/stackable-shared/src/yaml.rs @@ -1,5 +1,5 @@ //! Utility functions for processing data in the YAML file format -use std::{io::Write, path::Path, str::FromStr}; +use std::{io::Write, path::Path}; use semver::Version; use snafu::{ResultExt, Snafu}; @@ -34,26 +34,36 @@ pub enum Error { ParseUtf8Bytes { source: std::string::FromUtf8Error }, } -pub(crate) struct DocUrlReplacer<'a>(&'a str); +pub(crate) struct DocUrlReplacer(Version); -impl<'a> DocUrlReplacer<'a> { - pub(crate) fn new(operator_version: &'a str) -> Self { - Self(operator_version) +impl DocUrlReplacer { + pub(crate) fn new(operator_version: &str) -> Result { + let version = operator_version + .parse() + .context(ParseSemanticVersionSnafu { + input: operator_version, + })?; + + Ok(Self(version)) } - fn replace(&self, input: &str) -> Result { - let docs_version = match self.0 { - "0.0.0-dev" => "nightly".to_owned(), - ver => { - let v = Version::from_str(ver).context(ParseSemanticVersionSnafu { input })?; - format!("{major}.{minor}", major = v.major, minor = v.minor) + fn replace(&self, input: &str) -> String { + let docs_version = match ( + self.0.major, + self.0.minor, + self.0.patch, + self.0.pre.as_str(), + ) { + (0, 0, 0, "dev") => "nightly".to_owned(), + (major, minor, ..) => { + format!("{major}.{minor}") } }; - Ok(input.replace( + input.replace( STACKABLE_DOCS_HOME_URL_PLACEHOLDER, &format!("{STACKABLE_DOCS_HOME_BASE_URL}/{docs_version}"), - )) + ) } } @@ -96,8 +106,8 @@ pub trait YamlSchema: Sized + serde::Serialize { let yaml_string = String::from_utf8(buffer).context(ParseUtf8BytesSnafu)?; - let replacer = DocUrlReplacer::new(operator_version); - let yaml_string = replacer.replace(&yaml_string)?; + let replacer = DocUrlReplacer::new(operator_version)?; + let yaml_string = replacer.replace(&yaml_string); Ok(yaml_string) }