Skip to content

Commit

Permalink
add support for CRD annotations and labels in kube-derive (#1631)
Browse files Browse the repository at this point in the history
add support for crd annotations and labels

Signed-off-by: Andreas Metsälä <andreas.metsala@gmail.com>
  • Loading branch information
verokarhu authored Nov 13, 2024
1 parent b0538cb commit 8b5230f
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 4 deletions.
72 changes: 69 additions & 3 deletions kube-derive/src/custom_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use darling::{FromDeriveInput, FromMeta};
use proc_macro2::{Ident, Literal, Span, TokenStream};
use quote::ToTokens;
use quote::{ToTokens, TokenStreamExt};
use syn::{parse_quote, Data, DeriveInput, Path, Visibility};

/// Values we can parse from #[kube(attrs)]
Expand Down Expand Up @@ -37,6 +37,44 @@ struct KubeAttrs {
scale: Option<String>,
#[darling(default)]
crates: Crates,
#[darling(multiple, rename = "annotation")]
annotations: Vec<KVTuple>,
#[darling(multiple, rename = "label")]
labels: Vec<KVTuple>,
}

#[derive(Debug)]
struct KVTuple(String, String);

impl FromMeta for KVTuple {
fn from_list(items: &[darling::ast::NestedMeta]) -> darling::Result<Self> {
if items.len() == 2 {
if let (
darling::ast::NestedMeta::Lit(syn::Lit::Str(key)),
darling::ast::NestedMeta::Lit(syn::Lit::Str(value)),
) = (&items[0], &items[1])
{
return Ok(KVTuple(key.value(), value.value()));
}
}

Err(darling::Error::unsupported_format(
"expected `\"key\", \"value\"` format",
))
}
}

impl From<(&'static str, &'static str)> for KVTuple {
fn from((key, value): (&'static str, &'static str)) -> Self {
Self(key.to_string(), value.to_string())
}
}

impl ToTokens for KVTuple {
fn to_tokens(&self, tokens: &mut TokenStream) {
let (k, v) = (&self.0, &self.1);
tokens.append_all(quote! { (#k, #v) });
}
}

#[derive(Debug, FromMeta)]
Expand Down Expand Up @@ -172,6 +210,8 @@ pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStrea
serde_json,
std,
},
annotations,
labels,
} = kube_attrs;

let struct_name = kind_struct.unwrap_or_else(|| kind.clone());
Expand Down Expand Up @@ -247,6 +287,18 @@ pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStrea
derive_paths.push(syn::parse_quote! { #schemars::JsonSchema });
}

let meta_annotations = if !annotations.is_empty() {
quote! { Some(std::collections::BTreeMap::from([#((#annotations.0.to_string(), #annotations.1.to_string()),)*])) }
} else {
quote! { None }
};

let meta_labels = if !labels.is_empty() {
quote! { Some(std::collections::BTreeMap::from([#((#labels.0.to_string(), #labels.1.to_string()),)*])) }
} else {
quote! { None }
};

let docstr =
doc.unwrap_or_else(|| format!(" Auto-generated derived type for {ident} via `CustomResource`"));
let quoted_serde = Literal::string(&serde.to_token_stream().to_string());
Expand All @@ -268,6 +320,8 @@ pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStrea
pub fn new(name: &str, spec: #ident) -> Self {
Self {
metadata: #k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta {
annotations: #meta_annotations,
labels: #meta_labels,
name: Some(name.to_string()),
..Default::default()
},
Expand Down Expand Up @@ -382,7 +436,17 @@ pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStrea
let categories_json = serde_json::to_string(&categories).unwrap();
let short_json = serde_json::to_string(&shortnames).unwrap();
let crd_meta_name = format!("{plural}.{group}");
let crd_meta = quote! { { "name": #crd_meta_name } };

let mut crd_meta = TokenStream::new();
crd_meta.extend(quote! { "name": #crd_meta_name });

if !annotations.is_empty() {
crd_meta.extend(quote! { , "annotations": #meta_annotations });
}

if !labels.is_empty() {
crd_meta.extend(quote! { , "labels": #meta_labels });
}

let schemagen = if schema_mode.use_in_crd() {
quote! {
Expand Down Expand Up @@ -426,7 +490,9 @@ pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStrea
#schemagen

let jsondata = #serde_json::json!({
"metadata": #crd_meta,
"metadata": {
#crd_meta
},
"spec": {
"group": #group,
"scope": #scope,
Expand Down
6 changes: 6 additions & 0 deletions kube-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ mod resource;
/// Sets the description of the schema in the generated CRD. If not specified
/// `Auto-generated derived type for {customResourceName} via CustomResource` will be used instead.
///
/// ## `#[kube(annotation("ANNOTATION_KEY", "ANNOTATION_VALUE"))]`
/// Add a single annotation to the generated CRD.
///
/// ## `#[kube(label("LABEL_KEY", "LABEL_VALUE"))]`
/// Add a single label to the generated CRD.
///
/// ## Example with all properties
///
/// ```rust
Expand Down
22 changes: 21 additions & 1 deletion kube-derive/tests/crd_schema_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ use std::collections::{HashMap, HashSet};
shortname = "fo",
shortname = "f",
selectable = ".spec.nonNullable",
selectable = ".spec.nullable"
selectable = ".spec.nullable",
annotation("clux.dev", "cluxingv1"),
annotation("clux.dev/firewall", "enabled"),
label("clux.dev", "cluxingv1"),
label("clux.dev/persistence", "disabled")
)]
#[serde(rename_all = "camelCase")]
struct FooSpec {
Expand Down Expand Up @@ -150,6 +154,14 @@ fn test_serialized_matches_expected() {
"apiVersion": "clux.dev/v1",
"kind": "Foo",
"metadata": {
"annotations": {
"clux.dev": "cluxingv1",
"clux.dev/firewall": "enabled",
},
"labels": {
"clux.dev": "cluxingv1",
"clux.dev/persistence": "disabled",
},
"name": "bar",
},
"spec": {
Expand Down Expand Up @@ -182,6 +194,14 @@ fn test_crd_schema_matches_expected() {
"apiVersion": "apiextensions.k8s.io/v1",
"kind": "CustomResourceDefinition",
"metadata": {
"annotations": {
"clux.dev": "cluxingv1",
"clux.dev/firewall": "enabled",
},
"labels": {
"clux.dev": "cluxingv1",
"clux.dev/persistence": "disabled",
},
"name": "foos.clux.dev"
},
"spec": {
Expand Down

0 comments on commit 8b5230f

Please sign in to comment.