Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(stackable-versioned): Use enum for merged_crd instead of str #872

Merged
merged 2 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions crates/stackable-versioned-macros/src/codegen/common/container.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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
Expand Down
146 changes: 102 additions & 44 deletions crates/stackable-versioned-macros/src/codegen/vstruct/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ use syn::{parse_quote, DataStruct, Error, Ident};
use crate::{
attrs::common::ContainerAttributes,
codegen::{
common::{Container, ContainerInput, ContainerVersion, Item, VersionedContainer},
common::{
Container, ContainerInput, ContainerVersion, Item, KubernetesOptions, 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
Expand Down Expand Up @@ -85,24 +90,30 @@ impl Container<DataStruct, VersionedField> 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();
Techassi marked this conversation as resolved.
Show resolved Hide resolved
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
}
}

Expand All @@ -112,7 +123,7 @@ impl VersionedStruct {
&self,
version: &ContainerVersion,
next_version: Option<&ContainerVersion>,
) -> TokenStream {
) -> GenerateVersionReturn {
let mut token_stream = TokenStream::new();

let original_attributes = &self.original_attributes;
Expand All @@ -137,7 +148,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, options);

// 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! {
Expand All @@ -160,7 +191,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.
Expand Down Expand Up @@ -251,63 +282,90 @@ impl VersionedStruct {
impl VersionedStruct {
/// Generates the `kube::CustomResource` derive with the appropriate macro
/// attributes.
fn generate_kubernetes_cr_derive(&self, version: &ContainerVersion) -> Option<TokenStream> {
if let Some(kubernetes_options) = &self.options.kubernetes_options {
let group = &kubernetes_options.group;
let version = version.inner.to_string();
let kind = kubernetes_options
.kind
.as_ref()
.map_or(self.idents.kubernetes.to_string(), |kind| kind.clone());
fn generate_kubernetes_cr_derive(
&self,
version: &ContainerVersion,
options: &KubernetesOptions,
) -> TokenStream {
let group = &options.group;
let version = version.inner.to_string();
let kind = options
.kind
.as_ref()
.map_or(self.idents.kubernetes.to_string(), |kind| kind.clone());

return Some(quote! {
#[derive(::kube::CustomResource)]
#[kube(group = #group, version = #version, kind = #kind)]
});
quote! {
#[derive(::kube::CustomResource)]
#[kube(group = #group, version = #version, kind = #kind)]
}

None
}

/// 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<TokenStream>,
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())
}
}
}
}

/// Generates the inner `crd()` functions calls which get used in the
/// `merge_crds` function.
fn generate_kubernetes_crd_fn_call(&self, version: &ContainerVersion) -> Option<TokenStream> {
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;
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);

let path: syn::Path = parse_quote!(#version_ident::#struct_ident);
return Some(quote! {
<#path as ::kube::CustomResourceExt>::crd(),
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
}
}
}
}
}
}
2 changes: 1 addition & 1 deletion crates/stackable-versioned-macros/tests/k8s/pass/crd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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());
}
12 changes: 12 additions & 0 deletions crates/stackable-versioned/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added

- 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

## [0.2.0] - 2024-09-19

### Added
Expand Down