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

add support for CRD annotations and labels in kube-derive #1631

Merged
merged 1 commit into from
Nov 13, 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
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 @@
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(

Check warning on line 61 in kube-derive/src/custom_resource.rs

View check run for this annotation

Codecov / codecov/patch

kube-derive/src/custom_resource.rs#L61

Added line #L61 was not covered by tests
"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())

Check warning on line 69 in kube-derive/src/custom_resource.rs

View check run for this annotation

Codecov / codecov/patch

kube-derive/src/custom_resource.rs#L68-L69

Added lines #L68 - L69 were not covered by tests
}
}

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 @@
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 @@
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()),)*])) }
clux marked this conversation as resolved.
Show resolved Hide resolved
} 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 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 @@
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 @@
#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