From 1b2da79c1e015c71e3e713feabd70dde430a0b35 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Wed, 14 May 2025 18:43:54 +0200 Subject: [PATCH 01/23] feat: Add flux-converter :) --- Cargo.lock | 20 +-- Cargo.toml | 4 + crates/stackable-versioned-macros/Cargo.toml | 6 +- .../src/codegen/container/mod.rs | 38 +++-- .../src/codegen/container/struct.rs | 19 ++- .../src/codegen/flux_converter.rs | 147 +++++++++++++++++ .../src/codegen/mod.rs | 3 + .../src/codegen/module.rs | 2 +- crates/stackable-versioned/Cargo.toml | 17 +- .../src/flux_converter/mod.rs | 25 +++ .../src/flux_converter/tests/mod.rs | 148 ++++++++++++++++++ .../tests/test_data/convert_person_to_v2.json | 22 +++ .../tests/test_data/simple_from_k8s.json | 55 +++++++ crates/stackable-versioned/src/lib.rs | 7 +- 14 files changed, 484 insertions(+), 29 deletions(-) create mode 100644 crates/stackable-versioned-macros/src/codegen/flux_converter.rs create mode 100644 crates/stackable-versioned/src/flux_converter/mod.rs create mode 100644 crates/stackable-versioned/src/flux_converter/tests/mod.rs create mode 100644 crates/stackable-versioned/src/flux_converter/tests/test_data/convert_person_to_v2.json create mode 100644 crates/stackable-versioned/src/flux_converter/tests/test_data/simple_from_k8s.json diff --git a/Cargo.lock b/Cargo.lock index 0b6637a52..f32767639 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1666,8 +1666,7 @@ dependencies = [ [[package]] name = "kube" version = "0.99.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a4eb20010536b48abe97fec37d23d43069bcbe9686adcf9932202327bc5ca6e" +source = "git+https://github.com/sbernauer/kube.git?branch=fix%2Fderive-conversion-types-0.99#60edf7461cf59ea98eac08a2488f4076ee88c905" dependencies = [ "k8s-openapi", "kube-client", @@ -1679,8 +1678,7 @@ dependencies = [ [[package]] name = "kube-client" version = "0.99.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc2ed952042df20d15ac2fe9614d0ec14b6118eab89633985d4b36e688dccf1" +source = "git+https://github.com/sbernauer/kube.git?branch=fix%2Fderive-conversion-types-0.99#60edf7461cf59ea98eac08a2488f4076ee88c905" dependencies = [ "base64 0.22.1", "bytes", @@ -1716,8 +1714,7 @@ dependencies = [ [[package]] name = "kube-core" version = "0.99.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff0d0793db58e70ca6d689489183816cb3aa481673e7433dc618cf7e8007c675" +source = "git+https://github.com/sbernauer/kube.git?branch=fix%2Fderive-conversion-types-0.99#60edf7461cf59ea98eac08a2488f4076ee88c905" dependencies = [ "chrono", "form_urlencoded", @@ -1734,8 +1731,7 @@ dependencies = [ [[package]] name = "kube-derive" version = "0.99.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c562f58dc9f7ca5feac8a6ee5850ca221edd6f04ce0dd2ee873202a88cd494c9" +source = "git+https://github.com/sbernauer/kube.git?branch=fix%2Fderive-conversion-types-0.99#60edf7461cf59ea98eac08a2488f4076ee88c905" dependencies = [ "darling", "proc-macro2", @@ -1748,8 +1744,7 @@ dependencies = [ [[package]] name = "kube-runtime" version = "0.99.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88f34cfab9b4bd8633062e0e85edb81df23cb09f159f2e31c60b069ae826ffdc" +source = "git+https://github.com/sbernauer/kube.git?branch=fix%2Fderive-conversion-types-0.99#60edf7461cf59ea98eac08a2488f4076ee88c905" dependencies = [ "ahash", "async-broadcast", @@ -3211,6 +3206,11 @@ dependencies = [ name = "stackable-versioned" version = "0.7.1" dependencies = [ + "k8s-openapi", + "kube", + "schemars", + "serde", + "serde_json", "stackable-versioned-macros", ] diff --git a/Cargo.toml b/Cargo.toml index ee5c1e7fd..fc76dccb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,3 +87,7 @@ rsa.opt-level = 3 [profile.dev.package] insta.opt-level = 3 similar.opt-level = 3 + +[patch.crates-io] +# https://github.com/kube-rs/kube/pull/1759 will be in 1.1.0 +kube = { git = 'https://github.com/sbernauer/kube.git', branch = "fix/derive-conversion-types-0.99" } diff --git a/crates/stackable-versioned-macros/Cargo.toml b/crates/stackable-versioned-macros/Cargo.toml index 123679118..20a5ef747 100644 --- a/crates/stackable-versioned-macros/Cargo.toml +++ b/crates/stackable-versioned-macros/Cargo.toml @@ -25,8 +25,9 @@ normal = ["k8s-openapi", "kube"] proc-macro = true [features] -full = ["k8s"] -k8s = ["dep:kube", "dep:k8s-openapi"] +full = ["k8s", "flux-converter"] +k8s = ["dep:kube", "dep:k8s-openapi", "dep:snafu"] +flux-converter = ["k8s"] [dependencies] k8s-version = { path = "../k8s-version", features = ["darling"] } @@ -37,6 +38,7 @@ itertools.workspace = true k8s-openapi = { workspace = true, optional = true } kube = { workspace = true, optional = true } proc-macro2.workspace = true +snafu = { workspace = true, optional = true } syn.workspace = true quote.workspace = true diff --git a/crates/stackable-versioned-macros/src/codegen/container/mod.rs b/crates/stackable-versioned-macros/src/codegen/container/mod.rs index fbbff4006..daefc9ab4 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/mod.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/mod.rs @@ -84,11 +84,11 @@ impl Container { } } - /// Generates Kubernetes specific code to merge two or more CRDs into one. + /// Generates Kubernetes specific code to merge two CRDs or convert between different versions. /// /// This function only returns `Some` if it is a struct. Enums cannot be used to define /// Kubernetes custom resources. - pub(crate) fn generate_kubernetes_merge_crds( + pub(crate) fn generate_kubernetes_code( &self, enum_variant_idents: &[IdentString], enum_variant_strings: &[String], @@ -96,16 +96,28 @@ impl Container { vis: &Visibility, is_nested: bool, ) -> Option { - match self { - Container::Struct(s) => s.generate_kubernetes_merge_crds( - enum_variant_idents, - enum_variant_strings, - fn_calls, - vis, - is_nested, - ), - Container::Enum(_) => None, - } + let Container::Struct(s) = self else { + return None; + }; + + let mut tokens = TokenStream::new(); + tokens.extend(s.generate_kubernetes_merge_crds( + enum_variant_idents, + enum_variant_strings, + fn_calls, + vis, + is_nested, + )); + + #[cfg(feature = "flux-converter")] + tokens.extend(super::flux_converter::generate_kubernetes_conversion( + &s.common.idents.kubernetes, + &s.common.idents.original, + enum_variant_idents, + enum_variant_strings, + )); + + Some(tokens) } pub(crate) fn get_original_ident(&self) -> &Ident { @@ -214,7 +226,7 @@ impl StandaloneContainer { }); } - tokens.extend(self.container.generate_kubernetes_merge_crds( + tokens.extend(self.container.generate_kubernetes_code( &kubernetes_enum_variant_idents, &kubernetes_enum_variant_strings, &kubernetes_merge_crds_fn_calls, diff --git a/crates/stackable-versioned-macros/src/codegen/container/struct.rs b/crates/stackable-versioned-macros/src/codegen/container/struct.rs index 584a293b1..5e790b73a 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/struct.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/struct.rs @@ -349,6 +349,8 @@ impl Struct { vis: &Visibility, is_nested: bool, ) -> Option { + assert_eq!(enum_variant_idents.len(), enum_variant_strings.len()); + match &self.common.options.kubernetes_options { Some(kubernetes_options) if !kubernetes_options.skip_merged_crd => { let enum_ident = &self.common.idents.kubernetes; @@ -377,12 +379,27 @@ impl Struct { } } + #automatically_derived + impl ::std::str::FromStr for #enum_ident { + type Err = stackable_versioned::UnknownResourceVersionError; + + fn from_str(version: &str) -> Result { + match version { + #(#enum_variant_strings => Ok(Self::#enum_variant_idents),)* + _ => Err(stackable_versioned::UnknownResourceVersionError{version: version.to_string()}), + } + } + } + #automatically_derived impl #enum_ident { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. pub fn merged_crd( stored_apiversion: Self - ) -> ::std::result::Result<#k8s_openapi_path::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition, #kube_core_path::crd::MergeError> { + ) -> ::std::result::Result< + #k8s_openapi_path::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition, + #kube_core_path::crd::MergeError + > { #kube_core_path::crd::merge_crds(vec![#(#fn_calls),*], &stored_apiversion.to_string()) } } diff --git a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs new file mode 100644 index 000000000..5eb2423b3 --- /dev/null +++ b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs @@ -0,0 +1,147 @@ +use darling::util::IdentString; +use proc_macro2::TokenStream; +use quote::quote; + +pub(crate) fn generate_kubernetes_conversion( + enum_ident: &IdentString, + struct_ident: &IdentString, + enum_variant_idents: &[IdentString], + enum_variant_strings: &[String], +) -> Option { + assert_eq!(enum_variant_idents.len(), enum_variant_strings.len()); + + let versions = enum_variant_idents + .iter() + .zip(enum_variant_strings) + .collect::>(); + let conversion_chain = generate_conversion_chain(versions); + + let matches = conversion_chain.into_iter().map( + |((src, src_lower), (dst, _dst_lower), version_chain)| { + let version_chain_string = version_chain.iter() + .map(|(_,v)| v.parse::() + .expect("The versions always needs to be a valid TokenStream")); + + // TODO: Is there a bit more clever way how we can get this? + let src_lower = src_lower.parse::().expect("The versions always needs to be a valid TokenStream"); + + quote! { (Self::#src, Self::#dst) => { + let resource: #src_lower::#struct_ident = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(#enum_ident))); + + #( + let resource: #version_chain_string::#struct_ident = resource.into(); + )* + + converted.push( + serde_json::to_value(resource).expect(&format!("Failed to serialize {}", stringify!(#enum_ident))) + ); + }} + }, + ); + + Some(quote! { + #[automatically_derived] + impl #enum_ident { + pub fn convert(review: kube::core::conversion::ConversionReview) -> kube::core::conversion::ConversionResponse { + let request = kube::core::conversion::ConversionRequest::from_review(review) + .unwrap(); + let desired_api_version = ::from_str(&request.desired_api_version) + .expect(&format!("invalid desired version for {} resource", stringify!(#enum_ident))); + + let mut converted: Vec = Vec::with_capacity(request.objects.len()); + for object in &request.objects { + let object_spec = object + .get("spec") + .expect("The passed object had no spec") + .clone(); + let kind = object + .get("kind") + .expect("The objected asked to convert has no kind"); + let api_version = object + .get("apiVersion") + .expect("The objected asked to convert has no apiVersion") + .as_str() + .expect("The apiVersion of the objected asked to convert wasn't a String"); + + assert_eq!(kind, stringify!(#enum_ident)); + + let current_api_version = ::from_str(api_version) + .expect(&format!("invalid current version for {} resource", stringify!(#enum_ident))); + + match (¤t_api_version, &desired_api_version) { + #(#matches),* + } + } + + let response = kube::core::conversion::ConversionResponse::for_request(request); + response.success(converted) + } + } + }) +} + +pub fn generate_conversion_chain( + versions: Vec, +) -> Vec<(Version, Version, Vec)> { + let mut result = Vec::with_capacity(versions.len().pow(2)); + let n = versions.len(); + + for i in 0..n { + for j in 0..n { + let source = versions[i].clone(); + let destination = versions[j].clone(); + let chain = if i == j { + vec![] + } else if i < j { + versions[i + 1..=j].to_vec() + } else { + versions[j..i].iter().rev().cloned().collect() + }; + result.push((source, destination, chain)); + } + } + + result +} + +#[cfg(test)] +mod tests { + use super::generate_conversion_chain; + + #[test] + fn test_generate_conversion_chains() { + let versions = vec!["v1alpha1", "v1alpha2", "v1beta1", "v1", "v2"]; + let conversion_chain = generate_conversion_chain(versions); + + assert_eq!(conversion_chain, vec![ + ("v1alpha1", "v1alpha1", vec![]), + ("v1alpha1", "v1alpha2", vec!["v1alpha2"]), + ("v1alpha1", "v1beta1", vec!["v1alpha2", "v1beta1"]), + ("v1alpha1", "v1", vec!["v1alpha2", "v1beta1", "v1"]), + ("v1alpha1", "v2", vec!["v1alpha2", "v1beta1", "v1", "v2"]), + ("v1alpha2", "v1alpha1", vec!["v1alpha1"]), + ("v1alpha2", "v1alpha2", vec![]), + ("v1alpha2", "v1beta1", vec!["v1beta1"]), + ("v1alpha2", "v1", vec!["v1beta1", "v1"]), + ("v1alpha2", "v2", vec!["v1beta1", "v1", "v2"]), + ("v1beta1", "v1alpha1", vec!["v1alpha2", "v1alpha1"]), + ("v1beta1", "v1alpha2", vec!["v1alpha2"]), + ("v1beta1", "v1beta1", vec![]), + ("v1beta1", "v1", vec!["v1"]), + ("v1beta1", "v2", vec!["v1", "v2"]), + ("v1", "v1alpha1", vec!["v1beta1", "v1alpha2", "v1alpha1"]), + ("v1", "v1alpha2", vec!["v1beta1", "v1alpha2"]), + ("v1", "v1beta1", vec!["v1beta1"]), + ("v1", "v1", vec![]), + ("v1", "v2", vec!["v2"]), + ("v2", "v1alpha1", vec![ + "v1", "v1beta1", "v1alpha2", "v1alpha1" + ]), + ("v2", "v1alpha2", vec!["v1", "v1beta1", "v1alpha2"]), + ("v2", "v1beta1", vec!["v1", "v1beta1"]), + ("v2", "v1", vec!["v1"]), + ("v2", "v2", vec![]) + ]); + } +} diff --git a/crates/stackable-versioned-macros/src/codegen/mod.rs b/crates/stackable-versioned-macros/src/codegen/mod.rs index 4f4b2ea3c..e8fb2bdfa 100644 --- a/crates/stackable-versioned-macros/src/codegen/mod.rs +++ b/crates/stackable-versioned-macros/src/codegen/mod.rs @@ -10,6 +10,9 @@ pub(crate) mod container; pub(crate) mod item; pub(crate) mod module; +#[cfg(feature = "flux-converter")] +pub(crate) mod flux_converter; + #[derive(Debug)] pub(crate) struct VersionDefinition { /// Indicates that the container version is deprecated. diff --git a/crates/stackable-versioned-macros/src/codegen/module.rs b/crates/stackable-versioned-macros/src/codegen/module.rs index 217dacced..22581578a 100644 --- a/crates/stackable-versioned-macros/src/codegen/module.rs +++ b/crates/stackable-versioned-macros/src/codegen/module.rs @@ -221,7 +221,7 @@ impl Module { kubernetes_enum_variant_strings, )) = kubernetes_container_items.get(container.get_original_ident()) { - kubernetes_tokens.extend(container.generate_kubernetes_merge_crds( + kubernetes_tokens.extend(container.generate_kubernetes_code( kubernetes_enum_variant_idents, kubernetes_enum_variant_strings, kubernetes_merge_crds_fn_calls, diff --git a/crates/stackable-versioned/Cargo.toml b/crates/stackable-versioned/Cargo.toml index 9f4327f31..cf4d3a31a 100644 --- a/crates/stackable-versioned/Cargo.toml +++ b/crates/stackable-versioned/Cargo.toml @@ -11,10 +11,25 @@ repository.workspace = true all-features = true [features] -full = ["k8s"] +full = ["k8s", "flux-converter"] k8s = [ "stackable-versioned-macros/k8s", # Forward the k8s feature to the underlying macro crate ] +flux-converter = [ + "k8s", + "stackable-versioned-macros/flux-converter", + "dep:kube", + "dep:k8s-openapi", + "dep:serde", + "dep:schemars", + "dep:serde_json", +] [dependencies] stackable-versioned-macros = { path = "../stackable-versioned-macros" } + +kube = { workspace = true, optional = true } +k8s-openapi = { workspace = true, optional = true } +serde = { workspace = true, optional = true } +schemars = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } diff --git a/crates/stackable-versioned/src/flux_converter/mod.rs b/crates/stackable-versioned/src/flux_converter/mod.rs new file mode 100644 index 000000000..40cdc15a6 --- /dev/null +++ b/crates/stackable-versioned/src/flux_converter/mod.rs @@ -0,0 +1,25 @@ +//! `flux-converter` is part of the project DeLorean :) +//! +//! It converts between different CRD versions by using 1.21 GW of power, +//! 142km/h and time travel. + +use std::fmt::Display; + +#[cfg(test)] +mod tests; + +#[derive(Debug)] +pub struct UnknownResourceVersionError { + pub version: String, +} + +impl std::error::Error for UnknownResourceVersionError {} +impl Display for UnknownResourceVersionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "The version {version} is not known", + version = self.version + ) + } +} diff --git a/crates/stackable-versioned/src/flux_converter/tests/mod.rs b/crates/stackable-versioned/src/flux_converter/tests/mod.rs new file mode 100644 index 000000000..eccbcb5e1 --- /dev/null +++ b/crates/stackable-versioned/src/flux_converter/tests/mod.rs @@ -0,0 +1,148 @@ +use kube::CustomResource; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use stackable_versioned_macros::versioned; + +use crate as stackable_versioned; + +#[versioned( + k8s(group = "test.stackable.tech",), + version(name = "v1alpha1"), + version(name = "v1alpha2"), + version(name = "v1beta1"), + version(name = "v2"), + version(name = "v3") +)] +#[derive( + Clone, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + CustomResource, + Deserialize, + JsonSchema, + Serialize, +)] +#[serde(rename_all = "camelCase")] +struct PersonSpec { + username: String, + + // In v1alpha2 first and last name have been added + #[versioned(added(since = "v1alpha2"))] + first_name: String, + #[versioned(added(since = "v1alpha2"))] + last_name: String, + + // We started out with a enum. As we *need* to provide a default, we have a Unknown variant. + // Afterwards we figured let's be more flexible and accept any arbitrary String. + #[versioned( + added(since = "v2", default = "default_gender"), + changed(since = "v3", from_type = "Gender") + )] + gender: String, +} + +fn default_gender() -> Gender { + Gender::Unknown +} + +#[derive( + Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, JsonSchema, Serialize, +)] +#[serde(rename_all = "PascalCase")] +pub enum Gender { + Unknown, + Male, + Female, +} + +impl Into for Gender { + fn into(self) -> String { + match self { + Gender::Unknown => "Unknown".to_string(), + Gender::Male => "Male".to_string(), + Gender::Female => "Female".to_string(), + } + } +} +impl From for Gender { + fn from(value: String) -> Self { + match value.as_str() { + "Male" => Self::Male, + "Female" => Self::Female, + _ => Self::Unknown, + } + } +} + +/// TEMP, we need to implement downgrades manually +impl From for v1alpha1::PersonSpec { + fn from(value: v1alpha2::PersonSpec) -> Self { + Self { + username: value.username, + } + } +} +impl From for v1alpha2::PersonSpec { + fn from(value: v1beta1::PersonSpec) -> Self { + Self { + username: value.username, + first_name: value.first_name, + last_name: value.last_name, + } + } +} +impl From for v1beta1::PersonSpec { + fn from(value: v2::PersonSpec) -> Self { + Self { + username: value.username, + first_name: value.first_name, + last_name: value.last_name, + } + } +} +impl From for v2::PersonSpec { + fn from(value: v3::PersonSpec) -> Self { + Self { + username: value.username, + first_name: value.first_name, + last_name: value.last_name, + gender: value.gender.into(), + } + } +} +/// END TEMP + +#[cfg(test)] +mod tests { + use super::Person; + + #[test] + fn parse_simple_example_from_k8s() { + // this file contains dump of real request generated by kubernetes v1.22 + // Copied from https://github.com/kube-rs/kube/blob/main/kube-core/src/conversion/test_data/simple.json + let data = include_str!("./test_data/simple_from_k8s.json"); + // check that we can parse this review, and all chain of conversion worls + let review = serde_json::from_str(data).expect("invalid ConversionReview"); + let request = kube::core::conversion::ConversionRequest::from_review(review) + .expect("failed to get conversion request from review"); + let response = kube::core::conversion::ConversionResponse::for_request(request); + let _ = response.into_review(); + } + + #[test] + fn test_macro() { + let data = include_str!("./test_data/convert_person_to_v2.json"); + let review: kube::core::conversion::ConversionReview = + serde_json::from_str(data).expect("invalid ConversionReview"); + + let response = Person::convert(review); + assert_eq!( + serde_json::to_string(&response).unwrap(), + "{\"uid\":\"c4e55572-ee1f-4e94-9097-28936985d45f\",\"result\":{\"status\":\"Success\"},\"convertedObjects\":[{\"firstName\":\"\",\"gender\":\"Unknown\",\"lastName\":\"\",\"username\":\"sbernauer\"}]}" + ); + } +} diff --git a/crates/stackable-versioned/src/flux_converter/tests/test_data/convert_person_to_v2.json b/crates/stackable-versioned/src/flux_converter/tests/test_data/convert_person_to_v2.json new file mode 100644 index 000000000..09378fa38 --- /dev/null +++ b/crates/stackable-versioned/src/flux_converter/tests/test_data/convert_person_to_v2.json @@ -0,0 +1,22 @@ +{ + "kind": "ConversionReview", + "apiVersion": "apiextensions.k8s.io/v1", + "request": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "desiredAPIVersion": "v3", + "objects": [ + { + "apiVersion": "v1alpha1", + "kind": "Person", + "metadata": { + "name": "sbernauer", + "namespace": "default", + "uid": "d41e2019-5de3-409c-a7b2-0799ecb95e4b" + }, + "spec": { + "username": "sbernauer" + } + } + ] + } +} diff --git a/crates/stackable-versioned/src/flux_converter/tests/test_data/simple_from_k8s.json b/crates/stackable-versioned/src/flux_converter/tests/test_data/simple_from_k8s.json new file mode 100644 index 000000000..8cc9cbf1c --- /dev/null +++ b/crates/stackable-versioned/src/flux_converter/tests/test_data/simple_from_k8s.json @@ -0,0 +1,55 @@ +{ + "kind": "ConversionReview", + "apiVersion": "apiextensions.k8s.io/v1", + "request": { + "uid": "f263987e-4d58-465a-9195-bf72a1c83623", + "desiredAPIVersion": "nullable.se/v1", + "objects": [ + { + "apiVersion": "nullable.se/v2", + "kind": "ConfigMapGenerator", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"nullable.se/v2\",\"kind\":\"ConfigMapGenerator\",\"metadata\":{\"annotations\":{},\"name\":\"kek\",\"namespace\":\"default\"},\"spec\":{\"content\":\"x\"}}\n" + }, + "creationTimestamp": "2022-09-04T14:21:34Z", + "generation": 1, + "managedFields": [ + { + "apiVersion": "nullable.se/v2", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {} + } + }, + "f:spec": { + ".": {}, + "f:content": {} + } + }, + "manager": "kubectl-client-side-apply", + "operation": "Update", + "time": "2022-09-04T14:21:34Z" + } + ], + "name": "kek", + "namespace": "default", + "uid": "af7e84e4-573e-4b6e-bb66-0ea578c740da" + }, + "spec": { + "content": "x" + } + } + ] + }, + "response": { + "uid": "", + "convertedObjects": null, + "result": { + "metadata": {} + } + } +} diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index 8c0c399b1..d610974cb 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -12,9 +12,14 @@ //! See [`versioned`] for an in-depth usage guide and a list of supported //! parameters. -// Re-export macro pub use stackable_versioned_macros::*; +#[cfg(feature = "flux-converter")] +mod flux_converter; + +#[cfg(feature = "flux-converter")] +pub use flux_converter::UnknownResourceVersionError; + // Unused for now, might get picked up again in the future. #[doc(hidden)] pub trait AsVersionStr { From 4aee8a241992cd20b062b0c8de4b7810851cc152 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 15 May 2025 10:48:52 +0200 Subject: [PATCH 02/23] fix: Only emit conversion code for k8s CRDs --- crates/stackable-operator/Cargo.toml | 2 +- .../src/codegen/container/mod.rs | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/stackable-operator/Cargo.toml b/crates/stackable-operator/Cargo.toml index 34918f52d..ff14587d2 100644 --- a/crates/stackable-operator/Cargo.toml +++ b/crates/stackable-operator/Cargo.toml @@ -16,7 +16,7 @@ versioned = [] [dependencies] stackable-telemetry = { path = "../stackable-telemetry", features = ["clap"] } -stackable-versioned = { path = "../stackable-versioned", features = ["k8s"] } +stackable-versioned = { path = "../stackable-versioned", features = ["k8s", "flux-converter"] } stackable-operator-derive = { path = "../stackable-operator-derive" } stackable-shared = { path = "../stackable-shared" } diff --git a/crates/stackable-versioned-macros/src/codegen/container/mod.rs b/crates/stackable-versioned-macros/src/codegen/container/mod.rs index daefc9ab4..c29afc1dc 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/mod.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/mod.rs @@ -99,17 +99,22 @@ impl Container { let Container::Struct(s) = self else { return None; }; + let kubernetes_options = s.common.options.kubernetes_options.as_ref()?; let mut tokens = TokenStream::new(); - tokens.extend(s.generate_kubernetes_merge_crds( - enum_variant_idents, - enum_variant_strings, - fn_calls, - vis, - is_nested, - )); + + if !kubernetes_options.skip_merged_crd { + tokens.extend(s.generate_kubernetes_merge_crds( + enum_variant_idents, + enum_variant_strings, + fn_calls, + vis, + is_nested, + )); + } #[cfg(feature = "flux-converter")] + // TODO: Do we need a kubernetes_options.skip_conversion as well? tokens.extend(super::flux_converter::generate_kubernetes_conversion( &s.common.idents.kubernetes, &s.common.idents.original, From e5bf325f0ca6aa002545dcde64324958ec0b9c3a Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 15 May 2025 10:53:27 +0200 Subject: [PATCH 03/23] test: Update test assertions --- ..._macros__test__k8s_snapshots@basic.rs.snap | 148 +++++++ ...est__k8s_snapshots@crate_overrides.rs.snap | 148 +++++++ ...macros__test__k8s_snapshots@module.rs.snap | 296 ++++++++++++++ ...est__k8s_snapshots@module_preserve.rs.snap | 366 ++++++++++++++++++ ...__test__k8s_snapshots@renamed_kind.rs.snap | 186 +++++++++ ...os__test__k8s_snapshots@shortnames.rs.snap | 66 ++++ ...d_macros__test__k8s_snapshots@skip.rs.snap | 42 ++ 7 files changed, 1252 insertions(+) diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap index 83bab4878..53c73bfb7 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap @@ -112,6 +112,22 @@ impl ::std::fmt::Display for Foo { } } #[automatically_derived] +impl ::std::str::FromStr for Foo { + type Err = stackable_versioned::UnknownResourceVersionError; + fn from_str(version: &str) -> Result { + match version { + "v1alpha1" => Ok(Self::V1Alpha1), + "v1beta1" => Ok(Self::V1Beta1), + "v1" => Ok(Self::V1), + _ => { + Err(stackable_versioned::UnknownResourceVersionError { + version: version.to_string(), + }) + } + } + } +} +#[automatically_derived] impl Foo { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. pub fn merged_crd( @@ -130,3 +146,135 @@ impl Foo { ) } } +#[automatically_derived] +impl Foo { + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionResponse { + let request = kube::core::conversion::ConversionRequest::from_review(review) + .unwrap(); + let desired_api_version = ::from_str( + &request.desired_api_version, + ) + .expect( + &format!("invalid desired version for {} resource", stringify!(Foo)), + ); + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .expect("The passed object had no spec") + .clone(); + let kind = object + .get("kind") + .expect("The objected asked to convert has no kind"); + let api_version = object + .get("apiVersion") + .expect("The objected asked to convert has no apiVersion") + .as_str() + .expect( + "The apiVersion of the objected asked to convert wasn't a String", + ); + assert_eq!(kind, stringify!(Foo)); + let current_api_version = ::from_str(api_version) + .expect( + &format!("invalid current version for {} resource", stringify!(Foo)), + ); + match (¤t_api_version, &desired_api_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Alpha1, Self::V1Beta1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1beta1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1beta1::FooSpec = resource.into(); + let resource: v1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Beta1, Self::V1Alpha1) => { + let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Beta1, Self::V1Beta1) => { + let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Beta1, Self::V1) => { + let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1beta1::FooSpec = resource.into(); + let resource: v1alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1, Self::V1Beta1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1beta1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1, Self::V1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + } + } + let response = kube::core::conversion::ConversionResponse::for_request(request); + response.success(converted) + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap index 2999586ad..15df09c4a 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap @@ -115,6 +115,22 @@ impl ::std::fmt::Display for Foo { } } #[automatically_derived] +impl ::std::str::FromStr for Foo { + type Err = stackable_versioned::UnknownResourceVersionError; + fn from_str(version: &str) -> Result { + match version { + "v1alpha1" => Ok(Self::V1Alpha1), + "v1beta1" => Ok(Self::V1Beta1), + "v1" => Ok(Self::V1), + _ => { + Err(stackable_versioned::UnknownResourceVersionError { + version: version.to_string(), + }) + } + } + } +} +#[automatically_derived] impl Foo { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. pub fn merged_crd( @@ -133,3 +149,135 @@ impl Foo { ) } } +#[automatically_derived] +impl Foo { + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionResponse { + let request = kube::core::conversion::ConversionRequest::from_review(review) + .unwrap(); + let desired_api_version = ::from_str( + &request.desired_api_version, + ) + .expect( + &format!("invalid desired version for {} resource", stringify!(Foo)), + ); + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .expect("The passed object had no spec") + .clone(); + let kind = object + .get("kind") + .expect("The objected asked to convert has no kind"); + let api_version = object + .get("apiVersion") + .expect("The objected asked to convert has no apiVersion") + .as_str() + .expect( + "The apiVersion of the objected asked to convert wasn't a String", + ); + assert_eq!(kind, stringify!(Foo)); + let current_api_version = ::from_str(api_version) + .expect( + &format!("invalid current version for {} resource", stringify!(Foo)), + ); + match (¤t_api_version, &desired_api_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Alpha1, Self::V1Beta1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1beta1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1beta1::FooSpec = resource.into(); + let resource: v1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Beta1, Self::V1Alpha1) => { + let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Beta1, Self::V1Beta1) => { + let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Beta1, Self::V1) => { + let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1beta1::FooSpec = resource.into(); + let resource: v1alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1, Self::V1Beta1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1beta1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1, Self::V1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + } + } + let response = kube::core::conversion::ConversionResponse::for_request(request); + response.success(converted) + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap index d01dbc544..f45c32f3a 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap @@ -229,6 +229,22 @@ impl ::std::fmt::Display for Foo { } } #[automatically_derived] +impl ::std::str::FromStr for Foo { + type Err = stackable_versioned::UnknownResourceVersionError; + fn from_str(version: &str) -> Result { + match version { + "v1alpha1" => Ok(Self::V1Alpha1), + "v1" => Ok(Self::V1), + "v2alpha1" => Ok(Self::V2Alpha1), + _ => { + Err(stackable_versioned::UnknownResourceVersionError { + version: version.to_string(), + }) + } + } + } +} +#[automatically_derived] impl Foo { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. pub fn merged_crd( @@ -248,6 +264,138 @@ impl Foo { } } #[automatically_derived] +impl Foo { + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionResponse { + let request = kube::core::conversion::ConversionRequest::from_review(review) + .unwrap(); + let desired_api_version = ::from_str( + &request.desired_api_version, + ) + .expect( + &format!("invalid desired version for {} resource", stringify!(Foo)), + ); + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .expect("The passed object had no spec") + .clone(); + let kind = object + .get("kind") + .expect("The objected asked to convert has no kind"); + let api_version = object + .get("apiVersion") + .expect("The objected asked to convert has no apiVersion") + .as_str() + .expect( + "The apiVersion of the objected asked to convert wasn't a String", + ); + assert_eq!(kind, stringify!(Foo)); + let current_api_version = ::from_str(api_version) + .expect( + &format!("invalid current version for {} resource", stringify!(Foo)), + ); + match (¤t_api_version, &desired_api_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Alpha1, Self::V2Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1::FooSpec = resource.into(); + let resource: v2alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1, Self::V1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1, Self::V2Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v2alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V2Alpha1, Self::V1Alpha1) => { + let resource: v2alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1::FooSpec = resource.into(); + let resource: v1alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V2Alpha1, Self::V1) => { + let resource: v2alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + let resource: v1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V2Alpha1, Self::V2Alpha1) => { + let resource: v2alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + } + } + let response = kube::core::conversion::ConversionResponse::for_request(request); + response.success(converted) + } +} +#[automatically_derived] pub(crate) enum Bar { V1Alpha1, V1, @@ -267,6 +415,22 @@ impl ::std::fmt::Display for Bar { } } #[automatically_derived] +impl ::std::str::FromStr for Bar { + type Err = stackable_versioned::UnknownResourceVersionError; + fn from_str(version: &str) -> Result { + match version { + "v1alpha1" => Ok(Self::V1Alpha1), + "v1" => Ok(Self::V1), + "v2alpha1" => Ok(Self::V2Alpha1), + _ => { + Err(stackable_versioned::UnknownResourceVersionError { + version: version.to_string(), + }) + } + } + } +} +#[automatically_derived] impl Bar { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. pub fn merged_crd( @@ -285,3 +449,135 @@ impl Bar { ) } } +#[automatically_derived] +impl Bar { + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionResponse { + let request = kube::core::conversion::ConversionRequest::from_review(review) + .unwrap(); + let desired_api_version = ::from_str( + &request.desired_api_version, + ) + .expect( + &format!("invalid desired version for {} resource", stringify!(Bar)), + ); + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .expect("The passed object had no spec") + .clone(); + let kind = object + .get("kind") + .expect("The objected asked to convert has no kind"); + let api_version = object + .get("apiVersion") + .expect("The objected asked to convert has no apiVersion") + .as_str() + .expect( + "The apiVersion of the objected asked to convert wasn't a String", + ); + assert_eq!(kind, stringify!(Bar)); + let current_api_version = ::from_str(api_version) + .expect( + &format!("invalid current version for {} resource", stringify!(Bar)), + ); + match (¤t_api_version, &desired_api_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::BarSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Bar))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::BarSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Bar))); + let resource: v1::BarSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V1Alpha1, Self::V2Alpha1) => { + let resource: v1alpha1::BarSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Bar))); + let resource: v1::BarSpec = resource.into(); + let resource: v2alpha1::BarSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::BarSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Bar))); + let resource: v1alpha1::BarSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V1, Self::V1) => { + let resource: v1::BarSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Bar))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V1, Self::V2Alpha1) => { + let resource: v1::BarSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Bar))); + let resource: v2alpha1::BarSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V2Alpha1, Self::V1Alpha1) => { + let resource: v2alpha1::BarSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Bar))); + let resource: v1::BarSpec = resource.into(); + let resource: v1alpha1::BarSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V2Alpha1, Self::V1) => { + let resource: v2alpha1::BarSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Bar))); + let resource: v1::BarSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V2Alpha1, Self::V2Alpha1) => { + let resource: v2alpha1::BarSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Bar))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + } + } + let response = kube::core::conversion::ConversionResponse::for_request(request); + response.success(converted) + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap index 601a8a0a9..2d2524d78 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap @@ -217,6 +217,21 @@ pub(crate) mod versioned { } } } + impl ::std::str::FromStr for Foo { + type Err = stackable_versioned::UnknownResourceVersionError; + fn from_str(version: &str) -> Result { + match version { + "v1alpha1" => Ok(Self::V1Alpha1), + "v1" => Ok(Self::V1), + "v2alpha1" => Ok(Self::V2Alpha1), + _ => { + Err(stackable_versioned::UnknownResourceVersionError { + version: version.to_string(), + }) + } + } + } + } impl Foo { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. pub fn merged_crd( @@ -235,6 +250,174 @@ pub(crate) mod versioned { ) } } + #[automatically_derived] + impl Foo { + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionResponse { + let request = kube::core::conversion::ConversionRequest::from_review(review) + .unwrap(); + let desired_api_version = ::from_str( + &request.desired_api_version, + ) + .expect( + &format!("invalid desired version for {} resource", stringify!(Foo)), + ); + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .expect("The passed object had no spec") + .clone(); + let kind = object + .get("kind") + .expect("The objected asked to convert has no kind"); + let api_version = object + .get("apiVersion") + .expect("The objected asked to convert has no apiVersion") + .as_str() + .expect( + "The apiVersion of the objected asked to convert wasn't a String", + ); + assert_eq!(kind, stringify!(Foo)); + let current_api_version = ::from_str( + api_version, + ) + .expect( + &format!( + "invalid current version for {} resource", stringify!(Foo) + ), + ); + match (¤t_api_version, &desired_api_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec, + ) + .expect( + &format!("Failed to deserialize {}", stringify!(Foo)), + ); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec, + ) + .expect( + &format!("Failed to deserialize {}", stringify!(Foo)), + ); + let resource: v1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1Alpha1, Self::V2Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec, + ) + .expect( + &format!("Failed to deserialize {}", stringify!(Foo)), + ); + let resource: v1::FooSpec = resource.into(); + let resource: v2alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(Foo)), + ); + let resource: v1alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1, Self::V1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(Foo)), + ); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V1, Self::V2Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(Foo)), + ); + let resource: v2alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V2Alpha1, Self::V1Alpha1) => { + let resource: v2alpha1::FooSpec = serde_json::from_value( + object_spec, + ) + .expect( + &format!("Failed to deserialize {}", stringify!(Foo)), + ); + let resource: v1::FooSpec = resource.into(); + let resource: v1alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V2Alpha1, Self::V1) => { + let resource: v2alpha1::FooSpec = serde_json::from_value( + object_spec, + ) + .expect( + &format!("Failed to deserialize {}", stringify!(Foo)), + ); + let resource: v1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + (Self::V2Alpha1, Self::V2Alpha1) => { + let resource: v2alpha1::FooSpec = serde_json::from_value( + object_spec, + ) + .expect( + &format!("Failed to deserialize {}", stringify!(Foo)), + ); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + } + } + let response = kube::core::conversion::ConversionResponse::for_request( + request, + ); + response.success(converted) + } + } pub enum Bar { V1Alpha1, V1, @@ -252,6 +435,21 @@ pub(crate) mod versioned { } } } + impl ::std::str::FromStr for Bar { + type Err = stackable_versioned::UnknownResourceVersionError; + fn from_str(version: &str) -> Result { + match version { + "v1alpha1" => Ok(Self::V1Alpha1), + "v1" => Ok(Self::V1), + "v2alpha1" => Ok(Self::V2Alpha1), + _ => { + Err(stackable_versioned::UnknownResourceVersionError { + version: version.to_string(), + }) + } + } + } + } impl Bar { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. pub fn merged_crd( @@ -270,4 +468,172 @@ pub(crate) mod versioned { ) } } + #[automatically_derived] + impl Bar { + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionResponse { + let request = kube::core::conversion::ConversionRequest::from_review(review) + .unwrap(); + let desired_api_version = ::from_str( + &request.desired_api_version, + ) + .expect( + &format!("invalid desired version for {} resource", stringify!(Bar)), + ); + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .expect("The passed object had no spec") + .clone(); + let kind = object + .get("kind") + .expect("The objected asked to convert has no kind"); + let api_version = object + .get("apiVersion") + .expect("The objected asked to convert has no apiVersion") + .as_str() + .expect( + "The apiVersion of the objected asked to convert wasn't a String", + ); + assert_eq!(kind, stringify!(Bar)); + let current_api_version = ::from_str( + api_version, + ) + .expect( + &format!( + "invalid current version for {} resource", stringify!(Bar) + ), + ); + match (¤t_api_version, &desired_api_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::BarSpec = serde_json::from_value( + object_spec, + ) + .expect( + &format!("Failed to deserialize {}", stringify!(Bar)), + ); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::BarSpec = serde_json::from_value( + object_spec, + ) + .expect( + &format!("Failed to deserialize {}", stringify!(Bar)), + ); + let resource: v1::BarSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V1Alpha1, Self::V2Alpha1) => { + let resource: v1alpha1::BarSpec = serde_json::from_value( + object_spec, + ) + .expect( + &format!("Failed to deserialize {}", stringify!(Bar)), + ); + let resource: v1::BarSpec = resource.into(); + let resource: v2alpha1::BarSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::BarSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(Bar)), + ); + let resource: v1alpha1::BarSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V1, Self::V1) => { + let resource: v1::BarSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(Bar)), + ); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V1, Self::V2Alpha1) => { + let resource: v1::BarSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(Bar)), + ); + let resource: v2alpha1::BarSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V2Alpha1, Self::V1Alpha1) => { + let resource: v2alpha1::BarSpec = serde_json::from_value( + object_spec, + ) + .expect( + &format!("Failed to deserialize {}", stringify!(Bar)), + ); + let resource: v1::BarSpec = resource.into(); + let resource: v1alpha1::BarSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V2Alpha1, Self::V1) => { + let resource: v2alpha1::BarSpec = serde_json::from_value( + object_spec, + ) + .expect( + &format!("Failed to deserialize {}", stringify!(Bar)), + ); + let resource: v1::BarSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + (Self::V2Alpha1, Self::V2Alpha1) => { + let resource: v2alpha1::BarSpec = serde_json::from_value( + object_spec, + ) + .expect( + &format!("Failed to deserialize {}", stringify!(Bar)), + ); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Bar))), + ); + } + } + } + let response = kube::core::conversion::ConversionResponse::for_request( + request, + ); + response.success(converted) + } + } } diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap index fbda4713a..8abe0659f 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap @@ -112,6 +112,22 @@ impl ::std::fmt::Display for FooBar { } } #[automatically_derived] +impl ::std::str::FromStr for FooBar { + type Err = stackable_versioned::UnknownResourceVersionError; + fn from_str(version: &str) -> Result { + match version { + "v1alpha1" => Ok(Self::V1Alpha1), + "v1beta1" => Ok(Self::V1Beta1), + "v1" => Ok(Self::V1), + _ => { + Err(stackable_versioned::UnknownResourceVersionError { + version: version.to_string(), + }) + } + } + } +} +#[automatically_derived] impl FooBar { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. pub fn merged_crd( @@ -130,3 +146,173 @@ impl FooBar { ) } } +#[automatically_derived] +impl FooBar { + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionResponse { + let request = kube::core::conversion::ConversionRequest::from_review(review) + .unwrap(); + let desired_api_version = ::from_str( + &request.desired_api_version, + ) + .expect( + &format!("invalid desired version for {} resource", stringify!(FooBar)), + ); + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .expect("The passed object had no spec") + .clone(); + let kind = object + .get("kind") + .expect("The objected asked to convert has no kind"); + let api_version = object + .get("apiVersion") + .expect("The objected asked to convert has no apiVersion") + .as_str() + .expect( + "The apiVersion of the objected asked to convert wasn't a String", + ); + assert_eq!(kind, stringify!(FooBar)); + let current_api_version = ::from_str(api_version) + .expect( + &format!( + "invalid current version for {} resource", stringify!(FooBar) + ), + ); + match (¤t_api_version, &desired_api_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(FooBar)), + ); + converted + .push( + serde_json::to_value(resource) + .expect( + &format!("Failed to serialize {}", stringify!(FooBar)), + ), + ); + } + (Self::V1Alpha1, Self::V1Beta1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(FooBar)), + ); + let resource: v1beta1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect( + &format!("Failed to serialize {}", stringify!(FooBar)), + ), + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(FooBar)), + ); + let resource: v1beta1::FooSpec = resource.into(); + let resource: v1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect( + &format!("Failed to serialize {}", stringify!(FooBar)), + ), + ); + } + (Self::V1Beta1, Self::V1Alpha1) => { + let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(FooBar)), + ); + let resource: v1alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect( + &format!("Failed to serialize {}", stringify!(FooBar)), + ), + ); + } + (Self::V1Beta1, Self::V1Beta1) => { + let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(FooBar)), + ); + converted + .push( + serde_json::to_value(resource) + .expect( + &format!("Failed to serialize {}", stringify!(FooBar)), + ), + ); + } + (Self::V1Beta1, Self::V1) => { + let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(FooBar)), + ); + let resource: v1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect( + &format!("Failed to serialize {}", stringify!(FooBar)), + ), + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(FooBar)), + ); + let resource: v1beta1::FooSpec = resource.into(); + let resource: v1alpha1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect( + &format!("Failed to serialize {}", stringify!(FooBar)), + ), + ); + } + (Self::V1, Self::V1Beta1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(FooBar)), + ); + let resource: v1beta1::FooSpec = resource.into(); + converted + .push( + serde_json::to_value(resource) + .expect( + &format!("Failed to serialize {}", stringify!(FooBar)), + ), + ); + } + (Self::V1, Self::V1) => { + let resource: v1::FooSpec = serde_json::from_value(object_spec) + .expect( + &format!("Failed to deserialize {}", stringify!(FooBar)), + ); + converted + .push( + serde_json::to_value(resource) + .expect( + &format!("Failed to serialize {}", stringify!(FooBar)), + ), + ); + } + } + } + let response = kube::core::conversion::ConversionResponse::for_request(request); + response.success(converted) + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap index b92e44ecb..8ed572d90 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap @@ -39,6 +39,20 @@ impl ::std::fmt::Display for Foo { } } #[automatically_derived] +impl ::std::str::FromStr for Foo { + type Err = stackable_versioned::UnknownResourceVersionError; + fn from_str(version: &str) -> Result { + match version { + "v1alpha1" => Ok(Self::V1Alpha1), + _ => { + Err(stackable_versioned::UnknownResourceVersionError { + version: version.to_string(), + }) + } + } + } +} +#[automatically_derived] impl Foo { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. pub fn merged_crd( @@ -53,3 +67,55 @@ impl Foo { ) } } +#[automatically_derived] +impl Foo { + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionResponse { + let request = kube::core::conversion::ConversionRequest::from_review(review) + .unwrap(); + let desired_api_version = ::from_str( + &request.desired_api_version, + ) + .expect( + &format!("invalid desired version for {} resource", stringify!(Foo)), + ); + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .expect("The passed object had no spec") + .clone(); + let kind = object + .get("kind") + .expect("The objected asked to convert has no kind"); + let api_version = object + .get("apiVersion") + .expect("The objected asked to convert has no apiVersion") + .as_str() + .expect( + "The apiVersion of the objected asked to convert wasn't a String", + ); + assert_eq!(kind, stringify!(Foo)); + let current_api_version = ::from_str(api_version) + .expect( + &format!("invalid current version for {} resource", stringify!(Foo)), + ); + match (¤t_api_version, &desired_api_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) + .expect(&format!("Failed to deserialize {}", stringify!(Foo))); + converted + .push( + serde_json::to_value(resource) + .expect(&format!("Failed to serialize {}", stringify!(Foo))), + ); + } + } + } + let response = kube::core::conversion::ConversionResponse::for_request(request); + response.success(converted) + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap index 39f0b2263..5c804dd5a 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap @@ -92,3 +92,45 @@ pub mod v1 { pub baz: bool, } } +#[automatically_derived] +impl Foo { + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionResponse { + let request = kube::core::conversion::ConversionRequest::from_review(review) + .unwrap(); + let desired_api_version = ::from_str( + &request.desired_api_version, + ) + .expect( + &format!("invalid desired version for {} resource", stringify!(Foo)), + ); + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .expect("The passed object had no spec") + .clone(); + let kind = object + .get("kind") + .expect("The objected asked to convert has no kind"); + let api_version = object + .get("apiVersion") + .expect("The objected asked to convert has no apiVersion") + .as_str() + .expect( + "The apiVersion of the objected asked to convert wasn't a String", + ); + assert_eq!(kind, stringify!(Foo)); + let current_api_version = ::from_str(api_version) + .expect( + &format!("invalid current version for {} resource", stringify!(Foo)), + ); + match (¤t_api_version, &desired_api_version) {} + } + let response = kube::core::conversion::ConversionResponse::for_request(request); + response.success(converted) + } +} From 077fff41aae3af5dcf5c42c541d653c25b508a2f Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 15 May 2025 11:14:44 +0200 Subject: [PATCH 04/23] chore: Use snafu for ParseResourceVersionError --- Cargo.lock | 1 + crates/stackable-versioned-macros/Cargo.toml | 3 +- ..._macros__test__k8s_snapshots@basic.rs.snap | 136 +------ ...est__k8s_snapshots@crate_overrides.rs.snap | 136 +------ ...macros__test__k8s_snapshots@module.rs.snap | 272 +------------- ...est__k8s_snapshots@module_preserve.rs.snap | 344 +----------------- ...__test__k8s_snapshots@renamed_kind.rs.snap | 174 +-------- ...os__test__k8s_snapshots@shortnames.rs.snap | 56 +-- ...d_macros__test__k8s_snapshots@skip.rs.snap | 42 --- .../src/codegen/container/struct.rs | 4 +- crates/stackable-versioned/Cargo.toml | 2 + .../src/flux_converter/mod.rs | 20 +- crates/stackable-versioned/src/lib.rs | 2 +- 13 files changed, 28 insertions(+), 1164 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f32767639..b79e7e15e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3211,6 +3211,7 @@ dependencies = [ "schemars", "serde", "serde_json", + "snafu 0.8.5", "stackable-versioned-macros", ] diff --git a/crates/stackable-versioned-macros/Cargo.toml b/crates/stackable-versioned-macros/Cargo.toml index 20a5ef747..c199e3b6b 100644 --- a/crates/stackable-versioned-macros/Cargo.toml +++ b/crates/stackable-versioned-macros/Cargo.toml @@ -26,7 +26,7 @@ proc-macro = true [features] full = ["k8s", "flux-converter"] -k8s = ["dep:kube", "dep:k8s-openapi", "dep:snafu"] +k8s = ["dep:kube", "dep:k8s-openapi"] flux-converter = ["k8s"] [dependencies] @@ -38,7 +38,6 @@ itertools.workspace = true k8s-openapi = { workspace = true, optional = true } kube = { workspace = true, optional = true } proc-macro2.workspace = true -snafu = { workspace = true, optional = true } syn.workspace = true quote.workspace = true diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap index 53c73bfb7..4c569fd23 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap @@ -113,14 +113,14 @@ impl ::std::fmt::Display for Foo { } #[automatically_derived] impl ::std::str::FromStr for Foo { - type Err = stackable_versioned::UnknownResourceVersionError; + type Err = stackable_versioned::ParseResourceVersionError; fn from_str(version: &str) -> Result { match version { "v1alpha1" => Ok(Self::V1Alpha1), "v1beta1" => Ok(Self::V1Beta1), "v1" => Ok(Self::V1), _ => { - Err(stackable_versioned::UnknownResourceVersionError { + Err(stackable_versioned::ParseResourceVersionError::UnknownResourceVersion { version: version.to_string(), }) } @@ -146,135 +146,3 @@ impl Foo { ) } } -#[automatically_derived] -impl Foo { - pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionResponse { - let request = kube::core::conversion::ConversionRequest::from_review(review) - .unwrap(); - let desired_api_version = ::from_str( - &request.desired_api_version, - ) - .expect( - &format!("invalid desired version for {} resource", stringify!(Foo)), - ); - let mut converted: Vec = Vec::with_capacity( - request.objects.len(), - ); - for object in &request.objects { - let object_spec = object - .get("spec") - .expect("The passed object had no spec") - .clone(); - let kind = object - .get("kind") - .expect("The objected asked to convert has no kind"); - let api_version = object - .get("apiVersion") - .expect("The objected asked to convert has no apiVersion") - .as_str() - .expect( - "The apiVersion of the objected asked to convert wasn't a String", - ); - assert_eq!(kind, stringify!(Foo)); - let current_api_version = ::from_str(api_version) - .expect( - &format!("invalid current version for {} resource", stringify!(Foo)), - ); - match (¤t_api_version, &desired_api_version) { - (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Alpha1, Self::V1Beta1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1beta1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1beta1::FooSpec = resource.into(); - let resource: v1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Beta1, Self::V1Alpha1) => { - let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Beta1, Self::V1Beta1) => { - let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Beta1, Self::V1) => { - let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1, Self::V1Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1beta1::FooSpec = resource.into(); - let resource: v1alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1, Self::V1Beta1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1beta1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1, Self::V1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - } - } - let response = kube::core::conversion::ConversionResponse::for_request(request); - response.success(converted) - } -} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap index 15df09c4a..6fe38cae8 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap @@ -116,14 +116,14 @@ impl ::std::fmt::Display for Foo { } #[automatically_derived] impl ::std::str::FromStr for Foo { - type Err = stackable_versioned::UnknownResourceVersionError; + type Err = stackable_versioned::ParseResourceVersionError; fn from_str(version: &str) -> Result { match version { "v1alpha1" => Ok(Self::V1Alpha1), "v1beta1" => Ok(Self::V1Beta1), "v1" => Ok(Self::V1), _ => { - Err(stackable_versioned::UnknownResourceVersionError { + Err(stackable_versioned::ParseResourceVersionError::UnknownResourceVersion { version: version.to_string(), }) } @@ -149,135 +149,3 @@ impl Foo { ) } } -#[automatically_derived] -impl Foo { - pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionResponse { - let request = kube::core::conversion::ConversionRequest::from_review(review) - .unwrap(); - let desired_api_version = ::from_str( - &request.desired_api_version, - ) - .expect( - &format!("invalid desired version for {} resource", stringify!(Foo)), - ); - let mut converted: Vec = Vec::with_capacity( - request.objects.len(), - ); - for object in &request.objects { - let object_spec = object - .get("spec") - .expect("The passed object had no spec") - .clone(); - let kind = object - .get("kind") - .expect("The objected asked to convert has no kind"); - let api_version = object - .get("apiVersion") - .expect("The objected asked to convert has no apiVersion") - .as_str() - .expect( - "The apiVersion of the objected asked to convert wasn't a String", - ); - assert_eq!(kind, stringify!(Foo)); - let current_api_version = ::from_str(api_version) - .expect( - &format!("invalid current version for {} resource", stringify!(Foo)), - ); - match (¤t_api_version, &desired_api_version) { - (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Alpha1, Self::V1Beta1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1beta1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1beta1::FooSpec = resource.into(); - let resource: v1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Beta1, Self::V1Alpha1) => { - let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Beta1, Self::V1Beta1) => { - let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Beta1, Self::V1) => { - let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1, Self::V1Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1beta1::FooSpec = resource.into(); - let resource: v1alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1, Self::V1Beta1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1beta1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1, Self::V1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - } - } - let response = kube::core::conversion::ConversionResponse::for_request(request); - response.success(converted) - } -} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap index f45c32f3a..25404bc4d 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap @@ -230,14 +230,14 @@ impl ::std::fmt::Display for Foo { } #[automatically_derived] impl ::std::str::FromStr for Foo { - type Err = stackable_versioned::UnknownResourceVersionError; + type Err = stackable_versioned::ParseResourceVersionError; fn from_str(version: &str) -> Result { match version { "v1alpha1" => Ok(Self::V1Alpha1), "v1" => Ok(Self::V1), "v2alpha1" => Ok(Self::V2Alpha1), _ => { - Err(stackable_versioned::UnknownResourceVersionError { + Err(stackable_versioned::ParseResourceVersionError::UnknownResourceVersion { version: version.to_string(), }) } @@ -264,138 +264,6 @@ impl Foo { } } #[automatically_derived] -impl Foo { - pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionResponse { - let request = kube::core::conversion::ConversionRequest::from_review(review) - .unwrap(); - let desired_api_version = ::from_str( - &request.desired_api_version, - ) - .expect( - &format!("invalid desired version for {} resource", stringify!(Foo)), - ); - let mut converted: Vec = Vec::with_capacity( - request.objects.len(), - ); - for object in &request.objects { - let object_spec = object - .get("spec") - .expect("The passed object had no spec") - .clone(); - let kind = object - .get("kind") - .expect("The objected asked to convert has no kind"); - let api_version = object - .get("apiVersion") - .expect("The objected asked to convert has no apiVersion") - .as_str() - .expect( - "The apiVersion of the objected asked to convert wasn't a String", - ); - assert_eq!(kind, stringify!(Foo)); - let current_api_version = ::from_str(api_version) - .expect( - &format!("invalid current version for {} resource", stringify!(Foo)), - ); - match (¤t_api_version, &desired_api_version) { - (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Alpha1, Self::V2Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1::FooSpec = resource.into(); - let resource: v2alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1, Self::V1Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1, Self::V1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1, Self::V2Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v2alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V2Alpha1, Self::V1Alpha1) => { - let resource: v2alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1::FooSpec = resource.into(); - let resource: v1alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V2Alpha1, Self::V1) => { - let resource: v2alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - let resource: v1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V2Alpha1, Self::V2Alpha1) => { - let resource: v2alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - } - } - let response = kube::core::conversion::ConversionResponse::for_request(request); - response.success(converted) - } -} -#[automatically_derived] pub(crate) enum Bar { V1Alpha1, V1, @@ -416,14 +284,14 @@ impl ::std::fmt::Display for Bar { } #[automatically_derived] impl ::std::str::FromStr for Bar { - type Err = stackable_versioned::UnknownResourceVersionError; + type Err = stackable_versioned::ParseResourceVersionError; fn from_str(version: &str) -> Result { match version { "v1alpha1" => Ok(Self::V1Alpha1), "v1" => Ok(Self::V1), "v2alpha1" => Ok(Self::V2Alpha1), _ => { - Err(stackable_versioned::UnknownResourceVersionError { + Err(stackable_versioned::ParseResourceVersionError::UnknownResourceVersion { version: version.to_string(), }) } @@ -449,135 +317,3 @@ impl Bar { ) } } -#[automatically_derived] -impl Bar { - pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionResponse { - let request = kube::core::conversion::ConversionRequest::from_review(review) - .unwrap(); - let desired_api_version = ::from_str( - &request.desired_api_version, - ) - .expect( - &format!("invalid desired version for {} resource", stringify!(Bar)), - ); - let mut converted: Vec = Vec::with_capacity( - request.objects.len(), - ); - for object in &request.objects { - let object_spec = object - .get("spec") - .expect("The passed object had no spec") - .clone(); - let kind = object - .get("kind") - .expect("The objected asked to convert has no kind"); - let api_version = object - .get("apiVersion") - .expect("The objected asked to convert has no apiVersion") - .as_str() - .expect( - "The apiVersion of the objected asked to convert wasn't a String", - ); - assert_eq!(kind, stringify!(Bar)); - let current_api_version = ::from_str(api_version) - .expect( - &format!("invalid current version for {} resource", stringify!(Bar)), - ); - match (¤t_api_version, &desired_api_version) { - (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::BarSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Bar))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::BarSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Bar))); - let resource: v1::BarSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V1Alpha1, Self::V2Alpha1) => { - let resource: v1alpha1::BarSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Bar))); - let resource: v1::BarSpec = resource.into(); - let resource: v2alpha1::BarSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V1, Self::V1Alpha1) => { - let resource: v1::BarSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Bar))); - let resource: v1alpha1::BarSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V1, Self::V1) => { - let resource: v1::BarSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Bar))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V1, Self::V2Alpha1) => { - let resource: v1::BarSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Bar))); - let resource: v2alpha1::BarSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V2Alpha1, Self::V1Alpha1) => { - let resource: v2alpha1::BarSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Bar))); - let resource: v1::BarSpec = resource.into(); - let resource: v1alpha1::BarSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V2Alpha1, Self::V1) => { - let resource: v2alpha1::BarSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Bar))); - let resource: v1::BarSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V2Alpha1, Self::V2Alpha1) => { - let resource: v2alpha1::BarSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Bar))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - } - } - let response = kube::core::conversion::ConversionResponse::for_request(request); - response.success(converted) - } -} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap index 2d2524d78..0205342ad 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap @@ -218,14 +218,14 @@ pub(crate) mod versioned { } } impl ::std::str::FromStr for Foo { - type Err = stackable_versioned::UnknownResourceVersionError; + type Err = stackable_versioned::ParseResourceVersionError; fn from_str(version: &str) -> Result { match version { "v1alpha1" => Ok(Self::V1Alpha1), "v1" => Ok(Self::V1), "v2alpha1" => Ok(Self::V2Alpha1), _ => { - Err(stackable_versioned::UnknownResourceVersionError { + Err(stackable_versioned::ParseResourceVersionError::UnknownResourceVersion { version: version.to_string(), }) } @@ -250,174 +250,6 @@ pub(crate) mod versioned { ) } } - #[automatically_derived] - impl Foo { - pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionResponse { - let request = kube::core::conversion::ConversionRequest::from_review(review) - .unwrap(); - let desired_api_version = ::from_str( - &request.desired_api_version, - ) - .expect( - &format!("invalid desired version for {} resource", stringify!(Foo)), - ); - let mut converted: Vec = Vec::with_capacity( - request.objects.len(), - ); - for object in &request.objects { - let object_spec = object - .get("spec") - .expect("The passed object had no spec") - .clone(); - let kind = object - .get("kind") - .expect("The objected asked to convert has no kind"); - let api_version = object - .get("apiVersion") - .expect("The objected asked to convert has no apiVersion") - .as_str() - .expect( - "The apiVersion of the objected asked to convert wasn't a String", - ); - assert_eq!(kind, stringify!(Foo)); - let current_api_version = ::from_str( - api_version, - ) - .expect( - &format!( - "invalid current version for {} resource", stringify!(Foo) - ), - ); - match (¤t_api_version, &desired_api_version) { - (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( - object_spec, - ) - .expect( - &format!("Failed to deserialize {}", stringify!(Foo)), - ); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( - object_spec, - ) - .expect( - &format!("Failed to deserialize {}", stringify!(Foo)), - ); - let resource: v1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1Alpha1, Self::V2Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( - object_spec, - ) - .expect( - &format!("Failed to deserialize {}", stringify!(Foo)), - ); - let resource: v1::FooSpec = resource.into(); - let resource: v2alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1, Self::V1Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(Foo)), - ); - let resource: v1alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1, Self::V1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(Foo)), - ); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V1, Self::V2Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(Foo)), - ); - let resource: v2alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V2Alpha1, Self::V1Alpha1) => { - let resource: v2alpha1::FooSpec = serde_json::from_value( - object_spec, - ) - .expect( - &format!("Failed to deserialize {}", stringify!(Foo)), - ); - let resource: v1::FooSpec = resource.into(); - let resource: v1alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V2Alpha1, Self::V1) => { - let resource: v2alpha1::FooSpec = serde_json::from_value( - object_spec, - ) - .expect( - &format!("Failed to deserialize {}", stringify!(Foo)), - ); - let resource: v1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - (Self::V2Alpha1, Self::V2Alpha1) => { - let resource: v2alpha1::FooSpec = serde_json::from_value( - object_spec, - ) - .expect( - &format!("Failed to deserialize {}", stringify!(Foo)), - ); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - } - } - let response = kube::core::conversion::ConversionResponse::for_request( - request, - ); - response.success(converted) - } - } pub enum Bar { V1Alpha1, V1, @@ -436,14 +268,14 @@ pub(crate) mod versioned { } } impl ::std::str::FromStr for Bar { - type Err = stackable_versioned::UnknownResourceVersionError; + type Err = stackable_versioned::ParseResourceVersionError; fn from_str(version: &str) -> Result { match version { "v1alpha1" => Ok(Self::V1Alpha1), "v1" => Ok(Self::V1), "v2alpha1" => Ok(Self::V2Alpha1), _ => { - Err(stackable_versioned::UnknownResourceVersionError { + Err(stackable_versioned::ParseResourceVersionError::UnknownResourceVersion { version: version.to_string(), }) } @@ -468,172 +300,4 @@ pub(crate) mod versioned { ) } } - #[automatically_derived] - impl Bar { - pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionResponse { - let request = kube::core::conversion::ConversionRequest::from_review(review) - .unwrap(); - let desired_api_version = ::from_str( - &request.desired_api_version, - ) - .expect( - &format!("invalid desired version for {} resource", stringify!(Bar)), - ); - let mut converted: Vec = Vec::with_capacity( - request.objects.len(), - ); - for object in &request.objects { - let object_spec = object - .get("spec") - .expect("The passed object had no spec") - .clone(); - let kind = object - .get("kind") - .expect("The objected asked to convert has no kind"); - let api_version = object - .get("apiVersion") - .expect("The objected asked to convert has no apiVersion") - .as_str() - .expect( - "The apiVersion of the objected asked to convert wasn't a String", - ); - assert_eq!(kind, stringify!(Bar)); - let current_api_version = ::from_str( - api_version, - ) - .expect( - &format!( - "invalid current version for {} resource", stringify!(Bar) - ), - ); - match (¤t_api_version, &desired_api_version) { - (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::BarSpec = serde_json::from_value( - object_spec, - ) - .expect( - &format!("Failed to deserialize {}", stringify!(Bar)), - ); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::BarSpec = serde_json::from_value( - object_spec, - ) - .expect( - &format!("Failed to deserialize {}", stringify!(Bar)), - ); - let resource: v1::BarSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V1Alpha1, Self::V2Alpha1) => { - let resource: v1alpha1::BarSpec = serde_json::from_value( - object_spec, - ) - .expect( - &format!("Failed to deserialize {}", stringify!(Bar)), - ); - let resource: v1::BarSpec = resource.into(); - let resource: v2alpha1::BarSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V1, Self::V1Alpha1) => { - let resource: v1::BarSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(Bar)), - ); - let resource: v1alpha1::BarSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V1, Self::V1) => { - let resource: v1::BarSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(Bar)), - ); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V1, Self::V2Alpha1) => { - let resource: v1::BarSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(Bar)), - ); - let resource: v2alpha1::BarSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V2Alpha1, Self::V1Alpha1) => { - let resource: v2alpha1::BarSpec = serde_json::from_value( - object_spec, - ) - .expect( - &format!("Failed to deserialize {}", stringify!(Bar)), - ); - let resource: v1::BarSpec = resource.into(); - let resource: v1alpha1::BarSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V2Alpha1, Self::V1) => { - let resource: v2alpha1::BarSpec = serde_json::from_value( - object_spec, - ) - .expect( - &format!("Failed to deserialize {}", stringify!(Bar)), - ); - let resource: v1::BarSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - (Self::V2Alpha1, Self::V2Alpha1) => { - let resource: v2alpha1::BarSpec = serde_json::from_value( - object_spec, - ) - .expect( - &format!("Failed to deserialize {}", stringify!(Bar)), - ); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Bar))), - ); - } - } - } - let response = kube::core::conversion::ConversionResponse::for_request( - request, - ); - response.success(converted) - } - } } diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap index 8abe0659f..534fe35e5 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap @@ -113,14 +113,14 @@ impl ::std::fmt::Display for FooBar { } #[automatically_derived] impl ::std::str::FromStr for FooBar { - type Err = stackable_versioned::UnknownResourceVersionError; + type Err = stackable_versioned::ParseResourceVersionError; fn from_str(version: &str) -> Result { match version { "v1alpha1" => Ok(Self::V1Alpha1), "v1beta1" => Ok(Self::V1Beta1), "v1" => Ok(Self::V1), _ => { - Err(stackable_versioned::UnknownResourceVersionError { + Err(stackable_versioned::ParseResourceVersionError::UnknownResourceVersion { version: version.to_string(), }) } @@ -146,173 +146,3 @@ impl FooBar { ) } } -#[automatically_derived] -impl FooBar { - pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionResponse { - let request = kube::core::conversion::ConversionRequest::from_review(review) - .unwrap(); - let desired_api_version = ::from_str( - &request.desired_api_version, - ) - .expect( - &format!("invalid desired version for {} resource", stringify!(FooBar)), - ); - let mut converted: Vec = Vec::with_capacity( - request.objects.len(), - ); - for object in &request.objects { - let object_spec = object - .get("spec") - .expect("The passed object had no spec") - .clone(); - let kind = object - .get("kind") - .expect("The objected asked to convert has no kind"); - let api_version = object - .get("apiVersion") - .expect("The objected asked to convert has no apiVersion") - .as_str() - .expect( - "The apiVersion of the objected asked to convert wasn't a String", - ); - assert_eq!(kind, stringify!(FooBar)); - let current_api_version = ::from_str(api_version) - .expect( - &format!( - "invalid current version for {} resource", stringify!(FooBar) - ), - ); - match (¤t_api_version, &desired_api_version) { - (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(FooBar)), - ); - converted - .push( - serde_json::to_value(resource) - .expect( - &format!("Failed to serialize {}", stringify!(FooBar)), - ), - ); - } - (Self::V1Alpha1, Self::V1Beta1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(FooBar)), - ); - let resource: v1beta1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect( - &format!("Failed to serialize {}", stringify!(FooBar)), - ), - ); - } - (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(FooBar)), - ); - let resource: v1beta1::FooSpec = resource.into(); - let resource: v1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect( - &format!("Failed to serialize {}", stringify!(FooBar)), - ), - ); - } - (Self::V1Beta1, Self::V1Alpha1) => { - let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(FooBar)), - ); - let resource: v1alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect( - &format!("Failed to serialize {}", stringify!(FooBar)), - ), - ); - } - (Self::V1Beta1, Self::V1Beta1) => { - let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(FooBar)), - ); - converted - .push( - serde_json::to_value(resource) - .expect( - &format!("Failed to serialize {}", stringify!(FooBar)), - ), - ); - } - (Self::V1Beta1, Self::V1) => { - let resource: v1beta1::FooSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(FooBar)), - ); - let resource: v1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect( - &format!("Failed to serialize {}", stringify!(FooBar)), - ), - ); - } - (Self::V1, Self::V1Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(FooBar)), - ); - let resource: v1beta1::FooSpec = resource.into(); - let resource: v1alpha1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect( - &format!("Failed to serialize {}", stringify!(FooBar)), - ), - ); - } - (Self::V1, Self::V1Beta1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(FooBar)), - ); - let resource: v1beta1::FooSpec = resource.into(); - converted - .push( - serde_json::to_value(resource) - .expect( - &format!("Failed to serialize {}", stringify!(FooBar)), - ), - ); - } - (Self::V1, Self::V1) => { - let resource: v1::FooSpec = serde_json::from_value(object_spec) - .expect( - &format!("Failed to deserialize {}", stringify!(FooBar)), - ); - converted - .push( - serde_json::to_value(resource) - .expect( - &format!("Failed to serialize {}", stringify!(FooBar)), - ), - ); - } - } - } - let response = kube::core::conversion::ConversionResponse::for_request(request); - response.success(converted) - } -} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap index 8ed572d90..5ed1ac1cf 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap @@ -40,12 +40,12 @@ impl ::std::fmt::Display for Foo { } #[automatically_derived] impl ::std::str::FromStr for Foo { - type Err = stackable_versioned::UnknownResourceVersionError; + type Err = stackable_versioned::ParseResourceVersionError; fn from_str(version: &str) -> Result { match version { "v1alpha1" => Ok(Self::V1Alpha1), _ => { - Err(stackable_versioned::UnknownResourceVersionError { + Err(stackable_versioned::ParseResourceVersionError::UnknownResourceVersion { version: version.to_string(), }) } @@ -67,55 +67,3 @@ impl Foo { ) } } -#[automatically_derived] -impl Foo { - pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionResponse { - let request = kube::core::conversion::ConversionRequest::from_review(review) - .unwrap(); - let desired_api_version = ::from_str( - &request.desired_api_version, - ) - .expect( - &format!("invalid desired version for {} resource", stringify!(Foo)), - ); - let mut converted: Vec = Vec::with_capacity( - request.objects.len(), - ); - for object in &request.objects { - let object_spec = object - .get("spec") - .expect("The passed object had no spec") - .clone(); - let kind = object - .get("kind") - .expect("The objected asked to convert has no kind"); - let api_version = object - .get("apiVersion") - .expect("The objected asked to convert has no apiVersion") - .as_str() - .expect( - "The apiVersion of the objected asked to convert wasn't a String", - ); - assert_eq!(kind, stringify!(Foo)); - let current_api_version = ::from_str(api_version) - .expect( - &format!("invalid current version for {} resource", stringify!(Foo)), - ); - match (¤t_api_version, &desired_api_version) { - (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(Foo))); - converted - .push( - serde_json::to_value(resource) - .expect(&format!("Failed to serialize {}", stringify!(Foo))), - ); - } - } - } - let response = kube::core::conversion::ConversionResponse::for_request(request); - response.success(converted) - } -} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap index 5c804dd5a..39f0b2263 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap @@ -92,45 +92,3 @@ pub mod v1 { pub baz: bool, } } -#[automatically_derived] -impl Foo { - pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionResponse { - let request = kube::core::conversion::ConversionRequest::from_review(review) - .unwrap(); - let desired_api_version = ::from_str( - &request.desired_api_version, - ) - .expect( - &format!("invalid desired version for {} resource", stringify!(Foo)), - ); - let mut converted: Vec = Vec::with_capacity( - request.objects.len(), - ); - for object in &request.objects { - let object_spec = object - .get("spec") - .expect("The passed object had no spec") - .clone(); - let kind = object - .get("kind") - .expect("The objected asked to convert has no kind"); - let api_version = object - .get("apiVersion") - .expect("The objected asked to convert has no apiVersion") - .as_str() - .expect( - "The apiVersion of the objected asked to convert wasn't a String", - ); - assert_eq!(kind, stringify!(Foo)); - let current_api_version = ::from_str(api_version) - .expect( - &format!("invalid current version for {} resource", stringify!(Foo)), - ); - match (¤t_api_version, &desired_api_version) {} - } - let response = kube::core::conversion::ConversionResponse::for_request(request); - response.success(converted) - } -} diff --git a/crates/stackable-versioned-macros/src/codegen/container/struct.rs b/crates/stackable-versioned-macros/src/codegen/container/struct.rs index 5e790b73a..012a2634f 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/struct.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/struct.rs @@ -381,12 +381,12 @@ impl Struct { #automatically_derived impl ::std::str::FromStr for #enum_ident { - type Err = stackable_versioned::UnknownResourceVersionError; + type Err = stackable_versioned::ParseResourceVersionError; fn from_str(version: &str) -> Result { match version { #(#enum_variant_strings => Ok(Self::#enum_variant_idents),)* - _ => Err(stackable_versioned::UnknownResourceVersionError{version: version.to_string()}), + _ => Err(stackable_versioned::ParseResourceVersionError::UnknownResourceVersion{version: version.to_string()}), } } } diff --git a/crates/stackable-versioned/Cargo.toml b/crates/stackable-versioned/Cargo.toml index cf4d3a31a..b789d357d 100644 --- a/crates/stackable-versioned/Cargo.toml +++ b/crates/stackable-versioned/Cargo.toml @@ -23,6 +23,7 @@ flux-converter = [ "dep:serde", "dep:schemars", "dep:serde_json", + "dep:snafu", ] [dependencies] @@ -33,3 +34,4 @@ k8s-openapi = { workspace = true, optional = true } serde = { workspace = true, optional = true } schemars = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } +snafu = { workspace = true, optional = true } diff --git a/crates/stackable-versioned/src/flux_converter/mod.rs b/crates/stackable-versioned/src/flux_converter/mod.rs index 40cdc15a6..d37d6d250 100644 --- a/crates/stackable-versioned/src/flux_converter/mod.rs +++ b/crates/stackable-versioned/src/flux_converter/mod.rs @@ -3,23 +3,13 @@ //! It converts between different CRD versions by using 1.21 GW of power, //! 142km/h and time travel. -use std::fmt::Display; +use snafu::Snafu; #[cfg(test)] mod tests; -#[derive(Debug)] -pub struct UnknownResourceVersionError { - pub version: String, -} - -impl std::error::Error for UnknownResourceVersionError {} -impl Display for UnknownResourceVersionError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "The version {version} is not known", - version = self.version - ) - } +#[derive(Debug, Snafu)] +pub enum ParseResourceVersionError { + #[snafu(display("The resource version \"{version}\" is not known"))] + UnknownResourceVersion { version: String }, } diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index d610974cb..e7b20c837 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -18,7 +18,7 @@ pub use stackable_versioned_macros::*; mod flux_converter; #[cfg(feature = "flux-converter")] -pub use flux_converter::UnknownResourceVersionError; +pub use flux_converter::ParseResourceVersionError; // Unused for now, might get picked up again in the future. #[doc(hidden)] From 0383874ac9c2f2de81d7d636a76a835180261c87 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 15 May 2025 13:42:30 +0200 Subject: [PATCH 05/23] snafufication --- .../src/codegen/flux_converter.rs | 61 +++++++++++-------- .../src/flux_converter/mod.rs | 58 +++++++++++++++++- crates/stackable-versioned/src/lib.rs | 2 +- 3 files changed, 93 insertions(+), 28 deletions(-) diff --git a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs index 5eb2423b3..3cda2e6f0 100644 --- a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs +++ b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs @@ -26,15 +26,16 @@ pub(crate) fn generate_kubernetes_conversion( let src_lower = src_lower.parse::().expect("The versions always needs to be a valid TokenStream"); quote! { (Self::#src, Self::#dst) => { - let resource: #src_lower::#struct_ident = serde_json::from_value(object_spec) - .expect(&format!("Failed to deserialize {}", stringify!(#enum_ident))); + let resource: #src_lower::#struct_ident = serde_json::from_value(object_spec.clone()) + .map_err(|err| ConversionError::DeserializeObjectSpec{source: err, kind: stringify!(#enum_ident).to_string()})?; #( let resource: #version_chain_string::#struct_ident = resource.into(); )* converted.push( - serde_json::to_value(resource).expect(&format!("Failed to serialize {}", stringify!(#enum_ident))) + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec{source: err, kind: stringify!(#enum_ident).to_string()})? ); }} }, @@ -44,38 +45,46 @@ pub(crate) fn generate_kubernetes_conversion( #[automatically_derived] impl #enum_ident { pub fn convert(review: kube::core::conversion::ConversionReview) -> kube::core::conversion::ConversionResponse { + Self::try_convert(review).expect("Self::try_convert failed") + } + + fn try_convert(review: kube::core::conversion::ConversionReview) -> Result { + // Intentionally not using `snafu::ResultExt` here to keep the number of dependencies minimal + use stackable_versioned::ConversionError; + let request = kube::core::conversion::ConversionRequest::from_review(review) - .unwrap(); - let desired_api_version = ::from_str(&request.desired_api_version) - .expect(&format!("invalid desired version for {} resource", stringify!(#enum_ident))); + .map_err(|err| ConversionError::ConvertReviewToRequest{source: err})?; + let desired_object_version = ::from_str(&request.desired_api_version) + .map_err(|err| ConversionError::ParseDesiredResourceVersion{ + source: err, + version: request.desired_api_version.to_string() + })?; let mut converted: Vec = Vec::with_capacity(request.objects.len()); for object in &request.objects { - let object_spec = object - .get("spec") - .expect("The passed object had no spec") - .clone(); - let kind = object - .get("kind") - .expect("The objected asked to convert has no kind"); - let api_version = object - .get("apiVersion") - .expect("The objected asked to convert has no apiVersion") - .as_str() - .expect("The apiVersion of the objected asked to convert wasn't a String"); - - assert_eq!(kind, stringify!(#enum_ident)); - - let current_api_version = ::from_str(api_version) - .expect(&format!("invalid current version for {} resource", stringify!(#enum_ident))); - - match (¤t_api_version, &desired_api_version) { + let object_spec = object.get("spec").ok_or_else(|| ConversionError::ObjectHasNoSpec{})?; + let object_kind = object.get("kind").ok_or_else(|| ConversionError::ObjectHasNoKind{})?; + let object_kind = object_kind.as_str().ok_or_else(|| ConversionError::ObjectKindNotString{kind: object_kind.clone()})?; + let object_version = object.get("apiVersion").ok_or_else(|| ConversionError::ObjectHasNoApiVersion{})?; + let object_version = object_version.as_str().ok_or_else(|| ConversionError::ObjectApiVersionNotString{api_version: object_version.clone()})?; + + if object_kind != stringify!(#enum_ident) { + return Err(ConversionError::WrongObjectKind{expected_kind: stringify!(#enum_ident).to_string(), send_kind: object_kind.to_string()}); + } + + let current_object_version = ::from_str(object_version) + .map_err(|err| ConversionError::ParseCurrentResourceVersion{ + source: err, + version: object_version.to_string() + })?; + + match (¤t_object_version, &desired_object_version) { #(#matches),* } } let response = kube::core::conversion::ConversionResponse::for_request(request); - response.success(converted) + Ok(response.success(converted)) } } }) diff --git a/crates/stackable-versioned/src/flux_converter/mod.rs b/crates/stackable-versioned/src/flux_converter/mod.rs index d37d6d250..0dafe780b 100644 --- a/crates/stackable-versioned/src/flux_converter/mod.rs +++ b/crates/stackable-versioned/src/flux_converter/mod.rs @@ -3,6 +3,7 @@ //! It converts between different CRD versions by using 1.21 GW of power, //! 142km/h and time travel. +use kube::core::conversion::ConvertConversionReviewError; use snafu::Snafu; #[cfg(test)] @@ -10,6 +11,61 @@ mod tests; #[derive(Debug, Snafu)] pub enum ParseResourceVersionError { - #[snafu(display("The resource version \"{version}\" is not known"))] + #[snafu(display("the resource version \"{version}\" is not known"))] UnknownResourceVersion { version: String }, } + +#[derive(Debug, Snafu)] +pub enum ConversionError { + #[snafu(display("failed to convert ConversionReview to ConversionRequest"))] + ConvertReviewToRequest { + source: ConvertConversionReviewError, + }, + + #[snafu(display("failed to parse current resource version \"{version}\""))] + ParseCurrentResourceVersion { + source: ParseResourceVersionError, + version: String, + }, + + #[snafu(display("failed to parse desired resource version \"{version}\""))] + ParseDesiredResourceVersion { + source: ParseResourceVersionError, + version: String, + }, + + #[snafu(display("the object send for conversion has no \"spec\" field"))] + ObjectHasNoSpec {}, + + #[snafu(display("the object send for conversion has no \"kind\" field"))] + ObjectHasNoKind {}, + + #[snafu(display("the object send for conversion has no \"apiVersion\" field"))] + ObjectHasNoApiVersion {}, + + #[snafu(display("the \"kind\" field of the object send for conversion isn't a String"))] + ObjectKindNotString { kind: serde_json::Value }, + + #[snafu(display("the \"apiVersion\" field of the object send for conversion isn't a String"))] + ObjectApiVersionNotString { api_version: serde_json::Value }, + + #[snafu(display( + "I was asked to convert the kind \"{expected_kind}\", but I can only convert objects of kind \"{send_kind}\"" + ))] + WrongObjectKind { + expected_kind: String, + send_kind: String, + }, + + #[snafu(display("failed to deserialize object of kind \"{kind}\""))] + DeserializeObjectSpec { + source: serde_json::Error, + kind: String, + }, + + #[snafu(display("failed to serialize object of kind \"{kind}\""))] + SerializeObjectSpec { + source: serde_json::Error, + kind: String, + }, +} diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index e7b20c837..540b5fd7a 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -18,7 +18,7 @@ pub use stackable_versioned_macros::*; mod flux_converter; #[cfg(feature = "flux-converter")] -pub use flux_converter::ParseResourceVersionError; +pub use flux_converter::{ConversionError, ParseResourceVersionError}; // Unused for now, might get picked up again in the future. #[doc(hidden)] From c52ec128fe7fbbd0a6f20d0a2ff8a885209706e9 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 15 May 2025 16:24:47 +0200 Subject: [PATCH 06/23] Convert errors to correct respondes --- .../src/codegen/flux_converter.rs | 52 ++++++++++++++++--- .../src/flux_converter/mod.rs | 38 +++++++++++--- 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs index 3cda2e6f0..c3d0fa6d4 100644 --- a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs +++ b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs @@ -45,15 +45,54 @@ pub(crate) fn generate_kubernetes_conversion( #[automatically_derived] impl #enum_ident { pub fn convert(review: kube::core::conversion::ConversionReview) -> kube::core::conversion::ConversionResponse { - Self::try_convert(review).expect("Self::try_convert failed") + // Intentionally not using `snafu::ResultExt` here to keep the number of dependencies minimal + use kube::core::conversion::{ConversionRequest, ConversionResponse}; + use kube::core::response::StatusSummary; + use stackable_versioned::ConversionError; + + let request = match ConversionRequest::from_review(review) { + Ok(request) => request, + Err(err) => { + return ConversionResponse::invalid( + kube::client::Status { + status: Some(StatusSummary::Failure), + code: 400, + message: format!("The ConversionReview send did not include any request: {err}"), + reason: "ConversionReview request missing".to_string(), + details: None, + }, + ); + } + }; + + let converted = Self::try_convert(&request); + + let conversion_response = ConversionResponse::for_request(request); + match converted { + Ok(converted) => { + conversion_response.success(converted) + }, + Err(err) => { + let error_message = err.as_human_readable_error_message(); + + conversion_response.failure( + kube::client::Status { + status: Some(StatusSummary::Success), + code: err.http_return_code(), + message: error_message.clone(), + reason: error_message, + details: None, + }, + ) + } + } } - fn try_convert(review: kube::core::conversion::ConversionReview) -> Result { - // Intentionally not using `snafu::ResultExt` here to keep the number of dependencies minimal + fn try_convert(request: &kube::core::conversion::ConversionRequest) -> Result, stackable_versioned::ConversionError> { use stackable_versioned::ConversionError; - let request = kube::core::conversion::ConversionRequest::from_review(review) - .map_err(|err| ConversionError::ConvertReviewToRequest{source: err})?; + // FIXME: Check that request.types.{kind,api_version} match the expected values + let desired_object_version = ::from_str(&request.desired_api_version) .map_err(|err| ConversionError::ParseDesiredResourceVersion{ source: err, @@ -83,8 +122,7 @@ pub(crate) fn generate_kubernetes_conversion( } } - let response = kube::core::conversion::ConversionResponse::for_request(request); - Ok(response.success(converted)) + Ok(converted) } } }) diff --git a/crates/stackable-versioned/src/flux_converter/mod.rs b/crates/stackable-versioned/src/flux_converter/mod.rs index 0dafe780b..dfa6e5df2 100644 --- a/crates/stackable-versioned/src/flux_converter/mod.rs +++ b/crates/stackable-versioned/src/flux_converter/mod.rs @@ -3,7 +3,8 @@ //! It converts between different CRD versions by using 1.21 GW of power, //! 142km/h and time travel. -use kube::core::conversion::ConvertConversionReviewError; +use std::{error::Error, fmt::Write}; + use snafu::Snafu; #[cfg(test)] @@ -17,11 +18,6 @@ pub enum ParseResourceVersionError { #[derive(Debug, Snafu)] pub enum ConversionError { - #[snafu(display("failed to convert ConversionReview to ConversionRequest"))] - ConvertReviewToRequest { - source: ConvertConversionReviewError, - }, - #[snafu(display("failed to parse current resource version \"{version}\""))] ParseCurrentResourceVersion { source: ParseResourceVersionError, @@ -69,3 +65,33 @@ pub enum ConversionError { kind: String, }, } + +impl ConversionError { + pub fn http_return_code(&self) -> u16 { + match &self { + ConversionError::ParseCurrentResourceVersion { .. } => 500, + ConversionError::ParseDesiredResourceVersion { .. } => 500, + ConversionError::ObjectHasNoSpec {} => 400, + ConversionError::ObjectHasNoKind {} => 400, + ConversionError::ObjectHasNoApiVersion {} => 400, + ConversionError::ObjectKindNotString { .. } => 400, + ConversionError::ObjectApiVersionNotString { .. } => 400, + ConversionError::WrongObjectKind { .. } => 400, + ConversionError::DeserializeObjectSpec { .. } => 500, + ConversionError::SerializeObjectSpec { .. } => 500, + } + } + + pub fn as_human_readable_error_message(&self) -> String { + let mut error_message = String::new(); + write!(error_message, "{self}").expect("Writing to Strings can not fail"); + + let mut source = self.source(); + while let Some(err) = source { + write!(error_message, ": {err}").expect("Writing to Strings can not fail"); + source = err.source(); + } + + error_message + } +} From c57e8c066f788bedf7420e18dca6c0ae6a349b83 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 15 May 2025 17:25:26 +0200 Subject: [PATCH 07/23] test: Add some tests --- Cargo.lock | 1 + .../src/codegen/flux_converter.rs | 2 +- crates/stackable-versioned/Cargo.toml | 3 + .../fixtures/inputs/fail/request_missing.json | 4 ++ .../fail/undeserializable_missing_field.json | 20 ++++++ .../inputs/fail/unkown_current_version.json | 22 +++++++ .../inputs/fail/unkown_desired_version.json | 22 +++++++ .../fixtures/inputs/fail/wrong_object.json | 22 +++++++ .../inputs/pass/persons_to_v1alpha1.json | 22 +++++++ .../fixtures/inputs/pass/persons_to_v3.json | 22 +++++++ .../src/flux_converter/mod.rs | 2 +- .../src/flux_converter/tests/mod.rs | 61 +++++++++++++------ ...sts__tests__fail@request_missing.json.snap | 15 +++++ ...l@undeserializable_missing_field.json.snap | 15 +++++ ...sts__fail@unkown_current_version.json.snap | 15 +++++ ...sts__fail@unkown_desired_version.json.snap | 15 +++++ ..._tests__tests__fail@wrong_object.json.snap | 15 +++++ ..._tests__pass@persons_to_v1alpha1.json.snap | 16 +++++ ...tests__tests__pass@persons_to_v3.json.snap | 19 ++++++ 19 files changed, 291 insertions(+), 22 deletions(-) create mode 100644 crates/stackable-versioned/fixtures/inputs/fail/request_missing.json create mode 100644 crates/stackable-versioned/fixtures/inputs/fail/undeserializable_missing_field.json create mode 100644 crates/stackable-versioned/fixtures/inputs/fail/unkown_current_version.json create mode 100644 crates/stackable-versioned/fixtures/inputs/fail/unkown_desired_version.json create mode 100644 crates/stackable-versioned/fixtures/inputs/fail/wrong_object.json create mode 100644 crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json create mode 100644 crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json create mode 100644 crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@request_missing.json.snap create mode 100644 crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@undeserializable_missing_field.json.snap create mode 100644 crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_current_version.json.snap create mode 100644 crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_desired_version.json.snap create mode 100644 crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@wrong_object.json.snap create mode 100644 crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v1alpha1.json.snap create mode 100644 crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v3.json.snap diff --git a/Cargo.lock b/Cargo.lock index b79e7e15e..103edd301 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3206,6 +3206,7 @@ dependencies = [ name = "stackable-versioned" version = "0.7.1" dependencies = [ + "insta", "k8s-openapi", "kube", "schemars", diff --git a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs index c3d0fa6d4..fc62f0ebd 100644 --- a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs +++ b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs @@ -77,7 +77,7 @@ pub(crate) fn generate_kubernetes_conversion( conversion_response.failure( kube::client::Status { - status: Some(StatusSummary::Success), + status: Some(StatusSummary::Failure), code: err.http_return_code(), message: error_message.clone(), reason: error_message, diff --git a/crates/stackable-versioned/Cargo.toml b/crates/stackable-versioned/Cargo.toml index b789d357d..4ccf12343 100644 --- a/crates/stackable-versioned/Cargo.toml +++ b/crates/stackable-versioned/Cargo.toml @@ -35,3 +35,6 @@ serde = { workspace = true, optional = true } schemars = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } snafu = { workspace = true, optional = true } + +[dev-dependencies] +insta.workspace = true diff --git a/crates/stackable-versioned/fixtures/inputs/fail/request_missing.json b/crates/stackable-versioned/fixtures/inputs/fail/request_missing.json new file mode 100644 index 000000000..b5759bcdb --- /dev/null +++ b/crates/stackable-versioned/fixtures/inputs/fail/request_missing.json @@ -0,0 +1,4 @@ +{ + "kind": "ConversionReview", + "apiVersion": "apiextensions.k8s.io/v1" +} diff --git a/crates/stackable-versioned/fixtures/inputs/fail/undeserializable_missing_field.json b/crates/stackable-versioned/fixtures/inputs/fail/undeserializable_missing_field.json new file mode 100644 index 000000000..33bca0d9b --- /dev/null +++ b/crates/stackable-versioned/fixtures/inputs/fail/undeserializable_missing_field.json @@ -0,0 +1,20 @@ +{ + "kind": "ConversionReview", + "apiVersion": "apiextensions.k8s.io/v1", + "request": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "desiredAPIVersion": "v3", + "objects": [ + { + "apiVersion": "v1alpha1", + "kind": "Person", + "metadata": { + "name": "sbernauer", + "namespace": "default", + "uid": "d41e2019-5de3-409c-a7b2-0799ecb95e4b" + }, + "spec": {} + } + ] + } +} diff --git a/crates/stackable-versioned/fixtures/inputs/fail/unkown_current_version.json b/crates/stackable-versioned/fixtures/inputs/fail/unkown_current_version.json new file mode 100644 index 000000000..5b7c29e80 --- /dev/null +++ b/crates/stackable-versioned/fixtures/inputs/fail/unkown_current_version.json @@ -0,0 +1,22 @@ +{ + "kind": "ConversionReview", + "apiVersion": "apiextensions.k8s.io/v1", + "request": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "desiredAPIVersion": "v3", + "objects": [ + { + "apiVersion": "v99", + "kind": "Person", + "metadata": { + "name": "sbernauer", + "namespace": "default", + "uid": "d41e2019-5de3-409c-a7b2-0799ecb95e4b" + }, + "spec": { + "username": "sbernauer" + } + } + ] + } +} diff --git a/crates/stackable-versioned/fixtures/inputs/fail/unkown_desired_version.json b/crates/stackable-versioned/fixtures/inputs/fail/unkown_desired_version.json new file mode 100644 index 000000000..17d903f43 --- /dev/null +++ b/crates/stackable-versioned/fixtures/inputs/fail/unkown_desired_version.json @@ -0,0 +1,22 @@ +{ + "kind": "ConversionReview", + "apiVersion": "apiextensions.k8s.io/v1", + "request": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "desiredAPIVersion": "v99", + "objects": [ + { + "apiVersion": "v1alpha1", + "kind": "Person", + "metadata": { + "name": "sbernauer", + "namespace": "default", + "uid": "d41e2019-5de3-409c-a7b2-0799ecb95e4b" + }, + "spec": { + "username": "sbernauer" + } + } + ] + } +} diff --git a/crates/stackable-versioned/fixtures/inputs/fail/wrong_object.json b/crates/stackable-versioned/fixtures/inputs/fail/wrong_object.json new file mode 100644 index 000000000..0e8b24f2e --- /dev/null +++ b/crates/stackable-versioned/fixtures/inputs/fail/wrong_object.json @@ -0,0 +1,22 @@ +{ + "kind": "ConversionReview", + "apiVersion": "apiextensions.k8s.io/v1", + "request": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "desiredAPIVersion": "v3", + "objects": [ + { + "apiVersion": "v1alpha1", + "kind": "SomeOtherResource", + "metadata": { + "name": "sbernauer", + "namespace": "default", + "uid": "d41e2019-5de3-409c-a7b2-0799ecb95e4b" + }, + "spec": { + "username": "sbernauer" + } + } + ] + } +} diff --git a/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json b/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json new file mode 100644 index 000000000..6edf1e117 --- /dev/null +++ b/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json @@ -0,0 +1,22 @@ +{ + "kind": "ConversionReview", + "apiVersion": "apiextensions.k8s.io/v1", + "request": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "desiredAPIVersion": "v1alpha1", + "objects": [ + { + "apiVersion": "v1alpha1", + "kind": "Person", + "metadata": { + "name": "sbernauer", + "namespace": "default", + "uid": "d41e2019-5de3-409c-a7b2-0799ecb95e4b" + }, + "spec": { + "username": "sbernauer" + } + } + ] + } +} diff --git a/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json b/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json new file mode 100644 index 000000000..09378fa38 --- /dev/null +++ b/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json @@ -0,0 +1,22 @@ +{ + "kind": "ConversionReview", + "apiVersion": "apiextensions.k8s.io/v1", + "request": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "desiredAPIVersion": "v3", + "objects": [ + { + "apiVersion": "v1alpha1", + "kind": "Person", + "metadata": { + "name": "sbernauer", + "namespace": "default", + "uid": "d41e2019-5de3-409c-a7b2-0799ecb95e4b" + }, + "spec": { + "username": "sbernauer" + } + } + ] + } +} diff --git a/crates/stackable-versioned/src/flux_converter/mod.rs b/crates/stackable-versioned/src/flux_converter/mod.rs index dfa6e5df2..81ece8596 100644 --- a/crates/stackable-versioned/src/flux_converter/mod.rs +++ b/crates/stackable-versioned/src/flux_converter/mod.rs @@ -46,7 +46,7 @@ pub enum ConversionError { ObjectApiVersionNotString { api_version: serde_json::Value }, #[snafu(display( - "I was asked to convert the kind \"{expected_kind}\", but I can only convert objects of kind \"{send_kind}\"" + "I was asked to convert the kind \"{send_kind}\", but I can only convert objects of kind \"{expected_kind}\"" ))] WrongObjectKind { expected_kind: String, diff --git a/crates/stackable-versioned/src/flux_converter/tests/mod.rs b/crates/stackable-versioned/src/flux_converter/tests/mod.rs index eccbcb5e1..9edce10d6 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/mod.rs +++ b/crates/stackable-versioned/src/flux_converter/tests/mod.rs @@ -118,31 +118,52 @@ impl From for v2::PersonSpec { #[cfg(test)] mod tests { + use std::{fs::File, path::Path}; + + use insta::{assert_snapshot, glob}; + use kube::core::{ + conversion::{ConversionResponse, ConversionReview}, + response::StatusSummary, + }; + use super::Person; #[test] - fn parse_simple_example_from_k8s() { - // this file contains dump of real request generated by kubernetes v1.22 - // Copied from https://github.com/kube-rs/kube/blob/main/kube-core/src/conversion/test_data/simple.json - let data = include_str!("./test_data/simple_from_k8s.json"); - // check that we can parse this review, and all chain of conversion worls - let review = serde_json::from_str(data).expect("invalid ConversionReview"); - let request = kube::core::conversion::ConversionRequest::from_review(review) - .expect("failed to get conversion request from review"); - let response = kube::core::conversion::ConversionResponse::for_request(request); - let _ = response.into_review(); + fn pass() { + glob!("../../../fixtures/inputs/pass/", "*.json", |path| { + let (review, response) = run_for_file(path); + + assert_eq!(response.result.status, Some(StatusSummary::Success)); + assert_eq!(review.request.unwrap().uid, response.uid); + + let formatted = serde_json::to_string_pretty(&response) + .expect("Failed to serialize ConversionResponse"); + assert_snapshot!(formatted); + }) } #[test] - fn test_macro() { - let data = include_str!("./test_data/convert_person_to_v2.json"); - let review: kube::core::conversion::ConversionReview = - serde_json::from_str(data).expect("invalid ConversionReview"); - - let response = Person::convert(review); - assert_eq!( - serde_json::to_string(&response).unwrap(), - "{\"uid\":\"c4e55572-ee1f-4e94-9097-28936985d45f\",\"result\":{\"status\":\"Success\"},\"convertedObjects\":[{\"firstName\":\"\",\"gender\":\"Unknown\",\"lastName\":\"\",\"username\":\"sbernauer\"}]}" - ); + fn fail() { + glob!("../../../fixtures/inputs/fail/", "*.json", |path| { + let (review, response) = run_for_file(path); + + assert_eq!(response.result.status, Some(StatusSummary::Failure)); + if let Some(request) = &review.request { + assert_eq!(request.uid, response.uid); + } + + let formatted = serde_json::to_string_pretty(&response) + .expect("Failed to serialize ConversionResponse"); + assert_snapshot!(formatted); + }) + } + + fn run_for_file(path: &Path) -> (ConversionReview, ConversionResponse) { + let review: ConversionReview = + serde_json::from_reader(File::open(path).expect("failed to open test file")) + .expect("failed to parse ConversionReview from test file"); + let response = Person::convert(review.clone()); + + (review, response) } } diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@request_missing.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@request_missing.json.snap new file mode 100644 index 000000000..cfed7020d --- /dev/null +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@request_missing.json.snap @@ -0,0 +1,15 @@ +--- +source: crates/stackable-versioned/src/flux_converter/tests/mod.rs +expression: formatted +input_file: crates/stackable-versioned/fixtures/inputs/fail/request_missing.json +--- +{ + "uid": "", + "result": { + "status": "Failure", + "code": 400, + "message": "The ConversionReview send did not include any request: request missing in ConversionReview", + "reason": "ConversionReview request missing" + }, + "convertedObjects": [] +} diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@undeserializable_missing_field.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@undeserializable_missing_field.json.snap new file mode 100644 index 000000000..2079f0cb4 --- /dev/null +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@undeserializable_missing_field.json.snap @@ -0,0 +1,15 @@ +--- +source: crates/stackable-versioned/src/flux_converter/tests/mod.rs +expression: formatted +input_file: crates/stackable-versioned/fixtures/inputs/fail/undeserializable_missing_field.json +--- +{ + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "result": { + "status": "Failure", + "code": 500, + "message": "failed to deserialize object of kind \"Person\": missing field `username`", + "reason": "failed to deserialize object of kind \"Person\": missing field `username`" + }, + "convertedObjects": [] +} diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_current_version.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_current_version.json.snap new file mode 100644 index 000000000..98bf3008a --- /dev/null +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_current_version.json.snap @@ -0,0 +1,15 @@ +--- +source: crates/stackable-versioned/src/flux_converter/tests/mod.rs +expression: formatted +input_file: crates/stackable-versioned/fixtures/inputs/fail/unkown_current_version.json +--- +{ + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "result": { + "status": "Failure", + "code": 500, + "message": "failed to parse current resource version \"v99\": the resource version \"v99\" is not known", + "reason": "failed to parse current resource version \"v99\": the resource version \"v99\" is not known" + }, + "convertedObjects": [] +} diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_desired_version.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_desired_version.json.snap new file mode 100644 index 000000000..f3ebe1f7f --- /dev/null +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_desired_version.json.snap @@ -0,0 +1,15 @@ +--- +source: crates/stackable-versioned/src/flux_converter/tests/mod.rs +expression: formatted +input_file: crates/stackable-versioned/fixtures/inputs/fail/unkown_desired_version.json +--- +{ + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "result": { + "status": "Failure", + "code": 500, + "message": "failed to parse desired resource version \"v99\": the resource version \"v99\" is not known", + "reason": "failed to parse desired resource version \"v99\": the resource version \"v99\" is not known" + }, + "convertedObjects": [] +} diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@wrong_object.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@wrong_object.json.snap new file mode 100644 index 000000000..e4c3f6617 --- /dev/null +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@wrong_object.json.snap @@ -0,0 +1,15 @@ +--- +source: crates/stackable-versioned/src/flux_converter/tests/mod.rs +expression: formatted +input_file: crates/stackable-versioned/fixtures/inputs/fail/wrong_object.json +--- +{ + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "result": { + "status": "Failure", + "code": 400, + "message": "I was asked to convert the kind \"SomeOtherResource\", but I can only convert objects of kind \"Person\"", + "reason": "I was asked to convert the kind \"SomeOtherResource\", but I can only convert objects of kind \"Person\"" + }, + "convertedObjects": [] +} diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v1alpha1.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v1alpha1.json.snap new file mode 100644 index 000000000..78a9e8470 --- /dev/null +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v1alpha1.json.snap @@ -0,0 +1,16 @@ +--- +source: crates/stackable-versioned/src/flux_converter/tests/mod.rs +expression: formatted +input_file: crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json +--- +{ + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "result": { + "status": "Success" + }, + "convertedObjects": [ + { + "username": "sbernauer" + } + ] +} diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v3.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v3.json.snap new file mode 100644 index 000000000..0ca358317 --- /dev/null +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v3.json.snap @@ -0,0 +1,19 @@ +--- +source: crates/stackable-versioned/src/flux_converter/tests/mod.rs +expression: formatted +input_file: crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json +--- +{ + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "result": { + "status": "Success" + }, + "convertedObjects": [ + { + "firstName": "", + "gender": "Unknown", + "lastName": "", + "username": "sbernauer" + } + ] +} From cf859db8d06f5209957bd42cae755d2898a88eb7 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 15 May 2025 17:32:06 +0200 Subject: [PATCH 08/23] test: Improve tests --- .../inputs/pass/persons_to_v1alpha1.json | 38 +++++++++++++++++++ .../fixtures/inputs/pass/persons_to_v3.json | 38 +++++++++++++++++++ .../src/flux_converter/tests/mod.rs | 24 ++++++++---- ..._tests__pass@persons_to_v1alpha1.json.snap | 12 ++++++ ...tests__tests__pass@persons_to_v3.json.snap | 24 ++++++++++++ 5 files changed, 128 insertions(+), 8 deletions(-) diff --git a/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json b/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json index 6edf1e117..c27a60f4b 100644 --- a/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json +++ b/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json @@ -16,6 +16,44 @@ "spec": { "username": "sbernauer" } + }, + { + "apiVersion": "v1alpha2", + "kind": "Person", + "spec": { + "username": "sbernauer", + "firstName": "Sebastian", + "lastName": "Bernauer" + } + }, + { + "apiVersion": "v1beta1", + "kind": "Person", + "spec": { + "username": "sbernauer", + "firstName": "Sebastian", + "lastName": "Bernauer" + } + }, + { + "apiVersion": "v2", + "kind": "Person", + "spec": { + "username": "sbernauer", + "firstName": "Sebastian", + "lastName": "Bernauer", + "gender": "Male" + } + }, + { + "apiVersion": "v3", + "kind": "Person", + "spec": { + "username": "sbernauer", + "firstName": "Sebastian", + "lastName": "Bernauer", + "gender": "Male" + } } ] } diff --git a/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json b/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json index 09378fa38..fa678172f 100644 --- a/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json +++ b/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json @@ -16,6 +16,44 @@ "spec": { "username": "sbernauer" } + }, + { + "apiVersion": "v1alpha2", + "kind": "Person", + "spec": { + "username": "sbernauer", + "firstName": "Sebastian", + "lastName": "Bernauer" + } + }, + { + "apiVersion": "v1beta1", + "kind": "Person", + "spec": { + "username": "sbernauer", + "firstName": "Sebastian", + "lastName": "Bernauer" + } + }, + { + "apiVersion": "v2", + "kind": "Person", + "spec": { + "username": "sbernauer", + "firstName": "Sebastian", + "lastName": "Bernauer", + "gender": "Male" + } + }, + { + "apiVersion": "v3", + "kind": "Person", + "spec": { + "username": "sbernauer", + "firstName": "Sebastian", + "lastName": "Bernauer", + "gender": "Male" + } } ] } diff --git a/crates/stackable-versioned/src/flux_converter/tests/mod.rs b/crates/stackable-versioned/src/flux_converter/tests/mod.rs index 9edce10d6..873a49ed7 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/mod.rs +++ b/crates/stackable-versioned/src/flux_converter/tests/mod.rs @@ -133,12 +133,16 @@ mod tests { glob!("../../../fixtures/inputs/pass/", "*.json", |path| { let (review, response) = run_for_file(path); - assert_eq!(response.result.status, Some(StatusSummary::Success)); - assert_eq!(review.request.unwrap().uid, response.uid); - let formatted = serde_json::to_string_pretty(&response) .expect("Failed to serialize ConversionResponse"); assert_snapshot!(formatted); + + assert_eq!( + response.result.status, + Some(StatusSummary::Success), + "File {path:?} should be converted successfully" + ); + assert_eq!(review.request.unwrap().uid, response.uid); }) } @@ -147,14 +151,18 @@ mod tests { glob!("../../../fixtures/inputs/fail/", "*.json", |path| { let (review, response) = run_for_file(path); - assert_eq!(response.result.status, Some(StatusSummary::Failure)); - if let Some(request) = &review.request { - assert_eq!(request.uid, response.uid); - } - let formatted = serde_json::to_string_pretty(&response) .expect("Failed to serialize ConversionResponse"); assert_snapshot!(formatted); + + assert_eq!( + response.result.status, + Some(StatusSummary::Failure), + "File {path:?} should *not* be converted successfully" + ); + if let Some(request) = &review.request { + assert_eq!(request.uid, response.uid); + } }) } diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v1alpha1.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v1alpha1.json.snap index 78a9e8470..98a2e8ba0 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v1alpha1.json.snap +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v1alpha1.json.snap @@ -9,6 +9,18 @@ input_file: crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1. "status": "Success" }, "convertedObjects": [ + { + "username": "sbernauer" + }, + { + "username": "sbernauer" + }, + { + "username": "sbernauer" + }, + { + "username": "sbernauer" + }, { "username": "sbernauer" } diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v3.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v3.json.snap index 0ca358317..5a8bfccd3 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v3.json.snap +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v3.json.snap @@ -14,6 +14,30 @@ input_file: crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json "gender": "Unknown", "lastName": "", "username": "sbernauer" + }, + { + "firstName": "Sebastian", + "gender": "Unknown", + "lastName": "Bernauer", + "username": "sbernauer" + }, + { + "firstName": "Sebastian", + "gender": "Unknown", + "lastName": "Bernauer", + "username": "sbernauer" + }, + { + "firstName": "Sebastian", + "gender": "Male", + "lastName": "Bernauer", + "username": "sbernauer" + }, + { + "firstName": "Sebastian", + "gender": "Male", + "lastName": "Bernauer", + "username": "sbernauer" } ] } From cd4e9730a3b755583c9f1194e3b684377d668b0c Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 15 May 2025 18:10:58 +0200 Subject: [PATCH 09/23] fix typo --- crates/stackable-webhook/src/servers/conversion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-webhook/src/servers/conversion.rs b/crates/stackable-webhook/src/servers/conversion.rs index 922a9b431..9b1ff197b 100644 --- a/crates/stackable-webhook/src/servers/conversion.rs +++ b/crates/stackable-webhook/src/servers/conversion.rs @@ -63,7 +63,7 @@ impl ConversionWebhookServer { /// req /// } /// ``` - #[instrument(name = "create_conversion_webhhok_server", skip(handler))] + #[instrument(name = "create_conversion_webhook_server", skip(handler))] pub fn new(handler: H, options: Options) -> Self where H: WebhookHandler + Clone + Send + Sync + 'static, From aab3fffa73a505ac655f212b5f07f409b851d17b Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 15 May 2025 18:23:29 +0200 Subject: [PATCH 10/23] chore: Remove uneeded files --- .../tests/test_data/convert_person_to_v2.json | 22 -------- .../tests/test_data/simple_from_k8s.json | 55 ------------------- 2 files changed, 77 deletions(-) delete mode 100644 crates/stackable-versioned/src/flux_converter/tests/test_data/convert_person_to_v2.json delete mode 100644 crates/stackable-versioned/src/flux_converter/tests/test_data/simple_from_k8s.json diff --git a/crates/stackable-versioned/src/flux_converter/tests/test_data/convert_person_to_v2.json b/crates/stackable-versioned/src/flux_converter/tests/test_data/convert_person_to_v2.json deleted file mode 100644 index 09378fa38..000000000 --- a/crates/stackable-versioned/src/flux_converter/tests/test_data/convert_person_to_v2.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "kind": "ConversionReview", - "apiVersion": "apiextensions.k8s.io/v1", - "request": { - "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "desiredAPIVersion": "v3", - "objects": [ - { - "apiVersion": "v1alpha1", - "kind": "Person", - "metadata": { - "name": "sbernauer", - "namespace": "default", - "uid": "d41e2019-5de3-409c-a7b2-0799ecb95e4b" - }, - "spec": { - "username": "sbernauer" - } - } - ] - } -} diff --git a/crates/stackable-versioned/src/flux_converter/tests/test_data/simple_from_k8s.json b/crates/stackable-versioned/src/flux_converter/tests/test_data/simple_from_k8s.json deleted file mode 100644 index 8cc9cbf1c..000000000 --- a/crates/stackable-versioned/src/flux_converter/tests/test_data/simple_from_k8s.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "kind": "ConversionReview", - "apiVersion": "apiextensions.k8s.io/v1", - "request": { - "uid": "f263987e-4d58-465a-9195-bf72a1c83623", - "desiredAPIVersion": "nullable.se/v1", - "objects": [ - { - "apiVersion": "nullable.se/v2", - "kind": "ConfigMapGenerator", - "metadata": { - "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"nullable.se/v2\",\"kind\":\"ConfigMapGenerator\",\"metadata\":{\"annotations\":{},\"name\":\"kek\",\"namespace\":\"default\"},\"spec\":{\"content\":\"x\"}}\n" - }, - "creationTimestamp": "2022-09-04T14:21:34Z", - "generation": 1, - "managedFields": [ - { - "apiVersion": "nullable.se/v2", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - } - }, - "f:spec": { - ".": {}, - "f:content": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2022-09-04T14:21:34Z" - } - ], - "name": "kek", - "namespace": "default", - "uid": "af7e84e4-573e-4b6e-bb66-0ea578c740da" - }, - "spec": { - "content": "x" - } - } - ] - }, - "response": { - "uid": "", - "convertedObjects": null, - "result": { - "metadata": {} - } - } -} From 0660983770e91844ec83a963463c27bda176574e Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 15 May 2025 18:34:40 +0200 Subject: [PATCH 11/23] test: Fix tests --- crates/stackable-operator/Cargo.toml | 2 +- crates/stackable-versioned/Cargo.toml | 5 ++--- crates/stackable-versioned/src/flux_converter/mod.rs | 8 ++------ crates/stackable-versioned/src/lib.rs | 9 ++++++++- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/stackable-operator/Cargo.toml b/crates/stackable-operator/Cargo.toml index ff14587d2..34918f52d 100644 --- a/crates/stackable-operator/Cargo.toml +++ b/crates/stackable-operator/Cargo.toml @@ -16,7 +16,7 @@ versioned = [] [dependencies] stackable-telemetry = { path = "../stackable-telemetry", features = ["clap"] } -stackable-versioned = { path = "../stackable-versioned", features = ["k8s", "flux-converter"] } +stackable-versioned = { path = "../stackable-versioned", features = ["k8s"] } stackable-operator-derive = { path = "../stackable-operator-derive" } stackable-shared = { path = "../stackable-shared" } diff --git a/crates/stackable-versioned/Cargo.toml b/crates/stackable-versioned/Cargo.toml index 4ccf12343..7a08c6e65 100644 --- a/crates/stackable-versioned/Cargo.toml +++ b/crates/stackable-versioned/Cargo.toml @@ -22,8 +22,7 @@ flux-converter = [ "dep:k8s-openapi", "dep:serde", "dep:schemars", - "dep:serde_json", - "dep:snafu", + "dep:serde_json" ] [dependencies] @@ -34,7 +33,7 @@ k8s-openapi = { workspace = true, optional = true } serde = { workspace = true, optional = true } schemars = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } -snafu = { workspace = true, optional = true } +snafu.workspace = true [dev-dependencies] insta.workspace = true diff --git a/crates/stackable-versioned/src/flux_converter/mod.rs b/crates/stackable-versioned/src/flux_converter/mod.rs index 81ece8596..9ce109561 100644 --- a/crates/stackable-versioned/src/flux_converter/mod.rs +++ b/crates/stackable-versioned/src/flux_converter/mod.rs @@ -7,15 +7,11 @@ use std::{error::Error, fmt::Write}; use snafu::Snafu; +use crate::ParseResourceVersionError; + #[cfg(test)] mod tests; -#[derive(Debug, Snafu)] -pub enum ParseResourceVersionError { - #[snafu(display("the resource version \"{version}\" is not known"))] - UnknownResourceVersion { version: String }, -} - #[derive(Debug, Snafu)] pub enum ConversionError { #[snafu(display("failed to parse current resource version \"{version}\""))] diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index 540b5fd7a..980b80f52 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -12,13 +12,20 @@ //! See [`versioned`] for an in-depth usage guide and a list of supported //! parameters. +use snafu::Snafu; pub use stackable_versioned_macros::*; #[cfg(feature = "flux-converter")] mod flux_converter; #[cfg(feature = "flux-converter")] -pub use flux_converter::{ConversionError, ParseResourceVersionError}; +pub use flux_converter::ConversionError; + +#[derive(Debug, Snafu)] +pub enum ParseResourceVersionError { + #[snafu(display("the resource version \"{version}\" is not known"))] + UnknownResourceVersion { version: String }, +} // Unused for now, might get picked up again in the future. #[doc(hidden)] From cfcdd9935ba404e7566f9d9433847cab4195e5ac Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 16 May 2025 10:23:09 +0200 Subject: [PATCH 12/23] feat: Add some tracing --- Cargo.lock | 1 + .../src/codegen/flux_converter.rs | 34 ++++++++++++++++++- crates/stackable-versioned/Cargo.toml | 4 ++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 103edd301..215848ea7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3214,6 +3214,7 @@ dependencies = [ "serde_json", "snafu 0.8.5", "stackable-versioned-macros", + "tracing", ] [[package]] diff --git a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs index fc62f0ebd..76d3dfd2b 100644 --- a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs +++ b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs @@ -17,7 +17,8 @@ pub(crate) fn generate_kubernetes_conversion( let conversion_chain = generate_conversion_chain(versions); let matches = conversion_chain.into_iter().map( - |((src, src_lower), (dst, _dst_lower), version_chain)| { + |((src, src_lower), (dst, dst_lower), version_chain)| { + let steps = version_chain.len(); let version_chain_string = version_chain.iter() .map(|(_,v)| v.parse::() .expect("The versions always needs to be a valid TokenStream")); @@ -33,6 +34,14 @@ pub(crate) fn generate_kubernetes_conversion( let resource: #version_chain_string::#struct_ident = resource.into(); )* + tracing::trace!( + from = stringify!(#src_lower), + to = stringify!(#dst_lower), + conversion.steps = #steps, + "Successfully converted {type} object", + type = stringify!(#enum_ident), + ); + converted.push( serde_json::to_value(resource) .map_err(|err| ConversionError::SerializeObjectSpec{source: err, kind: stringify!(#enum_ident).to_string()})? @@ -44,6 +53,13 @@ pub(crate) fn generate_kubernetes_conversion( Some(quote! { #[automatically_derived] impl #enum_ident { + #[tracing::instrument( + skip_all, + fields( + conversion.kind = review.types.kind, + conversion.api_version = review.types.api_version, + ) + )] pub fn convert(review: kube::core::conversion::ConversionReview) -> kube::core::conversion::ConversionResponse { // Intentionally not using `snafu::ResultExt` here to keep the number of dependencies minimal use kube::core::conversion::{ConversionRequest, ConversionResponse}; @@ -53,6 +69,11 @@ pub(crate) fn generate_kubernetes_conversion( let request = match ConversionRequest::from_review(review) { Ok(request) => request, Err(err) => { + tracing::warn!( + ?err, + "Invalid ConversionReview send by Kubernetes apiserver. It probably did not include a request" + ); + return ConversionResponse::invalid( kube::client::Status { status: Some(StatusSummary::Failure), @@ -70,9 +91,20 @@ pub(crate) fn generate_kubernetes_conversion( let conversion_response = ConversionResponse::for_request(request); match converted { Ok(converted) => { + tracing::debug!( + "Successfully converted {num} objects of type {type}", + num = converted.len(), + type = stringify!(#enum_ident), + ); + conversion_response.success(converted) }, Err(err) => { + tracing::debug!( + "Failed to converted objects of type {type}", + type = stringify!(#enum_ident), + ); + let error_message = err.as_human_readable_error_message(); conversion_response.failure( diff --git a/crates/stackable-versioned/Cargo.toml b/crates/stackable-versioned/Cargo.toml index 7a08c6e65..fb147c487 100644 --- a/crates/stackable-versioned/Cargo.toml +++ b/crates/stackable-versioned/Cargo.toml @@ -22,7 +22,8 @@ flux-converter = [ "dep:k8s-openapi", "dep:serde", "dep:schemars", - "dep:serde_json" + "dep:serde_json", + "dep:tracing" ] [dependencies] @@ -34,6 +35,7 @@ serde = { workspace = true, optional = true } schemars = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } snafu.workspace = true +tracing = { workspace = true, optional = true } [dev-dependencies] insta.workspace = true From 65d8016f3f50b9e9d75970381b7df9594c655967 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 16 May 2025 10:45:24 +0200 Subject: [PATCH 13/23] typos --- crates/stackable-webhook/CHANGELOG.md | 4 ++-- crates/stackable-webhook/src/constants.rs | 2 +- crates/stackable-webhook/src/lib.rs | 6 +++--- crates/stackable-webhook/src/tls.rs | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/stackable-webhook/CHANGELOG.md b/crates/stackable-webhook/CHANGELOG.md index d3b39dca0..334112f77 100644 --- a/crates/stackable-webhook/CHANGELOG.md +++ b/crates/stackable-webhook/CHANGELOG.md @@ -28,8 +28,8 @@ All notable changes to this project will be documented in this file. ### Added -- Instrument `WebhookServer` with `AxumTraceLayer`, add static healthcheck without instrumentation ([#758]). -- Add shutdown signal hander for the `WebhookServer` ([#767]). +- Instrument `WebhookServer` with `AxumTraceLayer`, add static health-check without instrumentation ([#758]). +- Add shutdown signal handler for the `WebhookServer` ([#767]). ### Changed diff --git a/crates/stackable-webhook/src/constants.rs b/crates/stackable-webhook/src/constants.rs index 65f7c1ebb..1ba1e720c 100644 --- a/crates/stackable-webhook/src/constants.rs +++ b/crates/stackable-webhook/src/constants.rs @@ -8,5 +8,5 @@ pub const DEFAULT_HTTPS_PORT: u16 = 8443; /// The default IP address `127.0.0.1` the webhook server binds to. pub const DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); -/// The default socket address `127.0.0.1:8443` the webhook server vinds to. +/// The default socket address `127.0.0.1:8443` the webhook server binds to. pub const DEFAULT_SOCKET_ADDR: SocketAddr = SocketAddr::new(DEFAULT_IP_ADDRESS, DEFAULT_HTTPS_PORT); diff --git a/crates/stackable-webhook/src/lib.rs b/crates/stackable-webhook/src/lib.rs index e1bb001a9..075a28da9 100644 --- a/crates/stackable-webhook/src/lib.rs +++ b/crates/stackable-webhook/src/lib.rs @@ -1,10 +1,10 @@ //! Utility types and functions to easily create ready-to-use webhook servers //! which can handle different tasks, for example CRD conversions. All webhook -//! servers use HTTPS by defaultThis library is fully compatible with the +//! servers use HTTPS by default. This library is fully compatible with the //! [`tracing`] crate and emits debug level tracing data. //! //! Most users will only use the top-level exported generic [`WebhookServer`] -//! which enables complete control over the [Router] which handles registering +//! which enables complete control over the [`Router`] which handles registering //! routes and their handler functions. //! //! ``` @@ -20,7 +20,7 @@ //! only required parameters are a conversion handler function and [`Options`]. //! //! This library additionally also exposes lower-level structs and functions to -//! enable complete controll over these details if needed. +//! enable complete control over these details if needed. //! //! [1]: crate::servers::ConversionWebhookServer use axum::{Router, routing::get}; diff --git a/crates/stackable-webhook/src/tls.rs b/crates/stackable-webhook/src/tls.rs index f3bbef959..b9150fbab 100644 --- a/crates/stackable-webhook/src/tls.rs +++ b/crates/stackable-webhook/src/tls.rs @@ -62,7 +62,7 @@ pub enum Error { /// Custom implementation of [`std::cmp::PartialEq`] because some inner types /// don't implement it. /// -/// Note that this implementation is restritced to testing because there are +/// Note that this implementation is restricted to testing because there are /// variants that use [`stackable_certs::ca::Error`] which only implements /// [`PartialEq`] for tests. #[cfg(test)] @@ -84,7 +84,7 @@ impl PartialEq for Error { } } -/// A server which terminates TLS connections and allows clients to commnunicate +/// A server which terminates TLS connections and allows clients to communicate /// via HTTPS with the underlying HTTP router. pub struct TlsServer { config: Arc, @@ -96,7 +96,7 @@ impl TlsServer { #[instrument(name = "create_tls_server", skip(router))] pub async fn new(socket_addr: SocketAddr, router: Router) -> Result { // NOTE(@NickLarsenNZ): This code is not async, and does take some - // non-negligable amount of time to complete (moreso in debug ). + // non-negligible amount of time to complete (moreso in debug). // We run this in a thread reserved for blocking code so that the Tokio // executor is able to make progress on other futures instead of being // blocked. From 7b13023e278355e5e6708a1bc540710f1e4d608e Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Fri, 16 May 2025 11:10:08 +0200 Subject: [PATCH 14/23] feat(stackable-versioned): Add ApplyResource trait --- crates/stackable-versioned/Cargo.toml | 2 ++ .../stackable-versioned/src/apply_resource.rs | 17 +++++++++++++++++ crates/stackable-versioned/src/lib.rs | 3 +++ 3 files changed, 22 insertions(+) create mode 100644 crates/stackable-versioned/src/apply_resource.rs diff --git a/crates/stackable-versioned/Cargo.toml b/crates/stackable-versioned/Cargo.toml index fb147c487..823d04502 100644 --- a/crates/stackable-versioned/Cargo.toml +++ b/crates/stackable-versioned/Cargo.toml @@ -14,6 +14,8 @@ all-features = true full = ["k8s", "flux-converter"] k8s = [ "stackable-versioned-macros/k8s", # Forward the k8s feature to the underlying macro crate + "dep:kube", + "dep:k8s-openapi", ] flux-converter = [ "k8s", diff --git a/crates/stackable-versioned/src/apply_resource.rs b/crates/stackable-versioned/src/apply_resource.rs new file mode 100644 index 000000000..55b56da81 --- /dev/null +++ b/crates/stackable-versioned/src/apply_resource.rs @@ -0,0 +1,17 @@ +use k8s_openapi::Resource; +use kube::Client; +use serde::Serialize; + +/// Given a [kube::Client], apply a resource to the server. +/// +/// This is esspecially useful when you have custom requirements for deploying +/// CRDs to clusters which already have a definition. +/// +/// For example, you want to prevent stable versions (v1) from having any +/// change. +pub trait ApplyResource: Resource + Serialize { + type Error; + + /// Apply a resource to a cluster + fn apply(&self, kube_client: Client) -> Result<(), Self::Error>; +} diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index 980b80f52..bb2df6ae3 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -18,6 +18,9 @@ pub use stackable_versioned_macros::*; #[cfg(feature = "flux-converter")] mod flux_converter; +#[cfg(feature = "k8s")] +mod apply_resource; + #[cfg(feature = "flux-converter")] pub use flux_converter::ConversionError; From 8fd3c3fff43f63b1e7c1af473094a5b24bc738e4 Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Fri, 16 May 2025 11:11:26 +0200 Subject: [PATCH 15/23] wip(stackable-versioned): impl ApplyResource for CustomResourceDefinition NOTE: Just added comments to describe the next steps. NOTE: We will need to move to async, since the kube api and discovery is async. --- .../src/flux_converter/apply_crd.rs | 33 +++++++++++++++++++ .../src/flux_converter/mod.rs | 2 ++ 2 files changed, 35 insertions(+) create mode 100644 crates/stackable-versioned/src/flux_converter/apply_crd.rs diff --git a/crates/stackable-versioned/src/flux_converter/apply_crd.rs b/crates/stackable-versioned/src/flux_converter/apply_crd.rs new file mode 100644 index 000000000..a06b1ebce --- /dev/null +++ b/crates/stackable-versioned/src/flux_converter/apply_crd.rs @@ -0,0 +1,33 @@ +use std::convert::Infallible; + +use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition; + +use crate::apply_resource::ApplyResource; + +impl ApplyResource for CustomResourceDefinition { + type Error = Infallible; + + fn apply(&self, _kube_client: kube::Client) -> Result<(), Self::Error> { + // 1. Using the kube::Client, check if the CRD already exists. + // If it does not exist, then simple apply. + // + // 2. If the CRD already exists, then get it, and check... + // - spec.conversion (this is likely to change, which is fine) + // - spec.group (this should probably never change) + // - spec.names (it is ok to add names, probably not great to remove them) + // - spec.preserve_unknown_fields (is this ok to change?) + // - spec.scope (this should probably never change) + // + // 3. For spec.versions, where "A" is the sert of versions applied to the server, + // and "B" is the set of versions to be applied... + // - A - B: These versions are candidates for removal + // - B - A: These versions can be safely appended + // - A ∩ B: These versions are likely to change in the following ways: + // - New fields added (safe for vXalphaY, vXbetaY, and vX) + // - Fields changed (can happen in vXalphaY, vXbetaY, but shouldn't in vX) + // - Fields removed (can happen in vXalphaY, vXbetaY, but shouldn't in vX) + // + // Complete the rest of the owl... + Ok(()) + } +} diff --git a/crates/stackable-versioned/src/flux_converter/mod.rs b/crates/stackable-versioned/src/flux_converter/mod.rs index 9ce109561..033dd407a 100644 --- a/crates/stackable-versioned/src/flux_converter/mod.rs +++ b/crates/stackable-versioned/src/flux_converter/mod.rs @@ -9,6 +9,8 @@ use snafu::Snafu; use crate::ParseResourceVersionError; +mod apply_crd; + #[cfg(test)] mod tests; From 4093782e9eb05efb153853cee0ac55f18f9865d3 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 16 May 2025 11:18:44 +0200 Subject: [PATCH 16/23] fix: Remove Serilaize bound --- crates/stackable-versioned/src/apply_resource.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/stackable-versioned/src/apply_resource.rs b/crates/stackable-versioned/src/apply_resource.rs index 55b56da81..c1ea5d8f4 100644 --- a/crates/stackable-versioned/src/apply_resource.rs +++ b/crates/stackable-versioned/src/apply_resource.rs @@ -1,15 +1,13 @@ use k8s_openapi::Resource; use kube::Client; -use serde::Serialize; - /// Given a [kube::Client], apply a resource to the server. /// -/// This is esspecially useful when you have custom requirements for deploying +/// This is especially useful when you have custom requirements for deploying /// CRDs to clusters which already have a definition. /// /// For example, you want to prevent stable versions (v1) from having any /// change. -pub trait ApplyResource: Resource + Serialize { +pub trait ApplyResource: Resource { type Error; /// Apply a resource to a cluster From 1b1f611c3308814a29cad7c2ee74cff84b1910a5 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 16 May 2025 11:25:04 +0200 Subject: [PATCH 17/23] Put in some further clarifications --- .../src/flux_converter/apply_crd.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/stackable-versioned/src/flux_converter/apply_crd.rs b/crates/stackable-versioned/src/flux_converter/apply_crd.rs index a06b1ebce..62870358a 100644 --- a/crates/stackable-versioned/src/flux_converter/apply_crd.rs +++ b/crates/stackable-versioned/src/flux_converter/apply_crd.rs @@ -12,18 +12,23 @@ impl ApplyResource for CustomResourceDefinition { // If it does not exist, then simple apply. // // 2. If the CRD already exists, then get it, and check... - // - spec.conversion (this is likely to change, which is fine) - // - spec.group (this should probably never change) - // - spec.names (it is ok to add names, probably not great to remove them) - // - spec.preserve_unknown_fields (is this ok to change?) - // - spec.scope (this should probably never change) + // - spec.conversion (this will often change, which is fine (e.g. caBundle rotation)) + // - spec.group (this should never change) + // - spec.names (it is ok to add names, probably not great to remove them, but legit as + // we can only keep a limited number because of CR size limitations) + // - spec.preserve_unknown_fields (we can be opinionated and reject Some(false) + // (and accept None and Some(true)). This is because the field is deprecated in favor + // of setting x-preserve-unknown-fields to true in spec.versions\[*\].schema.openAPIV3Schema. + // See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-pruning + // for details. + // - spec.scope (this should never change) // - // 3. For spec.versions, where "A" is the sert of versions applied to the server, + // 3. For spec.versions, where "A" is the set of versions currently defined on the stored CRD, // and "B" is the set of versions to be applied... // - A - B: These versions are candidates for removal // - B - A: These versions can be safely appended // - A ∩ B: These versions are likely to change in the following ways: - // - New fields added (safe for vXalphaY, vXbetaY, and vX) + // - New optional fields added (safe for vXalphaY, vXbetaY, and vX) // - Fields changed (can happen in vXalphaY, vXbetaY, but shouldn't in vX) // - Fields removed (can happen in vXalphaY, vXbetaY, but shouldn't in vX) // From fe62581ff1c95f6459036cf154671c6ec68d4e7d Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 16 May 2025 11:30:56 +0200 Subject: [PATCH 18/23] fix clippy lint --- crates/stackable-versioned/src/apply_resource.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/stackable-versioned/src/apply_resource.rs b/crates/stackable-versioned/src/apply_resource.rs index c1ea5d8f4..a82c2077b 100644 --- a/crates/stackable-versioned/src/apply_resource.rs +++ b/crates/stackable-versioned/src/apply_resource.rs @@ -7,6 +7,9 @@ use kube::Client; /// /// For example, you want to prevent stable versions (v1) from having any /// change. + +// FIXME(Nick): Remove unused +#[allow(unused)] pub trait ApplyResource: Resource { type Error; From 9ed1c7984cf977d13ff2910af020f28dc026b610 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 16 May 2025 12:17:25 +0200 Subject: [PATCH 19/23] Use ecdsa instead of rsa for webhooks Performance of RSA was so shit I couldn't run anything --- crates/stackable-webhook/src/tls.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/stackable-webhook/src/tls.rs b/crates/stackable-webhook/src/tls.rs index b9150fbab..eeb0dbf46 100644 --- a/crates/stackable-webhook/src/tls.rs +++ b/crates/stackable-webhook/src/tls.rs @@ -8,7 +8,11 @@ use hyper::{body::Incoming, service::service_fn}; use hyper_util::rt::{TokioExecutor, TokioIo}; use opentelemetry::trace::{FutureExt, SpanKind}; use snafu::{ResultExt, Snafu}; -use stackable_certs::{CertificatePairError, ca::CertificateAuthority, keys::rsa}; +use stackable_certs::{ + CertificatePairError, + ca::{CertificateAuthority, DEFAULT_CA_VALIDITY_SECONDS}, + keys::ecdsa, +}; use stackable_operator::time::Duration; use tokio::net::TcpListener; use tokio_rustls::{ @@ -44,12 +48,12 @@ pub enum Error { #[snafu(display("failed to encode leaf certificate as DER"))] EncodeCertificateDer { - source: CertificatePairError, + source: CertificatePairError, }, #[snafu(display("failed to encode private key as DER"))] EncodePrivateKeyDer { - source: CertificatePairError, + source: CertificatePairError, }, #[snafu(display("failed to set safe TLS protocol versions"))] @@ -103,10 +107,13 @@ impl TlsServer { // See https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html let task = tokio::task::spawn_blocking(move || { let mut certificate_authority = - CertificateAuthority::new_rsa().context(CreateCertificateAuthoritySnafu)?; - + CertificateAuthority::new_ecdsa().context(CreateCertificateAuthoritySnafu)?; let leaf_certificate = certificate_authority - .generate_rsa_leaf_certificate("Leaf", "webhook", Duration::from_secs(3600)) + .generate_ecdsa_leaf_certificate( + "Leaf", + "webhook", + Duration::from_secs(DEFAULT_CA_VALIDITY_SECONDS), + ) .context(GenerateLeafCertificateSnafu)?; let certificate_der = leaf_certificate From f25eae4dad34876da7bd3ebd1f7203209a0cbced Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Mon, 19 May 2025 11:55:15 +0200 Subject: [PATCH 20/23] Adopt conversion webhook to handle multiple kinds --- Cargo.lock | 13 + crates/stackable-operator/Cargo.toml | 2 +- crates/stackable-versioned-macros/Cargo.toml | 1 + ..._macros__test__k8s_snapshots@basic.rs.snap | 318 +++++++++ ...est__k8s_snapshots@crate_overrides.rs.snap | 318 +++++++++ ...macros__test__k8s_snapshots@module.rs.snap | 636 +++++++++++++++++ ...est__k8s_snapshots@module_preserve.rs.snap | 654 ++++++++++++++++++ ...__test__k8s_snapshots@renamed_kind.rs.snap | 323 +++++++++ ...os__test__k8s_snapshots@shortnames.rs.snap | 134 ++++ ...d_macros__test__k8s_snapshots@skip.rs.snap | 111 +++ .../src/codegen/flux_converter.rs | 12 +- .../src/flux_converter/tests/mod.rs | 27 +- .../src/servers/conversion.rs | 93 +-- 13 files changed, 2586 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9bb234e0c..5ba343419 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3239,6 +3239,7 @@ dependencies = [ "snafu 0.8.5", "stackable-versioned", "syn 2.0.101", + "tracing", "trybuild", ] @@ -3266,6 +3267,18 @@ dependencies = [ "tracing-opentelemetry", ] +[[package]] +name = "stackable-webhook-example" +version = "0.0.1" +dependencies = [ + "snafu 0.8.5", + "stackable-operator", + "stackable-webhook", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "strsim" version = "0.11.1" diff --git a/crates/stackable-operator/Cargo.toml b/crates/stackable-operator/Cargo.toml index 34918f52d..ff14587d2 100644 --- a/crates/stackable-operator/Cargo.toml +++ b/crates/stackable-operator/Cargo.toml @@ -16,7 +16,7 @@ versioned = [] [dependencies] stackable-telemetry = { path = "../stackable-telemetry", features = ["clap"] } -stackable-versioned = { path = "../stackable-versioned", features = ["k8s"] } +stackable-versioned = { path = "../stackable-versioned", features = ["k8s", "flux-converter"] } stackable-operator-derive = { path = "../stackable-operator-derive" } stackable-shared = { path = "../stackable-shared" } diff --git a/crates/stackable-versioned-macros/Cargo.toml b/crates/stackable-versioned-macros/Cargo.toml index c199e3b6b..1dd847ec7 100644 --- a/crates/stackable-versioned-macros/Cargo.toml +++ b/crates/stackable-versioned-macros/Cargo.toml @@ -55,4 +55,5 @@ serde.workspace = true serde_json.workspace = true serde_yaml.workspace = true snafu.workspace = true +tracing.workspace = true trybuild.workspace = true diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap index 4c569fd23..bf5c6400d 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap @@ -146,3 +146,321 @@ impl Foo { ) } } +#[automatically_derived] +impl Foo { + #[tracing::instrument(skip_all)] + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionReview { + use kube::core::conversion::{ConversionRequest, ConversionResponse}; + use kube::core::response::StatusSummary; + use stackable_versioned::ConversionError; + let request = match ConversionRequest::from_review(review) { + Ok(request) => request, + Err(err) => { + tracing::warn!( + ? err, + "Invalid ConversionReview send by Kubernetes apiserver. It probably did not include a request" + ); + return ConversionResponse::invalid(kube::client::Status { + status: Some(StatusSummary::Failure), + code: 400, + message: format!( + "The ConversionReview send did not include any request: {err}" + ), + reason: "ConversionReview request missing".to_string(), + details: None, + }) + .into_review(); + } + }; + let converted = Self::try_convert(&request); + let conversion_response = ConversionResponse::for_request(request); + match converted { + Ok(converted) => { + tracing::debug!( + "Successfully converted {num} objects of type {type}", num = + converted.len(), type = stringify!(Foo), + ); + conversion_response.success(converted).into_review() + } + Err(err) => { + tracing::debug!( + "Failed to converted objects of type {type}", type = stringify!(Foo), + ); + let error_message = err.as_human_readable_error_message(); + conversion_response + .failure(kube::client::Status { + status: Some(StatusSummary::Failure), + code: err.http_return_code(), + message: error_message.clone(), + reason: error_message, + details: None, + }) + .into_review() + } + } + } + #[tracing::instrument(skip_all, err)] + fn try_convert( + request: &kube::core::conversion::ConversionRequest, + ) -> Result, stackable_versioned::ConversionError> { + use stackable_versioned::ConversionError; + let desired_object_version = ::from_str( + &request.desired_api_version, + ) + .map_err(|err| ConversionError::ParseDesiredResourceVersion { + source: err, + version: request.desired_api_version.to_string(), + })?; + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })?; + let object_kind = object + .get("kind") + .ok_or_else(|| ConversionError::ObjectHasNoKind { + })?; + let object_kind = object_kind + .as_str() + .ok_or_else(|| ConversionError::ObjectKindNotString { + kind: object_kind.clone(), + })?; + let object_version = object + .get("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })?; + let object_version = object_version + .as_str() + .ok_or_else(|| ConversionError::ObjectApiVersionNotString { + api_version: object_version.clone(), + })?; + if object_kind != stringify!(Foo) { + return Err(ConversionError::WrongObjectKind { + expected_kind: stringify!(Foo).to_string(), + send_kind: object_kind.to_string(), + }); + } + let current_object_version = ::from_str( + object_version, + ) + .map_err(|err| ConversionError::ParseCurrentResourceVersion { + source: err, + version: object_version.to_string(), + })?; + match (¤t_object_version, &desired_object_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1alpha1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V1Beta1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1beta1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1beta1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1beta1::FooSpec = resource.into(); + let resource: v1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1"), conversion + .steps = 2usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Beta1, Self::V1Alpha1) => { + let resource: v1beta1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1beta1), to = stringify!("v1alpha1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Beta1, Self::V1Beta1) => { + let resource: v1beta1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v1beta1), to = stringify!("v1beta1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Beta1, Self::V1) => { + let resource: v1beta1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1beta1), to = stringify!("v1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1beta1::FooSpec = resource.into(); + let resource: v1alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v1alpha1"), conversion + .steps = 2usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1, Self::V1Beta1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1beta1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v1beta1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1, Self::V1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v1), to = stringify!("v1"), conversion.steps = + 0usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + } + } + Ok(converted) + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap index 6fe38cae8..50919d889 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap @@ -149,3 +149,321 @@ impl Foo { ) } } +#[automatically_derived] +impl Foo { + #[tracing::instrument(skip_all)] + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionReview { + use kube::core::conversion::{ConversionRequest, ConversionResponse}; + use kube::core::response::StatusSummary; + use stackable_versioned::ConversionError; + let request = match ConversionRequest::from_review(review) { + Ok(request) => request, + Err(err) => { + tracing::warn!( + ? err, + "Invalid ConversionReview send by Kubernetes apiserver. It probably did not include a request" + ); + return ConversionResponse::invalid(kube::client::Status { + status: Some(StatusSummary::Failure), + code: 400, + message: format!( + "The ConversionReview send did not include any request: {err}" + ), + reason: "ConversionReview request missing".to_string(), + details: None, + }) + .into_review(); + } + }; + let converted = Self::try_convert(&request); + let conversion_response = ConversionResponse::for_request(request); + match converted { + Ok(converted) => { + tracing::debug!( + "Successfully converted {num} objects of type {type}", num = + converted.len(), type = stringify!(Foo), + ); + conversion_response.success(converted).into_review() + } + Err(err) => { + tracing::debug!( + "Failed to converted objects of type {type}", type = stringify!(Foo), + ); + let error_message = err.as_human_readable_error_message(); + conversion_response + .failure(kube::client::Status { + status: Some(StatusSummary::Failure), + code: err.http_return_code(), + message: error_message.clone(), + reason: error_message, + details: None, + }) + .into_review() + } + } + } + #[tracing::instrument(skip_all, err)] + fn try_convert( + request: &kube::core::conversion::ConversionRequest, + ) -> Result, stackable_versioned::ConversionError> { + use stackable_versioned::ConversionError; + let desired_object_version = ::from_str( + &request.desired_api_version, + ) + .map_err(|err| ConversionError::ParseDesiredResourceVersion { + source: err, + version: request.desired_api_version.to_string(), + })?; + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })?; + let object_kind = object + .get("kind") + .ok_or_else(|| ConversionError::ObjectHasNoKind { + })?; + let object_kind = object_kind + .as_str() + .ok_or_else(|| ConversionError::ObjectKindNotString { + kind: object_kind.clone(), + })?; + let object_version = object + .get("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })?; + let object_version = object_version + .as_str() + .ok_or_else(|| ConversionError::ObjectApiVersionNotString { + api_version: object_version.clone(), + })?; + if object_kind != stringify!(Foo) { + return Err(ConversionError::WrongObjectKind { + expected_kind: stringify!(Foo).to_string(), + send_kind: object_kind.to_string(), + }); + } + let current_object_version = ::from_str( + object_version, + ) + .map_err(|err| ConversionError::ParseCurrentResourceVersion { + source: err, + version: object_version.to_string(), + })?; + match (¤t_object_version, &desired_object_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1alpha1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V1Beta1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1beta1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1beta1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1beta1::FooSpec = resource.into(); + let resource: v1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1"), conversion + .steps = 2usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Beta1, Self::V1Alpha1) => { + let resource: v1beta1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1beta1), to = stringify!("v1alpha1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Beta1, Self::V1Beta1) => { + let resource: v1beta1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v1beta1), to = stringify!("v1beta1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Beta1, Self::V1) => { + let resource: v1beta1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1beta1), to = stringify!("v1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1beta1::FooSpec = resource.into(); + let resource: v1alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v1alpha1"), conversion + .steps = 2usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1, Self::V1Beta1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1beta1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v1beta1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1, Self::V1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v1), to = stringify!("v1"), conversion.steps = + 0usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + } + } + Ok(converted) + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap index 25404bc4d..3e2e16023 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap @@ -264,6 +264,324 @@ impl Foo { } } #[automatically_derived] +impl Foo { + #[tracing::instrument(skip_all)] + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionReview { + use kube::core::conversion::{ConversionRequest, ConversionResponse}; + use kube::core::response::StatusSummary; + use stackable_versioned::ConversionError; + let request = match ConversionRequest::from_review(review) { + Ok(request) => request, + Err(err) => { + tracing::warn!( + ? err, + "Invalid ConversionReview send by Kubernetes apiserver. It probably did not include a request" + ); + return ConversionResponse::invalid(kube::client::Status { + status: Some(StatusSummary::Failure), + code: 400, + message: format!( + "The ConversionReview send did not include any request: {err}" + ), + reason: "ConversionReview request missing".to_string(), + details: None, + }) + .into_review(); + } + }; + let converted = Self::try_convert(&request); + let conversion_response = ConversionResponse::for_request(request); + match converted { + Ok(converted) => { + tracing::debug!( + "Successfully converted {num} objects of type {type}", num = + converted.len(), type = stringify!(Foo), + ); + conversion_response.success(converted).into_review() + } + Err(err) => { + tracing::debug!( + "Failed to converted objects of type {type}", type = stringify!(Foo), + ); + let error_message = err.as_human_readable_error_message(); + conversion_response + .failure(kube::client::Status { + status: Some(StatusSummary::Failure), + code: err.http_return_code(), + message: error_message.clone(), + reason: error_message, + details: None, + }) + .into_review() + } + } + } + #[tracing::instrument(skip_all, err)] + fn try_convert( + request: &kube::core::conversion::ConversionRequest, + ) -> Result, stackable_versioned::ConversionError> { + use stackable_versioned::ConversionError; + let desired_object_version = ::from_str( + &request.desired_api_version, + ) + .map_err(|err| ConversionError::ParseDesiredResourceVersion { + source: err, + version: request.desired_api_version.to_string(), + })?; + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })?; + let object_kind = object + .get("kind") + .ok_or_else(|| ConversionError::ObjectHasNoKind { + })?; + let object_kind = object_kind + .as_str() + .ok_or_else(|| ConversionError::ObjectKindNotString { + kind: object_kind.clone(), + })?; + let object_version = object + .get("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })?; + let object_version = object_version + .as_str() + .ok_or_else(|| ConversionError::ObjectApiVersionNotString { + api_version: object_version.clone(), + })?; + if object_kind != stringify!(Foo) { + return Err(ConversionError::WrongObjectKind { + expected_kind: stringify!(Foo).to_string(), + send_kind: object_kind.to_string(), + }); + } + let current_object_version = ::from_str( + object_version, + ) + .map_err(|err| ConversionError::ParseCurrentResourceVersion { + source: err, + version: object_version.to_string(), + })?; + match (¤t_object_version, &desired_object_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1alpha1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V2Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1::FooSpec = resource.into(); + let resource: v2alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v2alpha1"), + conversion.steps = 2usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v1alpha1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1, Self::V1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v1), to = stringify!("v1"), conversion.steps = + 0usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1, Self::V2Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v2alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v2alpha1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V2Alpha1, Self::V1Alpha1) => { + let resource: v2alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1::FooSpec = resource.into(); + let resource: v1alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v2alpha1), to = stringify!("v1alpha1"), + conversion.steps = 2usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V2Alpha1, Self::V1) => { + let resource: v2alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v2alpha1), to = stringify!("v1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V2Alpha1, Self::V2Alpha1) => { + let resource: v2alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v2alpha1), to = stringify!("v2alpha1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + } + } + Ok(converted) + } +} +#[automatically_derived] pub(crate) enum Bar { V1Alpha1, V1, @@ -317,3 +635,321 @@ impl Bar { ) } } +#[automatically_derived] +impl Bar { + #[tracing::instrument(skip_all)] + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionReview { + use kube::core::conversion::{ConversionRequest, ConversionResponse}; + use kube::core::response::StatusSummary; + use stackable_versioned::ConversionError; + let request = match ConversionRequest::from_review(review) { + Ok(request) => request, + Err(err) => { + tracing::warn!( + ? err, + "Invalid ConversionReview send by Kubernetes apiserver. It probably did not include a request" + ); + return ConversionResponse::invalid(kube::client::Status { + status: Some(StatusSummary::Failure), + code: 400, + message: format!( + "The ConversionReview send did not include any request: {err}" + ), + reason: "ConversionReview request missing".to_string(), + details: None, + }) + .into_review(); + } + }; + let converted = Self::try_convert(&request); + let conversion_response = ConversionResponse::for_request(request); + match converted { + Ok(converted) => { + tracing::debug!( + "Successfully converted {num} objects of type {type}", num = + converted.len(), type = stringify!(Bar), + ); + conversion_response.success(converted).into_review() + } + Err(err) => { + tracing::debug!( + "Failed to converted objects of type {type}", type = stringify!(Bar), + ); + let error_message = err.as_human_readable_error_message(); + conversion_response + .failure(kube::client::Status { + status: Some(StatusSummary::Failure), + code: err.http_return_code(), + message: error_message.clone(), + reason: error_message, + details: None, + }) + .into_review() + } + } + } + #[tracing::instrument(skip_all, err)] + fn try_convert( + request: &kube::core::conversion::ConversionRequest, + ) -> Result, stackable_versioned::ConversionError> { + use stackable_versioned::ConversionError; + let desired_object_version = ::from_str( + &request.desired_api_version, + ) + .map_err(|err| ConversionError::ParseDesiredResourceVersion { + source: err, + version: request.desired_api_version.to_string(), + })?; + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })?; + let object_kind = object + .get("kind") + .ok_or_else(|| ConversionError::ObjectHasNoKind { + })?; + let object_kind = object_kind + .as_str() + .ok_or_else(|| ConversionError::ObjectKindNotString { + kind: object_kind.clone(), + })?; + let object_version = object + .get("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })?; + let object_version = object_version + .as_str() + .ok_or_else(|| ConversionError::ObjectApiVersionNotString { + api_version: object_version.clone(), + })?; + if object_kind != stringify!(Bar) { + return Err(ConversionError::WrongObjectKind { + expected_kind: stringify!(Bar).to_string(), + send_kind: object_kind.to_string(), + }); + } + let current_object_version = ::from_str( + object_version, + ) + .map_err(|err| ConversionError::ParseCurrentResourceVersion { + source: err, + version: object_version.to_string(), + })?; + match (¤t_object_version, &desired_object_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1alpha1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + let resource: v1::BarSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V2Alpha1) => { + let resource: v1alpha1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + let resource: v1::BarSpec = resource.into(); + let resource: v2alpha1::BarSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v2alpha1"), + conversion.steps = 2usize, + "Successfully converted {type} object", type = stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + let resource: v1alpha1::BarSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v1alpha1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V1, Self::V1) => { + let resource: v1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + tracing::trace!( + from = stringify!(v1), to = stringify!("v1"), conversion.steps = + 0usize, "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V1, Self::V2Alpha1) => { + let resource: v1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + let resource: v2alpha1::BarSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v2alpha1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V2Alpha1, Self::V1Alpha1) => { + let resource: v2alpha1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + let resource: v1::BarSpec = resource.into(); + let resource: v1alpha1::BarSpec = resource.into(); + tracing::trace!( + from = stringify!(v2alpha1), to = stringify!("v1alpha1"), + conversion.steps = 2usize, + "Successfully converted {type} object", type = stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V2Alpha1, Self::V1) => { + let resource: v2alpha1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + let resource: v1::BarSpec = resource.into(); + tracing::trace!( + from = stringify!(v2alpha1), to = stringify!("v1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V2Alpha1, Self::V2Alpha1) => { + let resource: v2alpha1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + tracing::trace!( + from = stringify!(v2alpha1), to = stringify!("v2alpha1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + } + } + Ok(converted) + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap index 0205342ad..b63502241 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap @@ -250,6 +250,333 @@ pub(crate) mod versioned { ) } } + #[automatically_derived] + impl Foo { + #[tracing::instrument(skip_all)] + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionReview { + use kube::core::conversion::{ConversionRequest, ConversionResponse}; + use kube::core::response::StatusSummary; + use stackable_versioned::ConversionError; + let request = match ConversionRequest::from_review(review) { + Ok(request) => request, + Err(err) => { + tracing::warn!( + ? err, + "Invalid ConversionReview send by Kubernetes apiserver. It probably did not include a request" + ); + return ConversionResponse::invalid(kube::client::Status { + status: Some(StatusSummary::Failure), + code: 400, + message: format!( + "The ConversionReview send did not include any request: {err}" + ), + reason: "ConversionReview request missing".to_string(), + details: None, + }) + .into_review(); + } + }; + let converted = Self::try_convert(&request); + let conversion_response = ConversionResponse::for_request(request); + match converted { + Ok(converted) => { + tracing::debug!( + "Successfully converted {num} objects of type {type}", num = + converted.len(), type = stringify!(Foo), + ); + conversion_response.success(converted).into_review() + } + Err(err) => { + tracing::debug!( + "Failed to converted objects of type {type}", type = + stringify!(Foo), + ); + let error_message = err.as_human_readable_error_message(); + conversion_response + .failure(kube::client::Status { + status: Some(StatusSummary::Failure), + code: err.http_return_code(), + message: error_message.clone(), + reason: error_message, + details: None, + }) + .into_review() + } + } + } + #[tracing::instrument(skip_all, err)] + fn try_convert( + request: &kube::core::conversion::ConversionRequest, + ) -> Result, stackable_versioned::ConversionError> { + use stackable_versioned::ConversionError; + let desired_object_version = ::from_str( + &request.desired_api_version, + ) + .map_err(|err| ConversionError::ParseDesiredResourceVersion { + source: err, + version: request.desired_api_version.to_string(), + })?; + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })?; + let object_kind = object + .get("kind") + .ok_or_else(|| ConversionError::ObjectHasNoKind { + })?; + let object_kind = object_kind + .as_str() + .ok_or_else(|| ConversionError::ObjectKindNotString { + kind: object_kind.clone(), + })?; + let object_version = object + .get("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })?; + let object_version = object_version + .as_str() + .ok_or_else(|| ConversionError::ObjectApiVersionNotString { + api_version: object_version.clone(), + })?; + if object_kind != stringify!(Foo) { + return Err(ConversionError::WrongObjectKind { + expected_kind: stringify!(Foo).to_string(), + send_kind: object_kind.to_string(), + }); + } + let current_object_version = ::from_str( + object_version, + ) + .map_err(|err| ConversionError::ParseCurrentResourceVersion { + source: err, + version: object_version.to_string(), + })?; + match (¤t_object_version, &desired_object_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1alpha1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V2Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1::FooSpec = resource.into(); + let resource: v2alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v2alpha1"), + conversion.steps = 2usize, + "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v1alpha1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1, Self::V1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v1), to = stringify!("v1"), conversion + .steps = 0usize, "Successfully converted {type} object", type + = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V1, Self::V2Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v2alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v2alpha1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V2Alpha1, Self::V1Alpha1) => { + let resource: v2alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1::FooSpec = resource.into(); + let resource: v1alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v2alpha1), to = stringify!("v1alpha1"), + conversion.steps = 2usize, + "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V2Alpha1, Self::V1) => { + let resource: v2alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + let resource: v1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v2alpha1), to = stringify!("v1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + (Self::V2Alpha1, Self::V2Alpha1) => { + let resource: v2alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v2alpha1), to = stringify!("v2alpha1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = + stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + } + } + Ok(converted) + } + } pub enum Bar { V1Alpha1, V1, @@ -300,4 +627,331 @@ pub(crate) mod versioned { ) } } + #[automatically_derived] + impl Bar { + #[tracing::instrument(skip_all)] + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionReview { + use kube::core::conversion::{ConversionRequest, ConversionResponse}; + use kube::core::response::StatusSummary; + use stackable_versioned::ConversionError; + let request = match ConversionRequest::from_review(review) { + Ok(request) => request, + Err(err) => { + tracing::warn!( + ? err, + "Invalid ConversionReview send by Kubernetes apiserver. It probably did not include a request" + ); + return ConversionResponse::invalid(kube::client::Status { + status: Some(StatusSummary::Failure), + code: 400, + message: format!( + "The ConversionReview send did not include any request: {err}" + ), + reason: "ConversionReview request missing".to_string(), + details: None, + }) + .into_review(); + } + }; + let converted = Self::try_convert(&request); + let conversion_response = ConversionResponse::for_request(request); + match converted { + Ok(converted) => { + tracing::debug!( + "Successfully converted {num} objects of type {type}", num = + converted.len(), type = stringify!(Bar), + ); + conversion_response.success(converted).into_review() + } + Err(err) => { + tracing::debug!( + "Failed to converted objects of type {type}", type = + stringify!(Bar), + ); + let error_message = err.as_human_readable_error_message(); + conversion_response + .failure(kube::client::Status { + status: Some(StatusSummary::Failure), + code: err.http_return_code(), + message: error_message.clone(), + reason: error_message, + details: None, + }) + .into_review() + } + } + } + #[tracing::instrument(skip_all, err)] + fn try_convert( + request: &kube::core::conversion::ConversionRequest, + ) -> Result, stackable_versioned::ConversionError> { + use stackable_versioned::ConversionError; + let desired_object_version = ::from_str( + &request.desired_api_version, + ) + .map_err(|err| ConversionError::ParseDesiredResourceVersion { + source: err, + version: request.desired_api_version.to_string(), + })?; + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })?; + let object_kind = object + .get("kind") + .ok_or_else(|| ConversionError::ObjectHasNoKind { + })?; + let object_kind = object_kind + .as_str() + .ok_or_else(|| ConversionError::ObjectKindNotString { + kind: object_kind.clone(), + })?; + let object_version = object + .get("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })?; + let object_version = object_version + .as_str() + .ok_or_else(|| ConversionError::ObjectApiVersionNotString { + api_version: object_version.clone(), + })?; + if object_kind != stringify!(Bar) { + return Err(ConversionError::WrongObjectKind { + expected_kind: stringify!(Bar).to_string(), + send_kind: object_kind.to_string(), + }); + } + let current_object_version = ::from_str( + object_version, + ) + .map_err(|err| ConversionError::ParseCurrentResourceVersion { + source: err, + version: object_version.to_string(), + })?; + match (¤t_object_version, &desired_object_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1alpha1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + let resource: v1::BarSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V2Alpha1) => { + let resource: v1alpha1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + let resource: v1::BarSpec = resource.into(); + let resource: v2alpha1::BarSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v2alpha1"), + conversion.steps = 2usize, + "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + let resource: v1alpha1::BarSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v1alpha1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V1, Self::V1) => { + let resource: v1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + tracing::trace!( + from = stringify!(v1), to = stringify!("v1"), conversion + .steps = 0usize, "Successfully converted {type} object", type + = stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V1, Self::V2Alpha1) => { + let resource: v1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + let resource: v2alpha1::BarSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v2alpha1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V2Alpha1, Self::V1Alpha1) => { + let resource: v2alpha1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + let resource: v1::BarSpec = resource.into(); + let resource: v1alpha1::BarSpec = resource.into(); + tracing::trace!( + from = stringify!(v2alpha1), to = stringify!("v1alpha1"), + conversion.steps = 2usize, + "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V2Alpha1, Self::V1) => { + let resource: v2alpha1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + let resource: v1::BarSpec = resource.into(); + tracing::trace!( + from = stringify!(v2alpha1), to = stringify!("v1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + (Self::V2Alpha1, Self::V2Alpha1) => { + let resource: v2alpha1::BarSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + tracing::trace!( + from = stringify!(v2alpha1), to = stringify!("v2alpha1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = + stringify!(Bar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?, + ); + } + } + } + Ok(converted) + } + } } diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap index 534fe35e5..75dbfd46f 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap @@ -146,3 +146,326 @@ impl FooBar { ) } } +#[automatically_derived] +impl FooBar { + #[tracing::instrument(skip_all)] + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionReview { + use kube::core::conversion::{ConversionRequest, ConversionResponse}; + use kube::core::response::StatusSummary; + use stackable_versioned::ConversionError; + let request = match ConversionRequest::from_review(review) { + Ok(request) => request, + Err(err) => { + tracing::warn!( + ? err, + "Invalid ConversionReview send by Kubernetes apiserver. It probably did not include a request" + ); + return ConversionResponse::invalid(kube::client::Status { + status: Some(StatusSummary::Failure), + code: 400, + message: format!( + "The ConversionReview send did not include any request: {err}" + ), + reason: "ConversionReview request missing".to_string(), + details: None, + }) + .into_review(); + } + }; + let converted = Self::try_convert(&request); + let conversion_response = ConversionResponse::for_request(request); + match converted { + Ok(converted) => { + tracing::debug!( + "Successfully converted {num} objects of type {type}", num = + converted.len(), type = stringify!(FooBar), + ); + conversion_response.success(converted).into_review() + } + Err(err) => { + tracing::debug!( + "Failed to converted objects of type {type}", type = + stringify!(FooBar), + ); + let error_message = err.as_human_readable_error_message(); + conversion_response + .failure(kube::client::Status { + status: Some(StatusSummary::Failure), + code: err.http_return_code(), + message: error_message.clone(), + reason: error_message, + details: None, + }) + .into_review() + } + } + } + #[tracing::instrument(skip_all, err)] + fn try_convert( + request: &kube::core::conversion::ConversionRequest, + ) -> Result, stackable_versioned::ConversionError> { + use stackable_versioned::ConversionError; + let desired_object_version = ::from_str( + &request.desired_api_version, + ) + .map_err(|err| ConversionError::ParseDesiredResourceVersion { + source: err, + version: request.desired_api_version.to_string(), + })?; + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })?; + let object_kind = object + .get("kind") + .ok_or_else(|| ConversionError::ObjectHasNoKind { + })?; + let object_kind = object_kind + .as_str() + .ok_or_else(|| ConversionError::ObjectKindNotString { + kind: object_kind.clone(), + })?; + let object_version = object + .get("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })?; + let object_version = object_version + .as_str() + .ok_or_else(|| ConversionError::ObjectApiVersionNotString { + api_version: object_version.clone(), + })?; + if object_kind != stringify!(FooBar) { + return Err(ConversionError::WrongObjectKind { + expected_kind: stringify!(FooBar).to_string(), + send_kind: object_kind.to_string(), + }); + } + let current_object_version = ::from_str( + object_version, + ) + .map_err(|err| ConversionError::ParseCurrentResourceVersion { + source: err, + version: object_version.to_string(), + })?; + match (¤t_object_version, &desired_object_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1alpha1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = + stringify!(FooBar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V1Beta1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + let resource: v1beta1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1beta1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = + stringify!(FooBar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?, + ); + } + (Self::V1Alpha1, Self::V1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + let resource: v1beta1::FooSpec = resource.into(); + let resource: v1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1"), conversion + .steps = 2usize, "Successfully converted {type} object", type = + stringify!(FooBar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?, + ); + } + (Self::V1Beta1, Self::V1Alpha1) => { + let resource: v1beta1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + let resource: v1alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1beta1), to = stringify!("v1alpha1"), + conversion.steps = 1usize, + "Successfully converted {type} object", type = + stringify!(FooBar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?, + ); + } + (Self::V1Beta1, Self::V1Beta1) => { + let resource: v1beta1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + tracing::trace!( + from = stringify!(v1beta1), to = stringify!("v1beta1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = + stringify!(FooBar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?, + ); + } + (Self::V1Beta1, Self::V1) => { + let resource: v1beta1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + let resource: v1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1beta1), to = stringify!("v1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(FooBar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?, + ); + } + (Self::V1, Self::V1Alpha1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + let resource: v1beta1::FooSpec = resource.into(); + let resource: v1alpha1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v1alpha1"), conversion + .steps = 2usize, "Successfully converted {type} object", type = + stringify!(FooBar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?, + ); + } + (Self::V1, Self::V1Beta1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + let resource: v1beta1::FooSpec = resource.into(); + tracing::trace!( + from = stringify!(v1), to = stringify!("v1beta1"), conversion + .steps = 1usize, "Successfully converted {type} object", type = + stringify!(FooBar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?, + ); + } + (Self::V1, Self::V1) => { + let resource: v1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + tracing::trace!( + from = stringify!(v1), to = stringify!("v1"), conversion.steps = + 0usize, "Successfully converted {type} object", type = + stringify!(FooBar), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?, + ); + } + } + } + Ok(converted) + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap index 5ed1ac1cf..17ca22651 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap @@ -67,3 +67,137 @@ impl Foo { ) } } +#[automatically_derived] +impl Foo { + #[tracing::instrument(skip_all)] + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionReview { + use kube::core::conversion::{ConversionRequest, ConversionResponse}; + use kube::core::response::StatusSummary; + use stackable_versioned::ConversionError; + let request = match ConversionRequest::from_review(review) { + Ok(request) => request, + Err(err) => { + tracing::warn!( + ? err, + "Invalid ConversionReview send by Kubernetes apiserver. It probably did not include a request" + ); + return ConversionResponse::invalid(kube::client::Status { + status: Some(StatusSummary::Failure), + code: 400, + message: format!( + "The ConversionReview send did not include any request: {err}" + ), + reason: "ConversionReview request missing".to_string(), + details: None, + }) + .into_review(); + } + }; + let converted = Self::try_convert(&request); + let conversion_response = ConversionResponse::for_request(request); + match converted { + Ok(converted) => { + tracing::debug!( + "Successfully converted {num} objects of type {type}", num = + converted.len(), type = stringify!(Foo), + ); + conversion_response.success(converted).into_review() + } + Err(err) => { + tracing::debug!( + "Failed to converted objects of type {type}", type = stringify!(Foo), + ); + let error_message = err.as_human_readable_error_message(); + conversion_response + .failure(kube::client::Status { + status: Some(StatusSummary::Failure), + code: err.http_return_code(), + message: error_message.clone(), + reason: error_message, + details: None, + }) + .into_review() + } + } + } + #[tracing::instrument(skip_all, err)] + fn try_convert( + request: &kube::core::conversion::ConversionRequest, + ) -> Result, stackable_versioned::ConversionError> { + use stackable_versioned::ConversionError; + let desired_object_version = ::from_str( + &request.desired_api_version, + ) + .map_err(|err| ConversionError::ParseDesiredResourceVersion { + source: err, + version: request.desired_api_version.to_string(), + })?; + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })?; + let object_kind = object + .get("kind") + .ok_or_else(|| ConversionError::ObjectHasNoKind { + })?; + let object_kind = object_kind + .as_str() + .ok_or_else(|| ConversionError::ObjectKindNotString { + kind: object_kind.clone(), + })?; + let object_version = object + .get("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })?; + let object_version = object_version + .as_str() + .ok_or_else(|| ConversionError::ObjectApiVersionNotString { + api_version: object_version.clone(), + })?; + if object_kind != stringify!(Foo) { + return Err(ConversionError::WrongObjectKind { + expected_kind: stringify!(Foo).to_string(), + send_kind: object_kind.to_string(), + }); + } + let current_object_version = ::from_str( + object_version, + ) + .map_err(|err| ConversionError::ParseCurrentResourceVersion { + source: err, + version: object_version.to_string(), + })?; + match (¤t_object_version, &desired_object_version) { + (Self::V1Alpha1, Self::V1Alpha1) => { + let resource: v1alpha1::FooSpec = serde_json::from_value( + object_spec.clone(), + ) + .map_err(|err| ConversionError::DeserializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + tracing::trace!( + from = stringify!(v1alpha1), to = stringify!("v1alpha1"), + conversion.steps = 0usize, + "Successfully converted {type} object", type = stringify!(Foo), + ); + converted + .push( + serde_json::to_value(resource) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?, + ); + } + } + } + Ok(converted) + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap index 39f0b2263..79d6b1716 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap @@ -92,3 +92,114 @@ pub mod v1 { pub baz: bool, } } +#[automatically_derived] +impl Foo { + #[tracing::instrument(skip_all)] + pub fn convert( + review: kube::core::conversion::ConversionReview, + ) -> kube::core::conversion::ConversionReview { + use kube::core::conversion::{ConversionRequest, ConversionResponse}; + use kube::core::response::StatusSummary; + use stackable_versioned::ConversionError; + let request = match ConversionRequest::from_review(review) { + Ok(request) => request, + Err(err) => { + tracing::warn!( + ? err, + "Invalid ConversionReview send by Kubernetes apiserver. It probably did not include a request" + ); + return ConversionResponse::invalid(kube::client::Status { + status: Some(StatusSummary::Failure), + code: 400, + message: format!( + "The ConversionReview send did not include any request: {err}" + ), + reason: "ConversionReview request missing".to_string(), + details: None, + }) + .into_review(); + } + }; + let converted = Self::try_convert(&request); + let conversion_response = ConversionResponse::for_request(request); + match converted { + Ok(converted) => { + tracing::debug!( + "Successfully converted {num} objects of type {type}", num = + converted.len(), type = stringify!(Foo), + ); + conversion_response.success(converted).into_review() + } + Err(err) => { + tracing::debug!( + "Failed to converted objects of type {type}", type = stringify!(Foo), + ); + let error_message = err.as_human_readable_error_message(); + conversion_response + .failure(kube::client::Status { + status: Some(StatusSummary::Failure), + code: err.http_return_code(), + message: error_message.clone(), + reason: error_message, + details: None, + }) + .into_review() + } + } + } + #[tracing::instrument(skip_all, err)] + fn try_convert( + request: &kube::core::conversion::ConversionRequest, + ) -> Result, stackable_versioned::ConversionError> { + use stackable_versioned::ConversionError; + let desired_object_version = ::from_str( + &request.desired_api_version, + ) + .map_err(|err| ConversionError::ParseDesiredResourceVersion { + source: err, + version: request.desired_api_version.to_string(), + })?; + let mut converted: Vec = Vec::with_capacity( + request.objects.len(), + ); + for object in &request.objects { + let object_spec = object + .get("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })?; + let object_kind = object + .get("kind") + .ok_or_else(|| ConversionError::ObjectHasNoKind { + })?; + let object_kind = object_kind + .as_str() + .ok_or_else(|| ConversionError::ObjectKindNotString { + kind: object_kind.clone(), + })?; + let object_version = object + .get("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })?; + let object_version = object_version + .as_str() + .ok_or_else(|| ConversionError::ObjectApiVersionNotString { + api_version: object_version.clone(), + })?; + if object_kind != stringify!(Foo) { + return Err(ConversionError::WrongObjectKind { + expected_kind: stringify!(Foo).to_string(), + send_kind: object_kind.to_string(), + }); + } + let current_object_version = ::from_str( + object_version, + ) + .map_err(|err| ConversionError::ParseCurrentResourceVersion { + source: err, + version: object_version.to_string(), + })?; + match (¤t_object_version, &desired_object_version) {} + } + Ok(converted) + } +} diff --git a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs index 76d3dfd2b..2bac6ec80 100644 --- a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs +++ b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs @@ -60,7 +60,7 @@ pub(crate) fn generate_kubernetes_conversion( conversion.api_version = review.types.api_version, ) )] - pub fn convert(review: kube::core::conversion::ConversionReview) -> kube::core::conversion::ConversionResponse { + pub fn convert(review: kube::core::conversion::ConversionReview) -> kube::core::conversion::ConversionReview { // Intentionally not using `snafu::ResultExt` here to keep the number of dependencies minimal use kube::core::conversion::{ConversionRequest, ConversionResponse}; use kube::core::response::StatusSummary; @@ -82,7 +82,7 @@ pub(crate) fn generate_kubernetes_conversion( reason: "ConversionReview request missing".to_string(), details: None, }, - ); + ).into_review(); } }; @@ -97,7 +97,7 @@ pub(crate) fn generate_kubernetes_conversion( type = stringify!(#enum_ident), ); - conversion_response.success(converted) + conversion_response.success(converted).into_review() }, Err(err) => { tracing::debug!( @@ -115,11 +115,15 @@ pub(crate) fn generate_kubernetes_conversion( reason: error_message, details: None, }, - ) + ).into_review() } } } + #[tracing::instrument( + skip_all, + err + )] fn try_convert(request: &kube::core::conversion::ConversionRequest) -> Result, stackable_versioned::ConversionError> { use stackable_versioned::ConversionError; diff --git a/crates/stackable-versioned/src/flux_converter/tests/mod.rs b/crates/stackable-versioned/src/flux_converter/tests/mod.rs index 873a49ed7..3aaad92fe 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/mod.rs +++ b/crates/stackable-versioned/src/flux_converter/tests/mod.rs @@ -121,17 +121,17 @@ mod tests { use std::{fs::File, path::Path}; use insta::{assert_snapshot, glob}; - use kube::core::{ - conversion::{ConversionResponse, ConversionReview}, - response::StatusSummary, - }; + use kube::core::{conversion::ConversionReview, response::StatusSummary}; use super::Person; #[test] fn pass() { glob!("../../../fixtures/inputs/pass/", "*.json", |path| { - let (review, response) = run_for_file(path); + let (request, response) = run_for_file(path); + let response = response + .response + .expect("ConversionReview had no response!"); let formatted = serde_json::to_string_pretty(&response) .expect("Failed to serialize ConversionResponse"); @@ -142,14 +142,17 @@ mod tests { Some(StatusSummary::Success), "File {path:?} should be converted successfully" ); - assert_eq!(review.request.unwrap().uid, response.uid); + assert_eq!(request.request.unwrap().uid, response.uid); }) } #[test] fn fail() { glob!("../../../fixtures/inputs/fail/", "*.json", |path| { - let (review, response) = run_for_file(path); + let (request, response) = run_for_file(path); + let response = response + .response + .expect("ConversionReview had no response!"); let formatted = serde_json::to_string_pretty(&response) .expect("Failed to serialize ConversionResponse"); @@ -160,18 +163,18 @@ mod tests { Some(StatusSummary::Failure), "File {path:?} should *not* be converted successfully" ); - if let Some(request) = &review.request { + if let Some(request) = &request.request { assert_eq!(request.uid, response.uid); } }) } - fn run_for_file(path: &Path) -> (ConversionReview, ConversionResponse) { - let review: ConversionReview = + fn run_for_file(path: &Path) -> (ConversionReview, ConversionReview) { + let request: ConversionReview = serde_json::from_reader(File::open(path).expect("failed to open test file")) .expect("failed to parse ConversionReview from test file"); - let response = Person::convert(review.clone()); + let response = Person::convert(request.clone()); - (review, response) + (request, response) } } diff --git a/crates/stackable-webhook/src/servers/conversion.rs b/crates/stackable-webhook/src/servers/conversion.rs index 9b1ff197b..ec29e8b75 100644 --- a/crates/stackable-webhook/src/servers/conversion.rs +++ b/crates/stackable-webhook/src/servers/conversion.rs @@ -39,9 +39,9 @@ pub struct ConversionWebhookServer { impl ConversionWebhookServer { /// Creates a new conversion webhook server **without** state which expects - /// POST requests being made to the `/convert` endpoint. + /// POST requests being made to the `/convert/{kind}` endpoints. /// - /// Each request is handled by the provided `handler` function. Any function + /// Each request is handled by the provided `handler` functions. Any function /// with the signature `(ConversionReview) -> ConversionReview` can be /// provided. The [`ConversionReview`] type can be imported via a re-export at /// [`crate::servers::ConversionReview`]. @@ -49,40 +49,44 @@ impl ConversionWebhookServer { /// # Example /// /// ``` + /// use stackable_operator::crd::authentication::core::AuthenticationClass; /// use stackable_webhook::{ /// servers::{ConversionReview, ConversionWebhookServer}, /// Options /// }; /// - /// // Construct the conversion webhook server - /// let server = ConversionWebhookServer::new(handler, Options::default()); + /// let handlers = [( + /// "AuthenticationClass", + /// AuthenticationClass::convert as fn(ConversionReview) -> ConversionReview, + /// )]; /// - /// // Define the handler function - /// fn handler(req: ConversionReview) -> ConversionReview { - /// // In here we can do the CRD conversion - /// req - /// } + /// // Construct the conversion webhook server + /// let server = ConversionWebhookServer::new(handlers, Options::default()); /// ``` - #[instrument(name = "create_conversion_webhook_server", skip(handler))] - pub fn new(handler: H, options: Options) -> Self + #[instrument(name = "create_conversion_webhook_server", skip(handlers))] + pub fn new<'a, H>(handlers: impl IntoIterator, options: Options) -> Self where H: WebhookHandler + Clone + Send + Sync + 'static, { - tracing::debug!("create new conversion webhook server"); + tracing::debug!("creating new conversion webhook server"); + + let mut router = Router::new(); + for (kind, handler) in handlers { + let handler_fn = |Json(review): Json| async { + let review = handler.call(review); + Json(review) + }; - let handler_fn = |Json(review): Json| async { - let review = handler.call(review); - Json(review) - }; + router = router.route(&format!("/convert/{kind}"), post(handler_fn)); + } - let router = Router::new().route("/convert", post(handler_fn)); Self { router, options } } - /// Creates a new conversion webhook server **with** state which expects - /// POST requests being made to the `/convert` endpoint. + /// Creates a new conversion webhook server **without** state which expects + /// POST requests being made to the `/convert/{kind}` endpoints. /// - /// Each request is handled by the provided `handler` function. Any function + /// Each request is handled by the provided `handler` functions. Any function /// with the signature `(ConversionReview, S) -> ConversionReview` can be /// provided. The [`ConversionReview`] type can be imported via a re-export at /// [`crate::servers::ConversionReview`]. @@ -104,21 +108,30 @@ impl ConversionWebhookServer { /// #[derive(Debug, Clone)] /// struct State {} /// + /// let handlers = [( + /// "AuthenticationClass", + /// auth_class_handler as fn(ConversionReview, state: Arc) -> ConversionReview, + /// )]; + /// /// let shared_state = Arc::new(State {}); /// let server = ConversionWebhookServer::new_with_state( - /// handler, + /// handlers, /// shared_state, /// Options::default(), /// ); /// /// // Define the handler function - /// fn handler(req: ConversionReview, state: Arc) -> ConversionReview { + /// fn auth_class_handler(req: ConversionReview, state: Arc) -> ConversionReview { /// // In here we can do the CRD conversion /// req /// } /// ``` - #[instrument(name = "create_conversion_webhook_server_with_state", skip(handler))] - pub fn new_with_state(handler: H, state: S, options: Options) -> Self + #[instrument(name = "create_conversion_webhook_server_with_state", skip(handlers))] + pub fn new_with_state<'a, H, S>( + handlers: impl IntoIterator, + state: S, + options: Options, + ) -> Self where H: StatefulWebhookHandler + Clone @@ -127,23 +140,25 @@ impl ConversionWebhookServer { + 'static, S: Clone + Debug + Send + Sync + 'static, { - tracing::debug!("create new conversion webhook server with state"); + tracing::debug!("creating new conversion webhook server with state"); - // NOTE (@Techassi): Initially, after adding the state extractor, the - // compiler kept throwing a trait error at me stating that the closure - // below doesn't implement the Handler trait from Axum. This had nothing - // to do with the state itself, but rather the order of extractors. All - // body consuming extractors, like the JSON extractor need to come last - // in the handler. - // https://docs.rs/axum/latest/axum/extract/index.html#the-order-of-extractors - let handler_fn = |State(state): State, Json(review): Json| async { - let review = handler.call(review, state); - Json(review) - }; + let mut router = Router::new(); + for (kind, handler) in handlers { + // NOTE (@Techassi): Initially, after adding the state extractor, the + // compiler kept throwing a trait error at me stating that the closure + // below doesn't implement the Handler trait from Axum. This had nothing + // to do with the state itself, but rather the order of extractors. All + // body consuming extractors, like the JSON extractor need to come last + // in the handler. + // https://docs.rs/axum/latest/axum/extract/index.html#the-order-of-extractors + let handler_fn = |State(state): State, Json(review): Json| async { + let review = handler.call(review, state); + Json(review) + }; - let router = Router::new() - .route("/convert", post(handler_fn)) - .with_state(state); + router = router.route(&format!("/convert/{kind}"), post(handler_fn)); + } + let router = router.with_state(state); Self { router, options } } From d40a048cd8cf139ee0dc334a0f087503107140ba Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Mon, 19 May 2025 11:56:26 +0200 Subject: [PATCH 21/23] Update tests --- ...ned_macros__test__k8s_snapshots@basic.rs.snap | 8 +++++++- ...__test__k8s_snapshots@crate_overrides.rs.snap | 8 +++++++- ...ed_macros__test__k8s_snapshots@module.rs.snap | 16 ++++++++++++++-- ...__test__k8s_snapshots@module_preserve.rs.snap | 16 ++++++++++++++-- ...ros__test__k8s_snapshots@renamed_kind.rs.snap | 8 +++++++- ...acros__test__k8s_snapshots@shortnames.rs.snap | 8 +++++++- ...oned_macros__test__k8s_snapshots@skip.rs.snap | 8 +++++++- 7 files changed, 63 insertions(+), 9 deletions(-) diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap index bf5c6400d..201d8f7a7 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap @@ -148,7 +148,13 @@ impl Foo { } #[automatically_derived] impl Foo { - #[tracing::instrument(skip_all)] + #[tracing::instrument( + skip_all, + fields( + conversion.kind = review.types.kind, + conversion.api_version = review.types.api_version, + ) + )] pub fn convert( review: kube::core::conversion::ConversionReview, ) -> kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap index 50919d889..586a7c39c 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap @@ -151,7 +151,13 @@ impl Foo { } #[automatically_derived] impl Foo { - #[tracing::instrument(skip_all)] + #[tracing::instrument( + skip_all, + fields( + conversion.kind = review.types.kind, + conversion.api_version = review.types.api_version, + ) + )] pub fn convert( review: kube::core::conversion::ConversionReview, ) -> kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap index 3e2e16023..5d4a3bdfb 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap @@ -265,7 +265,13 @@ impl Foo { } #[automatically_derived] impl Foo { - #[tracing::instrument(skip_all)] + #[tracing::instrument( + skip_all, + fields( + conversion.kind = review.types.kind, + conversion.api_version = review.types.api_version, + ) + )] pub fn convert( review: kube::core::conversion::ConversionReview, ) -> kube::core::conversion::ConversionReview { @@ -637,7 +643,13 @@ impl Bar { } #[automatically_derived] impl Bar { - #[tracing::instrument(skip_all)] + #[tracing::instrument( + skip_all, + fields( + conversion.kind = review.types.kind, + conversion.api_version = review.types.api_version, + ) + )] pub fn convert( review: kube::core::conversion::ConversionReview, ) -> kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap index b63502241..b40b96055 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap @@ -252,7 +252,13 @@ pub(crate) mod versioned { } #[automatically_derived] impl Foo { - #[tracing::instrument(skip_all)] + #[tracing::instrument( + skip_all, + fields( + conversion.kind = review.types.kind, + conversion.api_version = review.types.api_version, + ) + )] pub fn convert( review: kube::core::conversion::ConversionReview, ) -> kube::core::conversion::ConversionReview { @@ -629,7 +635,13 @@ pub(crate) mod versioned { } #[automatically_derived] impl Bar { - #[tracing::instrument(skip_all)] + #[tracing::instrument( + skip_all, + fields( + conversion.kind = review.types.kind, + conversion.api_version = review.types.api_version, + ) + )] pub fn convert( review: kube::core::conversion::ConversionReview, ) -> kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap index 75dbfd46f..ad4e20b5e 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap @@ -148,7 +148,13 @@ impl FooBar { } #[automatically_derived] impl FooBar { - #[tracing::instrument(skip_all)] + #[tracing::instrument( + skip_all, + fields( + conversion.kind = review.types.kind, + conversion.api_version = review.types.api_version, + ) + )] pub fn convert( review: kube::core::conversion::ConversionReview, ) -> kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap index 17ca22651..456462144 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap @@ -69,7 +69,13 @@ impl Foo { } #[automatically_derived] impl Foo { - #[tracing::instrument(skip_all)] + #[tracing::instrument( + skip_all, + fields( + conversion.kind = review.types.kind, + conversion.api_version = review.types.api_version, + ) + )] pub fn convert( review: kube::core::conversion::ConversionReview, ) -> kube::core::conversion::ConversionReview { diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap index 79d6b1716..0a083b0c6 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap @@ -94,7 +94,13 @@ pub mod v1 { } #[automatically_derived] impl Foo { - #[tracing::instrument(skip_all)] + #[tracing::instrument( + skip_all, + fields( + conversion.kind = review.types.kind, + conversion.api_version = review.types.api_version, + ) + )] pub fn convert( review: kube::core::conversion::ConversionReview, ) -> kube::core::conversion::ConversionReview { From 8c9ce4ff786e844c015077d004af82c8c738eb17 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Wed, 21 May 2025 08:35:29 +0200 Subject: [PATCH 22/23] fix: Parse entrire apiVersion and return entire objects again --- Cargo.lock | 196 +----- Cargo.toml | 5 +- ..._macros__test__k8s_snapshots@basic.rs.snap | 290 +++++---- ...est__k8s_snapshots@crate_overrides.rs.snap | 290 +++++---- ...macros__test__k8s_snapshots@module.rs.snap | 580 +++++++++++------- ...est__k8s_snapshots@module_preserve.rs.snap | 576 +++++++++++------ ...__test__k8s_snapshots@renamed_kind.rs.snap | 291 +++++---- ...os__test__k8s_snapshots@shortnames.rs.snap | 64 +- ...d_macros__test__k8s_snapshots@skip.rs.snap | 21 +- .../src/codegen/container/mod.rs | 1 + .../src/codegen/container/struct.rs | 23 +- .../src/codegen/flux_converter.rs | 48 +- .../fail/undeserializable_missing_field.json | 4 +- .../inputs/fail/unkown_current_version.json | 4 +- .../inputs/fail/unkown_desired_version.json | 4 +- .../fixtures/inputs/fail/wrong_object.json | 4 +- .../inputs/pass/persons_to_v1alpha1.json | 12 +- .../fixtures/inputs/pass/persons_to_v3.json | 12 +- .../src/flux_converter/tests/mod.rs | 18 +- ...sts__tests__fail@request_missing.json.snap | 20 +- ...l@undeserializable_missing_field.json.snap | 20 +- ...sts__fail@unkown_current_version.json.snap | 20 +- ...sts__fail@unkown_desired_version.json.snap | 20 +- ..._tests__tests__fail@wrong_object.json.snap | 20 +- ..._tests__pass@persons_to_v1alpha1.json.snap | 69 ++- ...tests__tests__pass@persons_to_v3.json.snap | 99 +-- crates/stackable-versioned/src/lib.rs | 3 + 27 files changed, 1623 insertions(+), 1091 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ba343419..f13fda6cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,29 +158,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "aws-lc-rs" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" -dependencies = [ - "bindgen", - "cc", - "cmake", - "dunce", - "fs_extra", -] - [[package]] name = "axum" version = "0.8.4" @@ -285,29 +262,6 @@ version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.101", - "which", -] - [[package]] name = "bit-set" version = "0.5.3" @@ -372,20 +326,9 @@ version = "1.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" dependencies = [ - "jobserver", - "libc", "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -402,17 +345,6 @@ dependencies = [ "serde", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.5.38" @@ -453,15 +385,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" -[[package]] -name = "cmake" -version = "0.1.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" -dependencies = [ - "cc", -] - [[package]] name = "colorchoice" version = "1.0.3" @@ -747,12 +670,6 @@ dependencies = [ "snafu 0.6.10", ] -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "dyn-clone" version = "1.0.19" @@ -956,12 +873,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "futures" version = "0.3.31" @@ -1531,15 +1442,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -1566,16 +1468,6 @@ dependencies = [ "regex", ] -[[package]] -name = "jobserver" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" -dependencies = [ - "getrandom 0.3.3", - "libc", -] - [[package]] name = "js-sys" version = "0.3.77" @@ -1764,40 +1656,18 @@ dependencies = [ "spin", ] -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" -[[package]] -name = "libloading" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" -dependencies = [ - "cfg-if", - "windows-targets 0.53.0", -] - [[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -1853,12 +1723,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.8" @@ -1879,16 +1743,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2338,7 +2192,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools", "proc-macro2", "quote", "syn 2.0.101", @@ -2606,12 +2460,6 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc_version" version = "0.4.1" @@ -2621,19 +2469,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.0.7" @@ -2643,7 +2478,7 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.9.4", + "linux-raw-sys", "windows-sys 0.59.0", ] @@ -2653,7 +2488,6 @@ version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ - "aws-lc-rs", "log", "once_cell", "ring", @@ -2712,7 +2546,6 @@ version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3223,7 +3056,7 @@ dependencies = [ "convert_case", "darling", "insta", - "itertools 0.14.0", + "itertools", "k8s-openapi", "k8s-version", "kube", @@ -3251,10 +3084,7 @@ dependencies = [ "futures-util", "hyper", "hyper-util", - "k8s-openapi", - "kube", "opentelemetry", - "serde_json", "snafu 0.8.5", "stackable-certs", "stackable-operator", @@ -3265,12 +3095,18 @@ dependencies = [ "tower-http", "tracing", "tracing-opentelemetry", + "x509-cert", ] [[package]] name = "stackable-webhook-example" version = "0.0.1" dependencies = [ + "k8s-openapi", + "kube", + "schemars", + "serde", + "serde_json", "snafu 0.8.5", "stackable-operator", "stackable-webhook", @@ -3370,7 +3206,7 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", + "rustix", "windows-sys 0.59.0", ] @@ -4022,18 +3858,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.44", -] - [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 7e77a8477..1de2f888d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/stackabletech/operator-rs" [workspace.dependencies] product-config = { git = "https://github.com/stackabletech/product-config.git", tag = "0.7.0" } -axum = "0.8.1" +axum = { version = "0.8.1", features = ["http2"] } chrono = { version = "0.4.38", default-features = false } clap = { version = "4.5.17", features = ["derive", "cargo", "env"] } const_format = "0.2.33" @@ -64,7 +64,8 @@ syn = "2.0.77" tempfile = "3.12.0" time = { version = "0.3.36" } tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread", "fs"] } -tokio-rustls = "0.26.0" +# Use ring instead of aws-lc-rs +tokio-rustls = { version = "0.26.0", default-features = false, features = ["ring", "logging", "tls12"] } tokio-test = "0.4.4" tower = { version = "0.5.1", features = ["util"] } tower-http = { version = "0.6.1", features = ["trace"] } diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap index 201d8f7a7..8fd1a1468 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@basic.rs.snap @@ -111,6 +111,7 @@ impl ::std::fmt::Display for Foo { } } } +/// Parses the version, such as `v1alpha1` #[automatically_derived] impl ::std::str::FromStr for Foo { type Err = stackable_versioned::ParseResourceVersionError; @@ -127,6 +128,24 @@ impl ::std::str::FromStr for Foo { } } } +/// Parses the entire `apiVersion`, such as `zookeeper.stackable.tech/v1alpha1`. +#[automatically_derived] +impl Foo { + pub fn from_api_version( + api_version: &str, + ) -> Result { + match api_version { + "stackable.tech/v1alpha1" => Ok(Self::V1Alpha1), + "stackable.tech/v1beta1" => Ok(Self::V1Beta1), + "stackable.tech/v1" => Ok(Self::V1), + _ => { + Err(stackable_versioned::ParseResourceVersionError::UnknownApiVersion { + api_version: api_version.to_string(), + }) + } + } + } +} #[automatically_derived] impl Foo { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. @@ -156,10 +175,10 @@ impl Foo { ) )] pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionReview { - use kube::core::conversion::{ConversionRequest, ConversionResponse}; - use kube::core::response::StatusSummary; + review: ::kube::core::conversion::ConversionReview, + ) -> ::kube::core::conversion::ConversionReview { + use ::kube::core::conversion::{ConversionRequest, ConversionResponse}; + use ::kube::core::response::StatusSummary; use stackable_versioned::ConversionError; let request = match ConversionRequest::from_review(review) { Ok(request) => request, @@ -191,9 +210,6 @@ impl Foo { conversion_response.success(converted).into_review() } Err(err) => { - tracing::debug!( - "Failed to converted objects of type {type}", type = stringify!(Foo), - ); let error_message = err.as_human_readable_error_message(); conversion_response .failure(kube::client::Status { @@ -209,12 +225,10 @@ impl Foo { } #[tracing::instrument(skip_all, err)] fn try_convert( - request: &kube::core::conversion::ConversionRequest, + request: &::kube::core::conversion::ConversionRequest, ) -> Result, stackable_versioned::ConversionError> { use stackable_versioned::ConversionError; - let desired_object_version = ::from_str( - &request.desired_api_version, - ) + let desired_object_version = Self::from_api_version(&request.desired_api_version) .map_err(|err| ConversionError::ParseDesiredResourceVersion { source: err, version: request.desired_api_version.to_string(), @@ -251,16 +265,14 @@ impl Foo { send_kind: object_kind.to_string(), }); } - let current_object_version = ::from_str( - object_version, - ) + let current_object_version = Self::from_api_version(object_version) .map_err(|err| ConversionError::ParseCurrentResourceVersion { source: err, version: object_version.to_string(), })?; match (¤t_object_version, &desired_object_version) { (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -272,87 +284,119 @@ impl Foo { conversion.steps = 0usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V1Beta1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1beta1::FooSpec = resource.into(); + let resource_spec: v1beta1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v1beta1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1beta1::FooSpec = resource.into(); - let resource: v1::FooSpec = resource.into(); + let resource_spec: v1beta1::FooSpec = resource_spec.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v1"), conversion .steps = 2usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Beta1, Self::V1Alpha1) => { - let resource: v1beta1::FooSpec = serde_json::from_value( + let resource_spec: v1beta1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1alpha1::FooSpec = resource.into(); + let resource_spec: v1alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1beta1), to = stringify!("v1alpha1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Beta1, Self::V1Beta1) => { - let resource: v1beta1::FooSpec = serde_json::from_value( + let resource_spec: v1beta1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -364,87 +408,119 @@ impl Foo { conversion.steps = 0usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Beta1, Self::V1) => { - let resource: v1beta1::FooSpec = serde_json::from_value( + let resource_spec: v1beta1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1::FooSpec = resource.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1beta1), to = stringify!("v1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1beta1::FooSpec = resource.into(); - let resource: v1alpha1::FooSpec = resource.into(); + let resource_spec: v1beta1::FooSpec = resource_spec.into(); + let resource_spec: v1alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v1alpha1"), conversion .steps = 2usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1Beta1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1beta1::FooSpec = resource.into(); + let resource_spec: v1beta1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v1beta1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -456,14 +532,22 @@ impl Foo { 0usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } } } diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap index 586a7c39c..89bf7798c 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@crate_overrides.rs.snap @@ -114,6 +114,7 @@ impl ::std::fmt::Display for Foo { } } } +/// Parses the version, such as `v1alpha1` #[automatically_derived] impl ::std::str::FromStr for Foo { type Err = stackable_versioned::ParseResourceVersionError; @@ -130,6 +131,24 @@ impl ::std::str::FromStr for Foo { } } } +/// Parses the entire `apiVersion`, such as `zookeeper.stackable.tech/v1alpha1`. +#[automatically_derived] +impl Foo { + pub fn from_api_version( + api_version: &str, + ) -> Result { + match api_version { + "foo.example.org/v1alpha1" => Ok(Self::V1Alpha1), + "foo.example.org/v1beta1" => Ok(Self::V1Beta1), + "foo.example.org/v1" => Ok(Self::V1), + _ => { + Err(stackable_versioned::ParseResourceVersionError::UnknownApiVersion { + api_version: api_version.to_string(), + }) + } + } + } +} #[automatically_derived] impl Foo { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. @@ -159,10 +178,10 @@ impl Foo { ) )] pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionReview { - use kube::core::conversion::{ConversionRequest, ConversionResponse}; - use kube::core::response::StatusSummary; + review: ::kube::core::conversion::ConversionReview, + ) -> ::kube::core::conversion::ConversionReview { + use ::kube::core::conversion::{ConversionRequest, ConversionResponse}; + use ::kube::core::response::StatusSummary; use stackable_versioned::ConversionError; let request = match ConversionRequest::from_review(review) { Ok(request) => request, @@ -194,9 +213,6 @@ impl Foo { conversion_response.success(converted).into_review() } Err(err) => { - tracing::debug!( - "Failed to converted objects of type {type}", type = stringify!(Foo), - ); let error_message = err.as_human_readable_error_message(); conversion_response .failure(kube::client::Status { @@ -212,12 +228,10 @@ impl Foo { } #[tracing::instrument(skip_all, err)] fn try_convert( - request: &kube::core::conversion::ConversionRequest, + request: &::kube::core::conversion::ConversionRequest, ) -> Result, stackable_versioned::ConversionError> { use stackable_versioned::ConversionError; - let desired_object_version = ::from_str( - &request.desired_api_version, - ) + let desired_object_version = Self::from_api_version(&request.desired_api_version) .map_err(|err| ConversionError::ParseDesiredResourceVersion { source: err, version: request.desired_api_version.to_string(), @@ -254,16 +268,14 @@ impl Foo { send_kind: object_kind.to_string(), }); } - let current_object_version = ::from_str( - object_version, - ) + let current_object_version = Self::from_api_version(object_version) .map_err(|err| ConversionError::ParseCurrentResourceVersion { source: err, version: object_version.to_string(), })?; match (¤t_object_version, &desired_object_version) { (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -275,87 +287,119 @@ impl Foo { conversion.steps = 0usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V1Beta1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1beta1::FooSpec = resource.into(); + let resource_spec: v1beta1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v1beta1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1beta1::FooSpec = resource.into(); - let resource: v1::FooSpec = resource.into(); + let resource_spec: v1beta1::FooSpec = resource_spec.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v1"), conversion .steps = 2usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Beta1, Self::V1Alpha1) => { - let resource: v1beta1::FooSpec = serde_json::from_value( + let resource_spec: v1beta1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1alpha1::FooSpec = resource.into(); + let resource_spec: v1alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1beta1), to = stringify!("v1alpha1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Beta1, Self::V1Beta1) => { - let resource: v1beta1::FooSpec = serde_json::from_value( + let resource_spec: v1beta1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -367,87 +411,119 @@ impl Foo { conversion.steps = 0usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Beta1, Self::V1) => { - let resource: v1beta1::FooSpec = serde_json::from_value( + let resource_spec: v1beta1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1::FooSpec = resource.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1beta1), to = stringify!("v1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1beta1::FooSpec = resource.into(); - let resource: v1alpha1::FooSpec = resource.into(); + let resource_spec: v1beta1::FooSpec = resource_spec.into(); + let resource_spec: v1alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v1alpha1"), conversion .steps = 2usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1Beta1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1beta1::FooSpec = resource.into(); + let resource_spec: v1beta1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v1beta1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -459,14 +535,22 @@ impl Foo { 0usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } } } diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap index 5d4a3bdfb..0e14d19d6 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module.rs.snap @@ -228,6 +228,7 @@ impl ::std::fmt::Display for Foo { } } } +/// Parses the version, such as `v1alpha1` #[automatically_derived] impl ::std::str::FromStr for Foo { type Err = stackable_versioned::ParseResourceVersionError; @@ -244,6 +245,24 @@ impl ::std::str::FromStr for Foo { } } } +/// Parses the entire `apiVersion`, such as `zookeeper.stackable.tech/v1alpha1`. +#[automatically_derived] +impl Foo { + pub fn from_api_version( + api_version: &str, + ) -> Result { + match api_version { + "foo.example.org/v1alpha1" => Ok(Self::V1Alpha1), + "foo.example.org/v1" => Ok(Self::V1), + "foo.example.org/v2alpha1" => Ok(Self::V2Alpha1), + _ => { + Err(stackable_versioned::ParseResourceVersionError::UnknownApiVersion { + api_version: api_version.to_string(), + }) + } + } + } +} #[automatically_derived] impl Foo { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. @@ -273,10 +292,10 @@ impl Foo { ) )] pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionReview { - use kube::core::conversion::{ConversionRequest, ConversionResponse}; - use kube::core::response::StatusSummary; + review: ::kube::core::conversion::ConversionReview, + ) -> ::kube::core::conversion::ConversionReview { + use ::kube::core::conversion::{ConversionRequest, ConversionResponse}; + use ::kube::core::response::StatusSummary; use stackable_versioned::ConversionError; let request = match ConversionRequest::from_review(review) { Ok(request) => request, @@ -308,9 +327,6 @@ impl Foo { conversion_response.success(converted).into_review() } Err(err) => { - tracing::debug!( - "Failed to converted objects of type {type}", type = stringify!(Foo), - ); let error_message = err.as_human_readable_error_message(); conversion_response .failure(kube::client::Status { @@ -326,12 +342,10 @@ impl Foo { } #[tracing::instrument(skip_all, err)] fn try_convert( - request: &kube::core::conversion::ConversionRequest, + request: &::kube::core::conversion::ConversionRequest, ) -> Result, stackable_versioned::ConversionError> { use stackable_versioned::ConversionError; - let desired_object_version = ::from_str( - &request.desired_api_version, - ) + let desired_object_version = Self::from_api_version(&request.desired_api_version) .map_err(|err| ConversionError::ParseDesiredResourceVersion { source: err, version: request.desired_api_version.to_string(), @@ -368,16 +382,14 @@ impl Foo { send_kind: object_kind.to_string(), }); } - let current_object_version = ::from_str( - object_version, - ) + let current_object_version = Self::from_api_version(object_version) .map_err(|err| ConversionError::ParseCurrentResourceVersion { source: err, version: object_version.to_string(), })?; match (¤t_object_version, &desired_object_version) { (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -389,87 +401,119 @@ impl Foo { conversion.steps = 0usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1::FooSpec = resource.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V2Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1::FooSpec = resource.into(); - let resource: v2alpha1::FooSpec = resource.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); + let resource_spec: v2alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v2alpha1"), conversion.steps = 2usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1alpha1::FooSpec = resource.into(); + let resource_spec: v1alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v1alpha1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -481,87 +525,119 @@ impl Foo { 0usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V2Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v2alpha1::FooSpec = resource.into(); + let resource_spec: v2alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v2alpha1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V2Alpha1, Self::V1Alpha1) => { - let resource: v2alpha1::FooSpec = serde_json::from_value( + let resource_spec: v2alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1::FooSpec = resource.into(); - let resource: v1alpha1::FooSpec = resource.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); + let resource_spec: v1alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v2alpha1), to = stringify!("v1alpha1"), conversion.steps = 2usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V2Alpha1, Self::V1) => { - let resource: v2alpha1::FooSpec = serde_json::from_value( + let resource_spec: v2alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1::FooSpec = resource.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v2alpha1), to = stringify!("v1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V2Alpha1, Self::V2Alpha1) => { - let resource: v2alpha1::FooSpec = serde_json::from_value( + let resource_spec: v2alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -573,14 +649,22 @@ impl Foo { conversion.steps = 0usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } } } @@ -606,6 +690,7 @@ impl ::std::fmt::Display for Bar { } } } +/// Parses the version, such as `v1alpha1` #[automatically_derived] impl ::std::str::FromStr for Bar { type Err = stackable_versioned::ParseResourceVersionError; @@ -622,6 +707,24 @@ impl ::std::str::FromStr for Bar { } } } +/// Parses the entire `apiVersion`, such as `zookeeper.stackable.tech/v1alpha1`. +#[automatically_derived] +impl Bar { + pub fn from_api_version( + api_version: &str, + ) -> Result { + match api_version { + "bar.example.org/v1alpha1" => Ok(Self::V1Alpha1), + "bar.example.org/v1" => Ok(Self::V1), + "bar.example.org/v2alpha1" => Ok(Self::V2Alpha1), + _ => { + Err(stackable_versioned::ParseResourceVersionError::UnknownApiVersion { + api_version: api_version.to_string(), + }) + } + } + } +} #[automatically_derived] impl Bar { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. @@ -651,10 +754,10 @@ impl Bar { ) )] pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionReview { - use kube::core::conversion::{ConversionRequest, ConversionResponse}; - use kube::core::response::StatusSummary; + review: ::kube::core::conversion::ConversionReview, + ) -> ::kube::core::conversion::ConversionReview { + use ::kube::core::conversion::{ConversionRequest, ConversionResponse}; + use ::kube::core::response::StatusSummary; use stackable_versioned::ConversionError; let request = match ConversionRequest::from_review(review) { Ok(request) => request, @@ -686,9 +789,6 @@ impl Bar { conversion_response.success(converted).into_review() } Err(err) => { - tracing::debug!( - "Failed to converted objects of type {type}", type = stringify!(Bar), - ); let error_message = err.as_human_readable_error_message(); conversion_response .failure(kube::client::Status { @@ -704,12 +804,10 @@ impl Bar { } #[tracing::instrument(skip_all, err)] fn try_convert( - request: &kube::core::conversion::ConversionRequest, + request: &::kube::core::conversion::ConversionRequest, ) -> Result, stackable_versioned::ConversionError> { use stackable_versioned::ConversionError; - let desired_object_version = ::from_str( - &request.desired_api_version, - ) + let desired_object_version = Self::from_api_version(&request.desired_api_version) .map_err(|err| ConversionError::ParseDesiredResourceVersion { source: err, version: request.desired_api_version.to_string(), @@ -746,16 +844,14 @@ impl Bar { send_kind: object_kind.to_string(), }); } - let current_object_version = ::from_str( - object_version, - ) + let current_object_version = Self::from_api_version(object_version) .map_err(|err| ConversionError::ParseCurrentResourceVersion { source: err, version: object_version.to_string(), })?; match (¤t_object_version, &desired_object_version) { (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::BarSpec = serde_json::from_value( + let resource_spec: v1alpha1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -767,87 +863,119 @@ impl Bar { conversion.steps = 0usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::BarSpec = serde_json::from_value( + let resource_spec: v1alpha1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Bar).to_string(), })?; - let resource: v1::BarSpec = resource.into(); + let resource_spec: v1::BarSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V2Alpha1) => { - let resource: v1alpha1::BarSpec = serde_json::from_value( + let resource_spec: v1alpha1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Bar).to_string(), })?; - let resource: v1::BarSpec = resource.into(); - let resource: v2alpha1::BarSpec = resource.into(); + let resource_spec: v1::BarSpec = resource_spec.into(); + let resource_spec: v2alpha1::BarSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v2alpha1"), conversion.steps = 2usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1Alpha1) => { - let resource: v1::BarSpec = serde_json::from_value( + let resource_spec: v1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Bar).to_string(), })?; - let resource: v1alpha1::BarSpec = resource.into(); + let resource_spec: v1alpha1::BarSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v1alpha1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1) => { - let resource: v1::BarSpec = serde_json::from_value( + let resource_spec: v1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -859,87 +987,119 @@ impl Bar { 0usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V2Alpha1) => { - let resource: v1::BarSpec = serde_json::from_value( + let resource_spec: v1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Bar).to_string(), })?; - let resource: v2alpha1::BarSpec = resource.into(); + let resource_spec: v2alpha1::BarSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v2alpha1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V2Alpha1, Self::V1Alpha1) => { - let resource: v2alpha1::BarSpec = serde_json::from_value( + let resource_spec: v2alpha1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Bar).to_string(), })?; - let resource: v1::BarSpec = resource.into(); - let resource: v1alpha1::BarSpec = resource.into(); + let resource_spec: v1::BarSpec = resource_spec.into(); + let resource_spec: v1alpha1::BarSpec = resource_spec.into(); tracing::trace!( from = stringify!(v2alpha1), to = stringify!("v1alpha1"), conversion.steps = 2usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V2Alpha1, Self::V1) => { - let resource: v2alpha1::BarSpec = serde_json::from_value( + let resource_spec: v2alpha1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Bar).to_string(), })?; - let resource: v1::BarSpec = resource.into(); + let resource_spec: v1::BarSpec = resource_spec.into(); tracing::trace!( from = stringify!(v2alpha1), to = stringify!("v1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V2Alpha1, Self::V2Alpha1) => { - let resource: v2alpha1::BarSpec = serde_json::from_value( + let resource_spec: v2alpha1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -951,14 +1111,22 @@ impl Bar { conversion.steps = 0usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } } } diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap index b40b96055..a7fb2446a 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@module_preserve.rs.snap @@ -217,6 +217,7 @@ pub(crate) mod versioned { } } } + /// Parses the version, such as `v1alpha1` impl ::std::str::FromStr for Foo { type Err = stackable_versioned::ParseResourceVersionError; fn from_str(version: &str) -> Result { @@ -232,6 +233,23 @@ pub(crate) mod versioned { } } } + /// Parses the entire `apiVersion`, such as `zookeeper.stackable.tech/v1alpha1`. + impl Foo { + pub fn from_api_version( + api_version: &str, + ) -> Result { + match api_version { + "foo.example.org/v1alpha1" => Ok(Self::V1Alpha1), + "foo.example.org/v1" => Ok(Self::V1), + "foo.example.org/v2alpha1" => Ok(Self::V2Alpha1), + _ => { + Err(stackable_versioned::ParseResourceVersionError::UnknownApiVersion { + api_version: api_version.to_string(), + }) + } + } + } + } impl Foo { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. pub fn merged_crd( @@ -260,10 +278,10 @@ pub(crate) mod versioned { ) )] pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionReview { - use kube::core::conversion::{ConversionRequest, ConversionResponse}; - use kube::core::response::StatusSummary; + review: ::kube::core::conversion::ConversionReview, + ) -> ::kube::core::conversion::ConversionReview { + use ::kube::core::conversion::{ConversionRequest, ConversionResponse}; + use ::kube::core::response::StatusSummary; use stackable_versioned::ConversionError; let request = match ConversionRequest::from_review(review) { Ok(request) => request, @@ -295,10 +313,6 @@ pub(crate) mod versioned { conversion_response.success(converted).into_review() } Err(err) => { - tracing::debug!( - "Failed to converted objects of type {type}", type = - stringify!(Foo), - ); let error_message = err.as_human_readable_error_message(); conversion_response .failure(kube::client::Status { @@ -314,10 +328,10 @@ pub(crate) mod versioned { } #[tracing::instrument(skip_all, err)] fn try_convert( - request: &kube::core::conversion::ConversionRequest, + request: &::kube::core::conversion::ConversionRequest, ) -> Result, stackable_versioned::ConversionError> { use stackable_versioned::ConversionError; - let desired_object_version = ::from_str( + let desired_object_version = Self::from_api_version( &request.desired_api_version, ) .map_err(|err| ConversionError::ParseDesiredResourceVersion { @@ -356,16 +370,14 @@ pub(crate) mod versioned { send_kind: object_kind.to_string(), }); } - let current_object_version = ::from_str( - object_version, - ) + let current_object_version = Self::from_api_version(object_version) .map_err(|err| ConversionError::ParseCurrentResourceVersion { source: err, version: object_version.to_string(), })?; match (¤t_object_version, &desired_object_version) { (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -378,90 +390,122 @@ pub(crate) mod versioned { "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1::FooSpec = resource.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V2Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1::FooSpec = resource.into(); - let resource: v2alpha1::FooSpec = resource.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); + let resource_spec: v2alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v2alpha1"), conversion.steps = 2usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1alpha1::FooSpec = resource.into(); + let resource_spec: v1alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v1alpha1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -473,90 +517,122 @@ pub(crate) mod versioned { .steps = 0usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V2Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v2alpha1::FooSpec = resource.into(); + let resource_spec: v2alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v2alpha1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V2Alpha1, Self::V1Alpha1) => { - let resource: v2alpha1::FooSpec = serde_json::from_value( + let resource_spec: v2alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1::FooSpec = resource.into(); - let resource: v1alpha1::FooSpec = resource.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); + let resource_spec: v1alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v2alpha1), to = stringify!("v1alpha1"), conversion.steps = 2usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V2Alpha1, Self::V1) => { - let resource: v2alpha1::FooSpec = serde_json::from_value( + let resource_spec: v2alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Foo).to_string(), })?; - let resource: v1::FooSpec = resource.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v2alpha1), to = stringify!("v1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V2Alpha1, Self::V2Alpha1) => { - let resource: v2alpha1::FooSpec = serde_json::from_value( + let resource_spec: v2alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -569,14 +645,22 @@ pub(crate) mod versioned { "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } } } @@ -600,6 +684,7 @@ pub(crate) mod versioned { } } } + /// Parses the version, such as `v1alpha1` impl ::std::str::FromStr for Bar { type Err = stackable_versioned::ParseResourceVersionError; fn from_str(version: &str) -> Result { @@ -615,6 +700,23 @@ pub(crate) mod versioned { } } } + /// Parses the entire `apiVersion`, such as `zookeeper.stackable.tech/v1alpha1`. + impl Bar { + pub fn from_api_version( + api_version: &str, + ) -> Result { + match api_version { + "bar.example.org/v1alpha1" => Ok(Self::V1Alpha1), + "bar.example.org/v1" => Ok(Self::V1), + "bar.example.org/v2alpha1" => Ok(Self::V2Alpha1), + _ => { + Err(stackable_versioned::ParseResourceVersionError::UnknownApiVersion { + api_version: api_version.to_string(), + }) + } + } + } + } impl Bar { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. pub fn merged_crd( @@ -643,10 +745,10 @@ pub(crate) mod versioned { ) )] pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionReview { - use kube::core::conversion::{ConversionRequest, ConversionResponse}; - use kube::core::response::StatusSummary; + review: ::kube::core::conversion::ConversionReview, + ) -> ::kube::core::conversion::ConversionReview { + use ::kube::core::conversion::{ConversionRequest, ConversionResponse}; + use ::kube::core::response::StatusSummary; use stackable_versioned::ConversionError; let request = match ConversionRequest::from_review(review) { Ok(request) => request, @@ -678,10 +780,6 @@ pub(crate) mod versioned { conversion_response.success(converted).into_review() } Err(err) => { - tracing::debug!( - "Failed to converted objects of type {type}", type = - stringify!(Bar), - ); let error_message = err.as_human_readable_error_message(); conversion_response .failure(kube::client::Status { @@ -697,10 +795,10 @@ pub(crate) mod versioned { } #[tracing::instrument(skip_all, err)] fn try_convert( - request: &kube::core::conversion::ConversionRequest, + request: &::kube::core::conversion::ConversionRequest, ) -> Result, stackable_versioned::ConversionError> { use stackable_versioned::ConversionError; - let desired_object_version = ::from_str( + let desired_object_version = Self::from_api_version( &request.desired_api_version, ) .map_err(|err| ConversionError::ParseDesiredResourceVersion { @@ -739,16 +837,14 @@ pub(crate) mod versioned { send_kind: object_kind.to_string(), }); } - let current_object_version = ::from_str( - object_version, - ) + let current_object_version = Self::from_api_version(object_version) .map_err(|err| ConversionError::ParseCurrentResourceVersion { source: err, version: object_version.to_string(), })?; match (¤t_object_version, &desired_object_version) { (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::BarSpec = serde_json::from_value( + let resource_spec: v1alpha1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -761,90 +857,122 @@ pub(crate) mod versioned { "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::BarSpec = serde_json::from_value( + let resource_spec: v1alpha1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Bar).to_string(), })?; - let resource: v1::BarSpec = resource.into(); + let resource_spec: v1::BarSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V2Alpha1) => { - let resource: v1alpha1::BarSpec = serde_json::from_value( + let resource_spec: v1alpha1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Bar).to_string(), })?; - let resource: v1::BarSpec = resource.into(); - let resource: v2alpha1::BarSpec = resource.into(); + let resource_spec: v1::BarSpec = resource_spec.into(); + let resource_spec: v2alpha1::BarSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v2alpha1"), conversion.steps = 2usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1Alpha1) => { - let resource: v1::BarSpec = serde_json::from_value( + let resource_spec: v1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Bar).to_string(), })?; - let resource: v1alpha1::BarSpec = resource.into(); + let resource_spec: v1alpha1::BarSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v1alpha1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1) => { - let resource: v1::BarSpec = serde_json::from_value( + let resource_spec: v1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -856,90 +984,122 @@ pub(crate) mod versioned { .steps = 0usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V2Alpha1) => { - let resource: v1::BarSpec = serde_json::from_value( + let resource_spec: v1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Bar).to_string(), })?; - let resource: v2alpha1::BarSpec = resource.into(); + let resource_spec: v2alpha1::BarSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v2alpha1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V2Alpha1, Self::V1Alpha1) => { - let resource: v2alpha1::BarSpec = serde_json::from_value( + let resource_spec: v2alpha1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Bar).to_string(), })?; - let resource: v1::BarSpec = resource.into(); - let resource: v1alpha1::BarSpec = resource.into(); + let resource_spec: v1::BarSpec = resource_spec.into(); + let resource_spec: v1alpha1::BarSpec = resource_spec.into(); tracing::trace!( from = stringify!(v2alpha1), to = stringify!("v1alpha1"), conversion.steps = 2usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V2Alpha1, Self::V1) => { - let resource: v2alpha1::BarSpec = serde_json::from_value( + let resource_spec: v2alpha1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(Bar).to_string(), })?; - let resource: v1::BarSpec = resource.into(); + let resource_spec: v1::BarSpec = resource_spec.into(); tracing::trace!( from = stringify!(v2alpha1), to = stringify!("v1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V2Alpha1, Self::V2Alpha1) => { - let resource: v2alpha1::BarSpec = serde_json::from_value( + let resource_spec: v2alpha1::BarSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -952,14 +1112,22 @@ pub(crate) mod versioned { "Successfully converted {type} object", type = stringify!(Bar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Bar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Bar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } } } diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap index ad4e20b5e..ef09353a1 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@renamed_kind.rs.snap @@ -111,6 +111,7 @@ impl ::std::fmt::Display for FooBar { } } } +/// Parses the version, such as `v1alpha1` #[automatically_derived] impl ::std::str::FromStr for FooBar { type Err = stackable_versioned::ParseResourceVersionError; @@ -127,6 +128,24 @@ impl ::std::str::FromStr for FooBar { } } } +/// Parses the entire `apiVersion`, such as `zookeeper.stackable.tech/v1alpha1`. +#[automatically_derived] +impl FooBar { + pub fn from_api_version( + api_version: &str, + ) -> Result { + match api_version { + "stackable.tech/v1alpha1" => Ok(Self::V1Alpha1), + "stackable.tech/v1beta1" => Ok(Self::V1Beta1), + "stackable.tech/v1" => Ok(Self::V1), + _ => { + Err(stackable_versioned::ParseResourceVersionError::UnknownApiVersion { + api_version: api_version.to_string(), + }) + } + } + } +} #[automatically_derived] impl FooBar { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. @@ -156,10 +175,10 @@ impl FooBar { ) )] pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionReview { - use kube::core::conversion::{ConversionRequest, ConversionResponse}; - use kube::core::response::StatusSummary; + review: ::kube::core::conversion::ConversionReview, + ) -> ::kube::core::conversion::ConversionReview { + use ::kube::core::conversion::{ConversionRequest, ConversionResponse}; + use ::kube::core::response::StatusSummary; use stackable_versioned::ConversionError; let request = match ConversionRequest::from_review(review) { Ok(request) => request, @@ -191,10 +210,6 @@ impl FooBar { conversion_response.success(converted).into_review() } Err(err) => { - tracing::debug!( - "Failed to converted objects of type {type}", type = - stringify!(FooBar), - ); let error_message = err.as_human_readable_error_message(); conversion_response .failure(kube::client::Status { @@ -210,12 +225,10 @@ impl FooBar { } #[tracing::instrument(skip_all, err)] fn try_convert( - request: &kube::core::conversion::ConversionRequest, + request: &::kube::core::conversion::ConversionRequest, ) -> Result, stackable_versioned::ConversionError> { use stackable_versioned::ConversionError; - let desired_object_version = ::from_str( - &request.desired_api_version, - ) + let desired_object_version = Self::from_api_version(&request.desired_api_version) .map_err(|err| ConversionError::ParseDesiredResourceVersion { source: err, version: request.desired_api_version.to_string(), @@ -252,16 +265,14 @@ impl FooBar { send_kind: object_kind.to_string(), }); } - let current_object_version = ::from_str( - object_version, - ) + let current_object_version = Self::from_api_version(object_version) .map_err(|err| ConversionError::ParseCurrentResourceVersion { source: err, version: object_version.to_string(), })?; match (¤t_object_version, &desired_object_version) { (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -274,89 +285,121 @@ impl FooBar { "Successfully converted {type} object", type = stringify!(FooBar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(FooBar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V1Beta1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(FooBar).to_string(), })?; - let resource: v1beta1::FooSpec = resource.into(); + let resource_spec: v1beta1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v1beta1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(FooBar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(FooBar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Alpha1, Self::V1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(FooBar).to_string(), })?; - let resource: v1beta1::FooSpec = resource.into(); - let resource: v1::FooSpec = resource.into(); + let resource_spec: v1beta1::FooSpec = resource_spec.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1alpha1), to = stringify!("v1"), conversion .steps = 2usize, "Successfully converted {type} object", type = stringify!(FooBar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(FooBar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Beta1, Self::V1Alpha1) => { - let resource: v1beta1::FooSpec = serde_json::from_value( + let resource_spec: v1beta1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(FooBar).to_string(), })?; - let resource: v1alpha1::FooSpec = resource.into(); + let resource_spec: v1alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1beta1), to = stringify!("v1alpha1"), conversion.steps = 1usize, "Successfully converted {type} object", type = stringify!(FooBar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(FooBar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Beta1, Self::V1Beta1) => { - let resource: v1beta1::FooSpec = serde_json::from_value( + let resource_spec: v1beta1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -369,87 +412,119 @@ impl FooBar { "Successfully converted {type} object", type = stringify!(FooBar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(FooBar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1Beta1, Self::V1) => { - let resource: v1beta1::FooSpec = serde_json::from_value( + let resource_spec: v1beta1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(FooBar).to_string(), })?; - let resource: v1::FooSpec = resource.into(); + let resource_spec: v1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1beta1), to = stringify!("v1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(FooBar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(FooBar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1Alpha1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(FooBar).to_string(), })?; - let resource: v1beta1::FooSpec = resource.into(); - let resource: v1alpha1::FooSpec = resource.into(); + let resource_spec: v1beta1::FooSpec = resource_spec.into(); + let resource_spec: v1alpha1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v1alpha1"), conversion .steps = 2usize, "Successfully converted {type} object", type = stringify!(FooBar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(FooBar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1Beta1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { source: err, kind: stringify!(FooBar).to_string(), })?; - let resource: v1beta1::FooSpec = resource.into(); + let resource_spec: v1beta1::FooSpec = resource_spec.into(); tracing::trace!( from = stringify!(v1), to = stringify!("v1beta1"), conversion .steps = 1usize, "Successfully converted {type} object", type = stringify!(FooBar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(FooBar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } (Self::V1, Self::V1) => { - let resource: v1::FooSpec = serde_json::from_value( + let resource_spec: v1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -461,14 +536,22 @@ impl FooBar { 0usize, "Successfully converted {type} object", type = stringify!(FooBar), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(FooBar).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(FooBar).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } } } diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap index 456462144..51b430749 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@shortnames.rs.snap @@ -38,6 +38,7 @@ impl ::std::fmt::Display for Foo { } } } +/// Parses the version, such as `v1alpha1` #[automatically_derived] impl ::std::str::FromStr for Foo { type Err = stackable_versioned::ParseResourceVersionError; @@ -52,6 +53,22 @@ impl ::std::str::FromStr for Foo { } } } +/// Parses the entire `apiVersion`, such as `zookeeper.stackable.tech/v1alpha1`. +#[automatically_derived] +impl Foo { + pub fn from_api_version( + api_version: &str, + ) -> Result { + match api_version { + "stackable.tech/v1alpha1" => Ok(Self::V1Alpha1), + _ => { + Err(stackable_versioned::ParseResourceVersionError::UnknownApiVersion { + api_version: api_version.to_string(), + }) + } + } + } +} #[automatically_derived] impl Foo { /// Generates a merged CRD containing all versions and marking `stored_apiversion` as stored. @@ -77,10 +94,10 @@ impl Foo { ) )] pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionReview { - use kube::core::conversion::{ConversionRequest, ConversionResponse}; - use kube::core::response::StatusSummary; + review: ::kube::core::conversion::ConversionReview, + ) -> ::kube::core::conversion::ConversionReview { + use ::kube::core::conversion::{ConversionRequest, ConversionResponse}; + use ::kube::core::response::StatusSummary; use stackable_versioned::ConversionError; let request = match ConversionRequest::from_review(review) { Ok(request) => request, @@ -112,9 +129,6 @@ impl Foo { conversion_response.success(converted).into_review() } Err(err) => { - tracing::debug!( - "Failed to converted objects of type {type}", type = stringify!(Foo), - ); let error_message = err.as_human_readable_error_message(); conversion_response .failure(kube::client::Status { @@ -130,12 +144,10 @@ impl Foo { } #[tracing::instrument(skip_all, err)] fn try_convert( - request: &kube::core::conversion::ConversionRequest, + request: &::kube::core::conversion::ConversionRequest, ) -> Result, stackable_versioned::ConversionError> { use stackable_versioned::ConversionError; - let desired_object_version = ::from_str( - &request.desired_api_version, - ) + let desired_object_version = Self::from_api_version(&request.desired_api_version) .map_err(|err| ConversionError::ParseDesiredResourceVersion { source: err, version: request.desired_api_version.to_string(), @@ -172,16 +184,14 @@ impl Foo { send_kind: object_kind.to_string(), }); } - let current_object_version = ::from_str( - object_version, - ) + let current_object_version = Self::from_api_version(object_version) .map_err(|err| ConversionError::ParseCurrentResourceVersion { source: err, version: object_version.to_string(), })?; match (¤t_object_version, &desired_object_version) { (Self::V1Alpha1, Self::V1Alpha1) => { - let resource: v1alpha1::FooSpec = serde_json::from_value( + let resource_spec: v1alpha1::FooSpec = serde_json::from_value( object_spec.clone(), ) .map_err(|err| ConversionError::DeserializeObjectSpec { @@ -193,14 +203,22 @@ impl Foo { conversion.steps = 0usize, "Successfully converted {type} object", type = stringify!(Foo), ); - converted - .push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec { - source: err, - kind: stringify!(Foo).to_string(), - })?, - ); + let mut object = object.clone(); + *object + .get_mut("spec") + .ok_or_else(|| ConversionError::ObjectHasNoSpec { + })? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec { + source: err, + kind: stringify!(Foo).to_string(), + })?; + *object + .get_mut("apiVersion") + .ok_or_else(|| ConversionError::ObjectHasNoApiVersion { + })? = serde_json::Value::String( + request.desired_api_version.clone(), + ); + converted.push(object); } } } diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap index 0a083b0c6..7055f4505 100644 --- a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__k8s_snapshots@skip.rs.snap @@ -102,10 +102,10 @@ impl Foo { ) )] pub fn convert( - review: kube::core::conversion::ConversionReview, - ) -> kube::core::conversion::ConversionReview { - use kube::core::conversion::{ConversionRequest, ConversionResponse}; - use kube::core::response::StatusSummary; + review: ::kube::core::conversion::ConversionReview, + ) -> ::kube::core::conversion::ConversionReview { + use ::kube::core::conversion::{ConversionRequest, ConversionResponse}; + use ::kube::core::response::StatusSummary; use stackable_versioned::ConversionError; let request = match ConversionRequest::from_review(review) { Ok(request) => request, @@ -137,9 +137,6 @@ impl Foo { conversion_response.success(converted).into_review() } Err(err) => { - tracing::debug!( - "Failed to converted objects of type {type}", type = stringify!(Foo), - ); let error_message = err.as_human_readable_error_message(); conversion_response .failure(kube::client::Status { @@ -155,12 +152,10 @@ impl Foo { } #[tracing::instrument(skip_all, err)] fn try_convert( - request: &kube::core::conversion::ConversionRequest, + request: &::kube::core::conversion::ConversionRequest, ) -> Result, stackable_versioned::ConversionError> { use stackable_versioned::ConversionError; - let desired_object_version = ::from_str( - &request.desired_api_version, - ) + let desired_object_version = Self::from_api_version(&request.desired_api_version) .map_err(|err| ConversionError::ParseDesiredResourceVersion { source: err, version: request.desired_api_version.to_string(), @@ -197,9 +192,7 @@ impl Foo { send_kind: object_kind.to_string(), }); } - let current_object_version = ::from_str( - object_version, - ) + let current_object_version = Self::from_api_version(object_version) .map_err(|err| ConversionError::ParseCurrentResourceVersion { source: err, version: object_version.to_string(), diff --git a/crates/stackable-versioned-macros/src/codegen/container/mod.rs b/crates/stackable-versioned-macros/src/codegen/container/mod.rs index c29afc1dc..f648d42d3 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/mod.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/mod.rs @@ -120,6 +120,7 @@ impl Container { &s.common.idents.original, enum_variant_idents, enum_variant_strings, + kubernetes_options, )); Some(tokens) diff --git a/crates/stackable-versioned-macros/src/codegen/container/struct.rs b/crates/stackable-versioned-macros/src/codegen/container/struct.rs index 012a2634f..64c22b765 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/struct.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/struct.rs @@ -353,7 +353,11 @@ impl Struct { match &self.common.options.kubernetes_options { Some(kubernetes_options) if !kubernetes_options.skip_merged_crd => { + let k8s_group = &kubernetes_options.group; let enum_ident = &self.common.idents.kubernetes; + let api_versions = enum_variant_strings + .iter() + .map(|version| format!("{k8s_group}/{version}")); // Only add the #[automatically_derived] attribute if this impl is used outside of a // module (in standalone mode). @@ -364,6 +368,9 @@ impl Struct { let k8s_openapi_path = &*kubernetes_options.crates.k8s_openapi; let kube_core_path = &*kubernetes_options.crates.kube_core; + // FIXME + let versioned_path = quote! { stackable_versioned }; + Some(quote! { #automatically_derived #vis enum #enum_ident { @@ -379,14 +386,26 @@ impl Struct { } } + /// Parses the version, such as `v1alpha1` #automatically_derived impl ::std::str::FromStr for #enum_ident { - type Err = stackable_versioned::ParseResourceVersionError; + type Err = #versioned_path::ParseResourceVersionError; fn from_str(version: &str) -> Result { match version { #(#enum_variant_strings => Ok(Self::#enum_variant_idents),)* - _ => Err(stackable_versioned::ParseResourceVersionError::UnknownResourceVersion{version: version.to_string()}), + _ => Err(#versioned_path::ParseResourceVersionError::UnknownResourceVersion{version: version.to_string()}), + } + } + } + + /// Parses the entire `apiVersion`, such as `zookeeper.stackable.tech/v1alpha1`. + #automatically_derived + impl #enum_ident { + pub fn from_api_version(api_version: &str) -> Result { + match api_version { + #(#api_versions => Ok(Self::#enum_variant_idents),)* + _ => Err(#versioned_path::ParseResourceVersionError::UnknownApiVersion{api_version: api_version.to_string()}), } } } diff --git a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs index 2bac6ec80..3228c0628 100644 --- a/crates/stackable-versioned-macros/src/codegen/flux_converter.rs +++ b/crates/stackable-versioned-macros/src/codegen/flux_converter.rs @@ -2,14 +2,23 @@ use darling::util::IdentString; use proc_macro2::TokenStream; use quote::quote; +use super::container::KubernetesOptions; + pub(crate) fn generate_kubernetes_conversion( enum_ident: &IdentString, struct_ident: &IdentString, enum_variant_idents: &[IdentString], enum_variant_strings: &[String], + kubernetes_options: &KubernetesOptions, ) -> Option { assert_eq!(enum_variant_idents.len(), enum_variant_strings.len()); + // Get the crate paths + let kube_core_path = &*kubernetes_options.crates.kube_core; + // FIXME + let kube_path = quote! {kube}; + let versioned_path = quote! {stackable_versioned}; + let versions = enum_variant_idents .iter() .zip(enum_variant_strings) @@ -27,11 +36,11 @@ pub(crate) fn generate_kubernetes_conversion( let src_lower = src_lower.parse::().expect("The versions always needs to be a valid TokenStream"); quote! { (Self::#src, Self::#dst) => { - let resource: #src_lower::#struct_ident = serde_json::from_value(object_spec.clone()) + let resource_spec: #src_lower::#struct_ident = serde_json::from_value(object_spec.clone()) .map_err(|err| ConversionError::DeserializeObjectSpec{source: err, kind: stringify!(#enum_ident).to_string()})?; #( - let resource: #version_chain_string::#struct_ident = resource.into(); + let resource_spec: #version_chain_string::#struct_ident = resource_spec.into(); )* tracing::trace!( @@ -42,10 +51,12 @@ pub(crate) fn generate_kubernetes_conversion( type = stringify!(#enum_ident), ); - converted.push( - serde_json::to_value(resource) - .map_err(|err| ConversionError::SerializeObjectSpec{source: err, kind: stringify!(#enum_ident).to_string()})? - ); + let mut object = object.clone(); + *object.get_mut("spec").ok_or_else(|| ConversionError::ObjectHasNoSpec{})? = serde_json::to_value(resource_spec) + .map_err(|err| ConversionError::SerializeObjectSpec{source: err, kind: stringify!(#enum_ident).to_string()})?; + *object.get_mut("apiVersion").ok_or_else(|| ConversionError::ObjectHasNoApiVersion{})? + = serde_json::Value::String(request.desired_api_version.clone()); + converted.push(object); }} }, ); @@ -60,11 +71,11 @@ pub(crate) fn generate_kubernetes_conversion( conversion.api_version = review.types.api_version, ) )] - pub fn convert(review: kube::core::conversion::ConversionReview) -> kube::core::conversion::ConversionReview { + pub fn convert(review: #kube_core_path::conversion::ConversionReview) -> #kube_core_path::conversion::ConversionReview { // Intentionally not using `snafu::ResultExt` here to keep the number of dependencies minimal - use kube::core::conversion::{ConversionRequest, ConversionResponse}; - use kube::core::response::StatusSummary; - use stackable_versioned::ConversionError; + use #kube_core_path::conversion::{ConversionRequest, ConversionResponse}; + use #kube_core_path::response::StatusSummary; + use #versioned_path::ConversionError; let request = match ConversionRequest::from_review(review) { Ok(request) => request, @@ -75,7 +86,7 @@ pub(crate) fn generate_kubernetes_conversion( ); return ConversionResponse::invalid( - kube::client::Status { + #kube_path::client::Status { status: Some(StatusSummary::Failure), code: 400, message: format!("The ConversionReview send did not include any request: {err}"), @@ -100,15 +111,10 @@ pub(crate) fn generate_kubernetes_conversion( conversion_response.success(converted).into_review() }, Err(err) => { - tracing::debug!( - "Failed to converted objects of type {type}", - type = stringify!(#enum_ident), - ); - let error_message = err.as_human_readable_error_message(); conversion_response.failure( - kube::client::Status { + #kube_path::client::Status { status: Some(StatusSummary::Failure), code: err.http_return_code(), message: error_message.clone(), @@ -124,12 +130,12 @@ pub(crate) fn generate_kubernetes_conversion( skip_all, err )] - fn try_convert(request: &kube::core::conversion::ConversionRequest) -> Result, stackable_versioned::ConversionError> { - use stackable_versioned::ConversionError; + fn try_convert(request: &#kube_core_path::conversion::ConversionRequest) -> Result, #versioned_path::ConversionError> { + use #versioned_path::ConversionError; // FIXME: Check that request.types.{kind,api_version} match the expected values - let desired_object_version = ::from_str(&request.desired_api_version) + let desired_object_version = Self::from_api_version(&request.desired_api_version) .map_err(|err| ConversionError::ParseDesiredResourceVersion{ source: err, version: request.desired_api_version.to_string() @@ -147,7 +153,7 @@ pub(crate) fn generate_kubernetes_conversion( return Err(ConversionError::WrongObjectKind{expected_kind: stringify!(#enum_ident).to_string(), send_kind: object_kind.to_string()}); } - let current_object_version = ::from_str(object_version) + let current_object_version = Self::from_api_version(object_version) .map_err(|err| ConversionError::ParseCurrentResourceVersion{ source: err, version: object_version.to_string() diff --git a/crates/stackable-versioned/fixtures/inputs/fail/undeserializable_missing_field.json b/crates/stackable-versioned/fixtures/inputs/fail/undeserializable_missing_field.json index 33bca0d9b..56b8c5c8d 100644 --- a/crates/stackable-versioned/fixtures/inputs/fail/undeserializable_missing_field.json +++ b/crates/stackable-versioned/fixtures/inputs/fail/undeserializable_missing_field.json @@ -3,10 +3,10 @@ "apiVersion": "apiextensions.k8s.io/v1", "request": { "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "desiredAPIVersion": "v3", + "desiredAPIVersion": "test.stackable.tech/v3", "objects": [ { - "apiVersion": "v1alpha1", + "apiVersion": "test.stackable.tech/v1alpha1", "kind": "Person", "metadata": { "name": "sbernauer", diff --git a/crates/stackable-versioned/fixtures/inputs/fail/unkown_current_version.json b/crates/stackable-versioned/fixtures/inputs/fail/unkown_current_version.json index 5b7c29e80..6df3bb2d1 100644 --- a/crates/stackable-versioned/fixtures/inputs/fail/unkown_current_version.json +++ b/crates/stackable-versioned/fixtures/inputs/fail/unkown_current_version.json @@ -3,10 +3,10 @@ "apiVersion": "apiextensions.k8s.io/v1", "request": { "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "desiredAPIVersion": "v3", + "desiredAPIVersion": "test.stackable.tech/v3", "objects": [ { - "apiVersion": "v99", + "apiVersion": "test.stackable.tech/v99", "kind": "Person", "metadata": { "name": "sbernauer", diff --git a/crates/stackable-versioned/fixtures/inputs/fail/unkown_desired_version.json b/crates/stackable-versioned/fixtures/inputs/fail/unkown_desired_version.json index 17d903f43..736a95dfb 100644 --- a/crates/stackable-versioned/fixtures/inputs/fail/unkown_desired_version.json +++ b/crates/stackable-versioned/fixtures/inputs/fail/unkown_desired_version.json @@ -3,10 +3,10 @@ "apiVersion": "apiextensions.k8s.io/v1", "request": { "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "desiredAPIVersion": "v99", + "desiredAPIVersion": "test.stackable.tech/v99", "objects": [ { - "apiVersion": "v1alpha1", + "apiVersion": "test.stackable.tech/v1alpha1", "kind": "Person", "metadata": { "name": "sbernauer", diff --git a/crates/stackable-versioned/fixtures/inputs/fail/wrong_object.json b/crates/stackable-versioned/fixtures/inputs/fail/wrong_object.json index 0e8b24f2e..3c5bbcd67 100644 --- a/crates/stackable-versioned/fixtures/inputs/fail/wrong_object.json +++ b/crates/stackable-versioned/fixtures/inputs/fail/wrong_object.json @@ -3,10 +3,10 @@ "apiVersion": "apiextensions.k8s.io/v1", "request": { "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "desiredAPIVersion": "v3", + "desiredAPIVersion": "test.stackable.tech/v3", "objects": [ { - "apiVersion": "v1alpha1", + "apiVersion": "test.stackable.tech/v1alpha1", "kind": "SomeOtherResource", "metadata": { "name": "sbernauer", diff --git a/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json b/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json index c27a60f4b..89a1d1704 100644 --- a/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json +++ b/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json @@ -3,10 +3,10 @@ "apiVersion": "apiextensions.k8s.io/v1", "request": { "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "desiredAPIVersion": "v1alpha1", + "desiredAPIVersion": "test.stackable.tech/v1alpha1", "objects": [ { - "apiVersion": "v1alpha1", + "apiVersion": "test.stackable.tech/v1alpha1", "kind": "Person", "metadata": { "name": "sbernauer", @@ -18,7 +18,7 @@ } }, { - "apiVersion": "v1alpha2", + "apiVersion": "test.stackable.tech/v1alpha2", "kind": "Person", "spec": { "username": "sbernauer", @@ -27,7 +27,7 @@ } }, { - "apiVersion": "v1beta1", + "apiVersion": "test.stackable.tech/v1beta1", "kind": "Person", "spec": { "username": "sbernauer", @@ -36,7 +36,7 @@ } }, { - "apiVersion": "v2", + "apiVersion": "test.stackable.tech/v2", "kind": "Person", "spec": { "username": "sbernauer", @@ -46,7 +46,7 @@ } }, { - "apiVersion": "v3", + "apiVersion": "test.stackable.tech/v3", "kind": "Person", "spec": { "username": "sbernauer", diff --git a/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json b/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json index fa678172f..6f1429da9 100644 --- a/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json +++ b/crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json @@ -3,10 +3,10 @@ "apiVersion": "apiextensions.k8s.io/v1", "request": { "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "desiredAPIVersion": "v3", + "desiredAPIVersion": "test.stackable.tech/v3", "objects": [ { - "apiVersion": "v1alpha1", + "apiVersion": "test.stackable.tech/v1alpha1", "kind": "Person", "metadata": { "name": "sbernauer", @@ -18,7 +18,7 @@ } }, { - "apiVersion": "v1alpha2", + "apiVersion": "test.stackable.tech/v1alpha2", "kind": "Person", "spec": { "username": "sbernauer", @@ -27,7 +27,7 @@ } }, { - "apiVersion": "v1beta1", + "apiVersion": "test.stackable.tech/v1beta1", "kind": "Person", "spec": { "username": "sbernauer", @@ -36,7 +36,7 @@ } }, { - "apiVersion": "v2", + "apiVersion": "test.stackable.tech/v2", "kind": "Person", "spec": { "username": "sbernauer", @@ -46,7 +46,7 @@ } }, { - "apiVersion": "v3", + "apiVersion": "test.stackable.tech/v3", "kind": "Person", "spec": { "username": "sbernauer", diff --git a/crates/stackable-versioned/src/flux_converter/tests/mod.rs b/crates/stackable-versioned/src/flux_converter/tests/mod.rs index 3aaad92fe..c30dd3d25 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/mod.rs +++ b/crates/stackable-versioned/src/flux_converter/tests/mod.rs @@ -78,7 +78,7 @@ impl From for Gender { } } -/// TEMP, we need to implement downgrades manually +// TEMP, we need to implement downgrades manually impl From for v1alpha1::PersonSpec { fn from(value: v1alpha2::PersonSpec) -> Self { Self { @@ -114,7 +114,7 @@ impl From for v2::PersonSpec { } } } -/// END TEMP +// END TEMP #[cfg(test)] mod tests { @@ -129,14 +129,15 @@ mod tests { fn pass() { glob!("../../../fixtures/inputs/pass/", "*.json", |path| { let (request, response) = run_for_file(path); - let response = response - .response - .expect("ConversionReview had no response!"); let formatted = serde_json::to_string_pretty(&response) .expect("Failed to serialize ConversionResponse"); assert_snapshot!(formatted); + let response = response + .response + .expect("ConversionReview had no response!"); + assert_eq!( response.result.status, Some(StatusSummary::Success), @@ -150,14 +151,15 @@ mod tests { fn fail() { glob!("../../../fixtures/inputs/fail/", "*.json", |path| { let (request, response) = run_for_file(path); - let response = response - .response - .expect("ConversionReview had no response!"); let formatted = serde_json::to_string_pretty(&response) .expect("Failed to serialize ConversionResponse"); assert_snapshot!(formatted); + let response = response + .response + .expect("ConversionReview had no response!"); + assert_eq!( response.result.status, Some(StatusSummary::Failure), diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@request_missing.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@request_missing.json.snap index cfed7020d..dd3931fc0 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@request_missing.json.snap +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@request_missing.json.snap @@ -4,12 +4,16 @@ expression: formatted input_file: crates/stackable-versioned/fixtures/inputs/fail/request_missing.json --- { - "uid": "", - "result": { - "status": "Failure", - "code": 400, - "message": "The ConversionReview send did not include any request: request missing in ConversionReview", - "reason": "ConversionReview request missing" - }, - "convertedObjects": [] + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "ConversionReview", + "response": { + "uid": "", + "result": { + "status": "Failure", + "code": 400, + "message": "The ConversionReview send did not include any request: request missing in ConversionReview", + "reason": "ConversionReview request missing" + }, + "convertedObjects": [] + } } diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@undeserializable_missing_field.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@undeserializable_missing_field.json.snap index 2079f0cb4..a713b1fa6 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@undeserializable_missing_field.json.snap +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@undeserializable_missing_field.json.snap @@ -4,12 +4,16 @@ expression: formatted input_file: crates/stackable-versioned/fixtures/inputs/fail/undeserializable_missing_field.json --- { - "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "result": { - "status": "Failure", - "code": 500, - "message": "failed to deserialize object of kind \"Person\": missing field `username`", - "reason": "failed to deserialize object of kind \"Person\": missing field `username`" - }, - "convertedObjects": [] + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "ConversionReview", + "response": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "result": { + "status": "Failure", + "code": 500, + "message": "failed to deserialize object of kind \"Person\": missing field `username`", + "reason": "failed to deserialize object of kind \"Person\": missing field `username`" + }, + "convertedObjects": [] + } } diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_current_version.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_current_version.json.snap index 98bf3008a..4fdd3285e 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_current_version.json.snap +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_current_version.json.snap @@ -4,12 +4,16 @@ expression: formatted input_file: crates/stackable-versioned/fixtures/inputs/fail/unkown_current_version.json --- { - "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "result": { - "status": "Failure", - "code": 500, - "message": "failed to parse current resource version \"v99\": the resource version \"v99\" is not known", - "reason": "failed to parse current resource version \"v99\": the resource version \"v99\" is not known" - }, - "convertedObjects": [] + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "ConversionReview", + "response": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "result": { + "status": "Failure", + "code": 500, + "message": "failed to parse current resource version \"test.stackable.tech/v99\": the api version \"test.stackable.tech/v99\" is not known", + "reason": "failed to parse current resource version \"test.stackable.tech/v99\": the api version \"test.stackable.tech/v99\" is not known" + }, + "convertedObjects": [] + } } diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_desired_version.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_desired_version.json.snap index f3ebe1f7f..4499203b6 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_desired_version.json.snap +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@unkown_desired_version.json.snap @@ -4,12 +4,16 @@ expression: formatted input_file: crates/stackable-versioned/fixtures/inputs/fail/unkown_desired_version.json --- { - "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "result": { - "status": "Failure", - "code": 500, - "message": "failed to parse desired resource version \"v99\": the resource version \"v99\" is not known", - "reason": "failed to parse desired resource version \"v99\": the resource version \"v99\" is not known" - }, - "convertedObjects": [] + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "ConversionReview", + "response": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "result": { + "status": "Failure", + "code": 500, + "message": "failed to parse desired resource version \"test.stackable.tech/v99\": the api version \"test.stackable.tech/v99\" is not known", + "reason": "failed to parse desired resource version \"test.stackable.tech/v99\": the api version \"test.stackable.tech/v99\" is not known" + }, + "convertedObjects": [] + } } diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@wrong_object.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@wrong_object.json.snap index e4c3f6617..932eebbd8 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@wrong_object.json.snap +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__fail@wrong_object.json.snap @@ -4,12 +4,16 @@ expression: formatted input_file: crates/stackable-versioned/fixtures/inputs/fail/wrong_object.json --- { - "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "result": { - "status": "Failure", - "code": 400, - "message": "I was asked to convert the kind \"SomeOtherResource\", but I can only convert objects of kind \"Person\"", - "reason": "I was asked to convert the kind \"SomeOtherResource\", but I can only convert objects of kind \"Person\"" - }, - "convertedObjects": [] + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "ConversionReview", + "response": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "result": { + "status": "Failure", + "code": 400, + "message": "I was asked to convert the kind \"SomeOtherResource\", but I can only convert objects of kind \"Person\"", + "reason": "I was asked to convert the kind \"SomeOtherResource\", but I can only convert objects of kind \"Person\"" + }, + "convertedObjects": [] + } } diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v1alpha1.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v1alpha1.json.snap index 98a2e8ba0..4ec591f94 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v1alpha1.json.snap +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v1alpha1.json.snap @@ -4,25 +4,54 @@ expression: formatted input_file: crates/stackable-versioned/fixtures/inputs/pass/persons_to_v1alpha1.json --- { - "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "result": { - "status": "Success" - }, - "convertedObjects": [ - { - "username": "sbernauer" + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "ConversionReview", + "response": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "result": { + "status": "Success" }, - { - "username": "sbernauer" - }, - { - "username": "sbernauer" - }, - { - "username": "sbernauer" - }, - { - "username": "sbernauer" - } - ] + "convertedObjects": [ + { + "apiVersion": "test.stackable.tech/v1alpha1", + "kind": "Person", + "metadata": { + "name": "sbernauer", + "namespace": "default", + "uid": "d41e2019-5de3-409c-a7b2-0799ecb95e4b" + }, + "spec": { + "username": "sbernauer" + } + }, + { + "apiVersion": "test.stackable.tech/v1alpha1", + "kind": "Person", + "spec": { + "username": "sbernauer" + } + }, + { + "apiVersion": "test.stackable.tech/v1alpha1", + "kind": "Person", + "spec": { + "username": "sbernauer" + } + }, + { + "apiVersion": "test.stackable.tech/v1alpha1", + "kind": "Person", + "spec": { + "username": "sbernauer" + } + }, + { + "apiVersion": "test.stackable.tech/v1alpha1", + "kind": "Person", + "spec": { + "username": "sbernauer" + } + } + ] + } } diff --git a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v3.json.snap b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v3.json.snap index 5a8bfccd3..f145a76ef 100644 --- a/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v3.json.snap +++ b/crates/stackable-versioned/src/flux_converter/tests/snapshots/stackable_versioned__flux_converter__tests__tests__pass@persons_to_v3.json.snap @@ -4,40 +4,69 @@ expression: formatted input_file: crates/stackable-versioned/fixtures/inputs/pass/persons_to_v3.json --- { - "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", - "result": { - "status": "Success" - }, - "convertedObjects": [ - { - "firstName": "", - "gender": "Unknown", - "lastName": "", - "username": "sbernauer" + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "ConversionReview", + "response": { + "uid": "c4e55572-ee1f-4e94-9097-28936985d45f", + "result": { + "status": "Success" }, - { - "firstName": "Sebastian", - "gender": "Unknown", - "lastName": "Bernauer", - "username": "sbernauer" - }, - { - "firstName": "Sebastian", - "gender": "Unknown", - "lastName": "Bernauer", - "username": "sbernauer" - }, - { - "firstName": "Sebastian", - "gender": "Male", - "lastName": "Bernauer", - "username": "sbernauer" - }, - { - "firstName": "Sebastian", - "gender": "Male", - "lastName": "Bernauer", - "username": "sbernauer" - } - ] + "convertedObjects": [ + { + "apiVersion": "test.stackable.tech/v3", + "kind": "Person", + "metadata": { + "name": "sbernauer", + "namespace": "default", + "uid": "d41e2019-5de3-409c-a7b2-0799ecb95e4b" + }, + "spec": { + "firstName": "", + "gender": "Unknown", + "lastName": "", + "username": "sbernauer" + } + }, + { + "apiVersion": "test.stackable.tech/v3", + "kind": "Person", + "spec": { + "firstName": "Sebastian", + "gender": "Unknown", + "lastName": "Bernauer", + "username": "sbernauer" + } + }, + { + "apiVersion": "test.stackable.tech/v3", + "kind": "Person", + "spec": { + "firstName": "Sebastian", + "gender": "Unknown", + "lastName": "Bernauer", + "username": "sbernauer" + } + }, + { + "apiVersion": "test.stackable.tech/v3", + "kind": "Person", + "spec": { + "firstName": "Sebastian", + "gender": "Male", + "lastName": "Bernauer", + "username": "sbernauer" + } + }, + { + "apiVersion": "test.stackable.tech/v3", + "kind": "Person", + "spec": { + "firstName": "Sebastian", + "gender": "Male", + "lastName": "Bernauer", + "username": "sbernauer" + } + } + ] + } } diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index bb2df6ae3..0499fd5f8 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -28,6 +28,9 @@ pub use flux_converter::ConversionError; pub enum ParseResourceVersionError { #[snafu(display("the resource version \"{version}\" is not known"))] UnknownResourceVersion { version: String }, + + #[snafu(display("the api version \"{api_version}\" is not known"))] + UnknownApiVersion { api_version: String }, } // Unused for now, might get picked up again in the future. From 78345363a89158049996293dc0c9aa3b0bb9d89c Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Wed, 21 May 2025 08:58:26 +0200 Subject: [PATCH 23/23] Improve Cargo.toml --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1de2f888d..2e63b12e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ itertools = "0.14.0" json-patch = "4.0.0" k8s-openapi = { version = "0.25.0", default-features = false, features = ["schemars", "v1_33"] } # We use rustls instead of openssl for easier portability, e.g. so that we can build stackablectl without the need to vendor (build from source) openssl +# Use ring instead of aws-lc-rs, as this currently fails to build in "make run-dev" kube = { version = "1.0.0", default-features = false, features = ["client", "jsonpatch", "runtime", "derive", "rustls-tls", "ring"] } opentelemetry = "0.29.1" opentelemetry_sdk = { version = "0.29.0", features = ["rt-tokio"] } @@ -64,7 +65,7 @@ syn = "2.0.77" tempfile = "3.12.0" time = { version = "0.3.36" } tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread", "fs"] } -# Use ring instead of aws-lc-rs +# Use ring instead of aws-lc-rs, as this currently fails to build in "make run-dev" tokio-rustls = { version = "0.26.0", default-features = false, features = ["ring", "logging", "tls12"] } tokio-test = "0.4.4" tower = { version = "0.5.1", features = ["util"] }