From 1b2da79c1e015c71e3e713feabd70dde430a0b35 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Wed, 14 May 2025 18:43:54 +0200 Subject: [PATCH 01/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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