diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index c90f09dd..a7364352 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ### Fixed +- Fix the logback configuration for logback versions from 1.3.6/1.4.6 to 1.3.11/1.4.11 ([#874]). - BREAKING: Avoid clashing volumes and mounts by only adding volumes or mounts if they do not already exist. This makes functions such as `PodBuilder::add_volume` or `ContainerBuilder::add_volume_mount` as well as related ones fallible ([#871]). ### Changed @@ -13,6 +14,7 @@ All notable changes to this project will be documented in this file. - BREAKING: Remove the `unique_identifier` argument from `ResolvedS3Connection::add_volumes_and_mounts`, `ResolvedS3Connection::volumes_and_mounts` and `ResolvedS3Connection::credentials_mount_paths` as it is not needed anymore ([#871]). [#871]: https://github.com/stackabletech/operator-rs/pull/871 +[#874]: https://github.com/stackabletech/operator-rs/pull/874 ## [0.76.0] - 2024-09-19 diff --git a/crates/stackable-operator/src/product_logging/framework.rs b/crates/stackable-operator/src/product_logging/framework.rs index ad0d16c3..5acfa2de 100644 --- a/crates/stackable-operator/src/product_logging/framework.rs +++ b/crates/stackable-operator/src/product_logging/framework.rs @@ -605,7 +605,19 @@ pub fn create_logback_config( {max_log_file_size_in_mib}MB - 5 seconds + + 5000 diff --git a/crates/stackable-versioned-macros/src/attrs/common/container.rs b/crates/stackable-versioned-macros/src/attrs/common/container.rs index d5a3ccb0..434c9e1a 100644 --- a/crates/stackable-versioned-macros/src/attrs/common/container.rs +++ b/crates/stackable-versioned-macros/src/attrs/common/container.rs @@ -141,7 +141,10 @@ pub(crate) struct OptionAttributes { #[derive(Clone, Debug, FromMeta)] pub(crate) struct KubernetesAttributes { pub(crate) skip: Option, + pub(crate) singular: Option, + pub(crate) plural: Option, pub(crate) kind: Option, + pub(crate) namespaced: Flag, pub(crate) group: String, } diff --git a/crates/stackable-versioned-macros/src/codegen/common/container.rs b/crates/stackable-versioned-macros/src/codegen/common/container.rs index 26011259..4472552d 100644 --- a/crates/stackable-versioned-macros/src/codegen/common/container.rs +++ b/crates/stackable-versioned-macros/src/codegen/common/container.rs @@ -1,5 +1,7 @@ use std::ops::Deref; +use convert_case::{Case, Casing}; +use k8s_version::Version; use proc_macro2::TokenStream; use quote::format_ident; use syn::{Attribute, Ident, Visibility}; @@ -52,6 +54,16 @@ impl IdentExt for Ident { } } +pub(crate) trait VersionExt { + fn as_variant_ident(&self) -> Ident; +} + +impl VersionExt for Version { + fn as_variant_ident(&self) -> Ident { + format_ident!("{ident}", ident = self.to_string().to_case(Case::Pascal)) + } +} + /// This struct bundles values from [`DeriveInput`][1]. /// /// [`DeriveInput`][1] cannot be used directly when constructing a @@ -118,6 +130,9 @@ impl VersionedContainer { let kubernetes_options = attributes.kubernetes_attrs.map(|a| KubernetesOptions { skip_merged_crd: a.skip.map_or(false, |s| s.merged_crd.is_present()), + namespaced: a.namespaced.is_present(), + singular: a.singular, + plural: a.plural, group: a.group, kind: a.kind, }); @@ -166,7 +181,10 @@ pub(crate) struct VersionedContainerOptions { #[derive(Debug)] pub(crate) struct KubernetesOptions { + pub(crate) singular: Option, + pub(crate) plural: Option, pub(crate) skip_merged_crd: bool, pub(crate) kind: Option, + pub(crate) namespaced: bool, pub(crate) group: String, } diff --git a/crates/stackable-versioned-macros/src/codegen/vstruct/mod.rs b/crates/stackable-versioned-macros/src/codegen/vstruct/mod.rs index 6dd49390..0c6b42c5 100644 --- a/crates/stackable-versioned-macros/src/codegen/vstruct/mod.rs +++ b/crates/stackable-versioned-macros/src/codegen/vstruct/mod.rs @@ -8,13 +8,17 @@ use syn::{parse_quote, DataStruct, Error, Ident}; use crate::{ attrs::common::ContainerAttributes, codegen::{ - common::{Container, ContainerInput, ContainerVersion, Item, VersionedContainer}, + common::{ + Container, ContainerInput, ContainerVersion, Item, VersionExt, VersionedContainer, + }, vstruct::field::VersionedField, }, }; pub(crate) mod field; +type GenerateVersionReturn = (TokenStream, Option<(TokenStream, (Ident, String))>); + /// Stores individual versions of a single struct. Each version tracks field /// actions, which describe if the field was added, renamed or deprecated in /// that version. Fields which are not versioned, are included in every @@ -85,24 +89,30 @@ impl Container for VersionedStruct { } fn generate_tokens(&self) -> TokenStream { - let mut kubernetes_crd_fn_calls = TokenStream::new(); - let mut container_definition = TokenStream::new(); + let mut tokens = TokenStream::new(); + + let mut enum_variants = Vec::new(); + let mut crd_fn_calls = Vec::new(); let mut versions = self.versions.iter().peekable(); while let Some(version) = versions.next() { - container_definition.extend(self.generate_version(version, versions.peek().copied())); - kubernetes_crd_fn_calls.extend(self.generate_kubernetes_crd_fn_call(version)); + let (container_definition, merged_crd) = + self.generate_version(version, versions.peek().copied()); + + if let Some((crd_fn_call, enum_variant)) = merged_crd { + enum_variants.push(enum_variant); + crd_fn_calls.push(crd_fn_call); + } + + tokens.extend(container_definition); } - // If tokens for the 'crd()' function calls were generated, also generate - // the 'merge_crds' call. - if !kubernetes_crd_fn_calls.is_empty() { - container_definition - .extend(self.generate_kubernetes_merge_crds(kubernetes_crd_fn_calls)); + if !crd_fn_calls.is_empty() { + tokens.extend(self.generate_kubernetes_merge_crds(crd_fn_calls, enum_variants)); } - container_definition + tokens } } @@ -112,7 +122,7 @@ impl VersionedStruct { &self, version: &ContainerVersion, next_version: Option<&ContainerVersion>, - ) -> TokenStream { + ) -> GenerateVersionReturn { let mut token_stream = TokenStream::new(); let original_attributes = &self.original_attributes; @@ -137,7 +147,27 @@ impl VersionedStruct { let version_specific_docs = self.generate_struct_docs(version); // Generate K8s specific code - let kubernetes_cr_derive = self.generate_kubernetes_cr_derive(version); + let (kubernetes_cr_derive, merged_crd) = match &self.options.kubernetes_options { + Some(options) => { + // Generate the CustomResource derive macro with the appropriate + // attributes supplied using #[kube()]. + let cr_derive = self.generate_kubernetes_cr_derive(version); + + // Generate merged_crd specific code when not opted out. + let merged_crd = if !options.skip_merged_crd { + let crd_fn_call = self.generate_kubernetes_crd_fn_call(version); + let enum_variant = version.inner.as_variant_ident(); + let enum_display = version.inner.to_string(); + + Some((crd_fn_call, (enum_variant, enum_display))) + } else { + None + }; + + (Some(cr_derive), merged_crd) + } + None => (None, None), + }; // Generate tokens for the module and the contained struct token_stream.extend(quote! { @@ -160,7 +190,7 @@ impl VersionedStruct { token_stream.extend(self.generate_from_impl(version, next_version)); } - token_stream + (token_stream, merged_crd) } /// Generates version specific doc comments for the struct. @@ -253,6 +283,7 @@ impl VersionedStruct { /// attributes. fn generate_kubernetes_cr_derive(&self, version: &ContainerVersion) -> Option { if let Some(kubernetes_options) = &self.options.kubernetes_options { + // Required arguments let group = &kubernetes_options.group; let version = version.inner.to_string(); let kind = kubernetes_options @@ -260,9 +291,22 @@ impl VersionedStruct { .as_ref() .map_or(self.idents.kubernetes.to_string(), |kind| kind.clone()); + // Optional arguments + let namespaced = kubernetes_options + .namespaced + .then_some(quote! { , namespaced }); + let singular = kubernetes_options + .singular + .as_ref() + .map(|s| quote! { , singular = #s }); + let plural = kubernetes_options + .plural + .as_ref() + .map(|p| quote! { , plural = #p }); + return Some(quote! { #[derive(::kube::CustomResource)] - #[kube(group = #group, version = #version, kind = #kind)] + #[kube(group = #group, version = #version, kind = #kind #singular #plural #namespaced)] }); } @@ -270,21 +314,29 @@ impl VersionedStruct { } /// Generates the `merge_crds` function call. - fn generate_kubernetes_merge_crds(&self, fn_calls: TokenStream) -> TokenStream { + fn generate_kubernetes_merge_crds( + &self, + crd_fn_calls: Vec, + enum_variants: Vec<(Ident, String)>, + ) -> TokenStream { let ident = &self.idents.kubernetes; + let version_enum_definition = self.generate_kubernetes_version_enum(enum_variants); + quote! { #[automatically_derived] pub struct #ident; + #version_enum_definition + #[automatically_derived] impl #ident { /// Generates a merged CRD which contains all versions defined using the /// `#[versioned()]` macro. pub fn merged_crd( - stored_apiversion: &str + stored_apiversion: Version ) -> ::std::result::Result<::k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition, ::kube::core::crd::MergeError> { - ::kube::core::crd::merge_crds(vec![#fn_calls], stored_apiversion) + ::kube::core::crd::merge_crds(vec![#(#crd_fn_calls),*], &stored_apiversion.to_string()) } } } @@ -292,22 +344,41 @@ impl VersionedStruct { /// Generates the inner `crd()` functions calls which get used in the /// `merge_crds` function. - fn generate_kubernetes_crd_fn_call(&self, version: &ContainerVersion) -> Option { - if self - .options - .kubernetes_options - .as_ref() - .is_some_and(|o| !o.skip_merged_crd) - { - let struct_ident = &self.idents.kubernetes; - let version_ident = &version.ident; - - let path: syn::Path = parse_quote!(#version_ident::#struct_ident); - return Some(quote! { - <#path as ::kube::CustomResourceExt>::crd(), + fn generate_kubernetes_crd_fn_call(&self, version: &ContainerVersion) -> TokenStream { + let struct_ident = &self.idents.kubernetes; + let version_ident = &version.ident; + let path: syn::Path = parse_quote!(#version_ident::#struct_ident); + + quote! { + <#path as ::kube::CustomResourceExt>::crd() + } + } + + fn generate_kubernetes_version_enum(&self, enum_variants: Vec<(Ident, String)>) -> TokenStream { + let mut enum_variant_matches = TokenStream::new(); + let mut enum_variant_idents = TokenStream::new(); + + for (enum_variant_ident, enum_variant_display) in enum_variants { + enum_variant_idents.extend(quote! {#enum_variant_ident,}); + enum_variant_matches.extend(quote! { + Version::#enum_variant_ident => f.write_str(#enum_variant_display), }); } - None + quote! { + #[automatically_derived] + pub enum Version { + #enum_variant_idents + } + + #[automatically_derived] + impl ::std::fmt::Display for Version { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::result::Result<(), ::std::fmt::Error> { + match self { + #enum_variant_matches + } + } + } + } } } diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index 9aa688f3..814b1c62 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -462,6 +462,15 @@ println!("{}", serde_yaml::to_string(&merged_crd).unwrap()); ``` "# )] +/// Currently, the following arguments are supported: +/// +/// - `group`: Sets the CRD group, usually the domain of the company. +/// - `kind`: Allows overwriting the kind field of the CRD. This defaults +/// to the struct name (without the 'Spec' suffix). +/// - `singular`: Sets the singular name. +/// - `plural`: Sets the plural name. +/// - `namespaced`: Specifies that this is a namespaced resource rather than +/// a cluster scoped. #[proc_macro_attribute] pub fn versioned(attrs: TokenStream, input: TokenStream) -> TokenStream { let attrs = match NestedMeta::parse_meta_list(attrs.into()) { diff --git a/crates/stackable-versioned-macros/tests/k8s/pass/crd.rs b/crates/stackable-versioned-macros/tests/k8s/pass/crd.rs index 0defd825..73f690dc 100644 --- a/crates/stackable-versioned-macros/tests/k8s/pass/crd.rs +++ b/crates/stackable-versioned-macros/tests/k8s/pass/crd.rs @@ -9,7 +9,12 @@ fn main() { version(name = "v1alpha1"), version(name = "v1beta1"), version(name = "v1"), - k8s(group = "stackable.tech") + k8s( + group = "stackable.tech", + singular = "foo", + plural = "foos", + namespaced, + ) )] #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct FooSpec { @@ -21,6 +26,6 @@ fn main() { baz: bool, } - let merged_crd = Foo::merged_crd("v1").unwrap(); + let merged_crd = Foo::merged_crd(Version::V1).unwrap(); println!("{}", serde_yaml::to_string(&merged_crd).unwrap()); } diff --git a/crates/stackable-versioned/CHANGELOG.md b/crates/stackable-versioned/CHANGELOG.md index 053abf13..6b6f561e 100644 --- a/crates/stackable-versioned/CHANGELOG.md +++ b/crates/stackable-versioned/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Add forwarding of `singular`, `plural`, and `namespaced` arguments in `k8s()` + ([#873]). +- Generate a `Version` enum containing all declared versions as variants + ([#872]). + +### Changed + +- The `merged_crd` associated function now takes `Version` instead of `&str` as + input ([#872]). + +[#872]: https://github.com/stackabletech/operator-rs/pull/872 +[#873]: https://github.com/stackabletech/operator-rs/pull/873 + ## [0.2.0] - 2024-09-19 ### Added