Skip to content

Commit

Permalink
add plural to Resource and GroupVersionKind
Browse files Browse the repository at this point in the history
replaces #468.
closes #467

This changes kube-derive to no longer implement `k8s_openapi` traits.
We now instead implement `kube::Resource` instead so we can control the
plural.

Because the trait now picks up on an override when it is generating the
path_url, it is now correct in the Api urls.
  • Loading branch information
clux committed Mar 28, 2021
1 parent 40c8611 commit 695db0a
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 45 deletions.
33 changes: 22 additions & 11 deletions examples/crd_derive.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use k8s_openapi::{apimachinery::pkg::apis::meta::v1::Condition, Resource};
use kube::CustomResource;
use k8s_openapi::apimachinery::pkg::apis::meta::v1::Condition;
use kube::{CustomResource, Resource};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

Expand All @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize};
group = "clux.dev",
version = "v1",
kind = "Foo",
plural = "fooz",
struct = "FooCrd",
namespaced,
status = "FooStatus",
Expand All @@ -35,7 +36,7 @@ pub struct FooStatus {
}

fn main() {
println!("Kind {}", FooCrd::KIND);
println!("Kind {}", FooCrd::kind(&()));
let mut foo = FooCrd::new("hi", MyFoo {
name: "hi".into(),
info: None,
Expand Down Expand Up @@ -84,13 +85,13 @@ fn verify_crd() {
"apiVersion": "apiextensions.k8s.io/v1",
"kind": "CustomResourceDefinition",
"metadata": {
"name": "foos.clux.dev"
"name": "fooz.clux.dev"
},
"spec": {
"group": "clux.dev",
"names": {
"kind": "Foo",
"plural": "foos",
"plural": "fooz",
"shortNames": ["f"],
"singular": "foo"
},
Expand Down Expand Up @@ -183,20 +184,29 @@ fn verify_crd() {
}
});
let crd = serde_json::to_value(FooCrd::crd()).unwrap();
println!("got crd: {}", serde_yaml::to_string(&FooCrd::crd()).unwrap());
assert_eq!(crd, output);
}

#[test]
fn verify_resource() {
use static_assertions::{assert_impl_all, assert_impl_one};
assert_eq!(FooCrd::KIND, "Foo");
assert_eq!(FooCrd::GROUP, "clux.dev");
assert_eq!(FooCrd::VERSION, "v1");
assert_eq!(FooCrd::API_VERSION, "clux.dev/v1");
assert_impl_all!(FooCrd: k8s_openapi::Resource, k8s_openapi::Metadata, Default);
assert_eq!(FooCrd::kind(&()), "Foo");
assert_eq!(FooCrd::group(&()), "clux.dev");
assert_eq!(FooCrd::version(&()), "v1");
assert_eq!(FooCrd::api_version(&()), "clux.dev/v1");
assert_impl_all!(FooCrd: Resource, Default);
assert_impl_one!(MyFoo: JsonSchema);
}

#[tokio::test]
async fn verify_api_gen() {
use kube::{Api, Client};
let client = Client::try_default().await.unwrap();
let api: Api<FooCrd> = Api::namespaced(client, "myns");
assert_eq!(api.resource_url(), "/apis/clux.dev/v1/namespaces/myns/fooz");
}

#[test]
fn verify_default() {
let fdef = FooCrd::default();
Expand All @@ -206,6 +216,7 @@ apiVersion: clux.dev/v1
kind: Foo
metadata: {}
spec:
name: """#;
name: ""
"#;
assert_eq!(exp, ser);
}
66 changes: 43 additions & 23 deletions kube-derive/src/custom_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStrea
impl #rootident {
pub fn new(name: &str, spec: #ident) -> Self {
Self {
api_version: <#rootident as k8s_openapi::Resource>::API_VERSION.to_string(),
kind: <#rootident as k8s_openapi::Resource>::KIND.to_string(),
api_version: <#rootident as kube::Resource>::api_version(&()).to_string(),
kind: <#rootident as kube::Resource>::kind(&()).to_string(),
metadata: k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta {
name: Some(name.to_string()),
..Default::default()
Expand All @@ -177,37 +177,58 @@ pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStrea
}
};

// 2. Implement Resource trait for k8s_openapi
// 2. Implement Resource trait
let api_ver = format!("{}/{}", group, version);
let impl_resource = quote! {
impl k8s_openapi::Resource for #rootident {
const API_VERSION: &'static str = #api_ver;
const GROUP: &'static str = #group;
const KIND: &'static str = #kind;
const VERSION: &'static str = #version;
}
};
impl kube::Resource for #rootident {
type DynamicType = ();

fn kind<'a>(_: &'a ()) -> std::borrow::Cow<'a, str> {
#kind.into()
}

fn plural<'a>(_: &'a ()) -> std::borrow::Cow<'a, str> {
#plural.into()
}

fn group<'a>(_: &'a ()) -> std::borrow::Cow<'a, str> {
#group.into()
}

fn version<'a>(_: &'a ()) -> std::borrow::Cow<'a, str> {
#version.into()
}

fn api_version<'a>(_: &'a ()) -> std::borrow::Cow<'a, str> {
#api_ver.into()
}

// 3. Implement Metadata trait for k8s_openapi
let impl_metadata = quote! {
impl k8s_openapi::Metadata for #rootident {
type Ty = k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
fn metadata(&self) -> &Self::Ty {
fn meta(&self) -> &kube::api::ObjectMeta {
&self.metadata
}
fn metadata_mut(&mut self) -> &mut Self::Ty {
&mut self.metadata

fn name(&self) -> String {
self.meta().name.clone().expect("kind has metadata.name")
}

fn resource_ver(&self) -> Option<String> {
self.meta().resource_version.clone()
}

fn namespace(&self) -> Option<String> {
self.meta().namespace.clone()
}
}
};
// 4. Implement Default if requested

// 3. Implement Default if requested
let impl_default = if has_default {
quote! {
impl Default for #rootident {
fn default() -> Self {
Self {
api_version: <#rootident as k8s_openapi::Resource>::API_VERSION.to_string(),
kind: <#rootident as k8s_openapi::Resource>::KIND.to_string(),
api_version: <#rootident as kube::Resource>::api_version(&()).to_string(),
kind: <#rootident as kube::Resource>::kind(&()).to_string(),
metadata: k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta::default(),
spec: Default::default(),
#statusdef
Expand All @@ -219,7 +240,7 @@ pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStrea
quote! {}
};

// 5. Implement CustomResource
// 4. Implement CustomResource
let name = singular.unwrap_or_else(|| kind.to_ascii_lowercase());
let plural = plural.unwrap_or_else(|| to_plural(&name));
let scope = if namespaced { "Namespaced" } else { "Cluster" };
Expand Down Expand Up @@ -315,7 +336,7 @@ pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStrea
}
};

// TODO: should ::crd be from a trait?
// Implement the ::crd method (fine to not have in a trait as its a generated type)
let impl_crd = quote! {
impl #rootident {
pub fn crd() -> #apiext::CustomResourceDefinition {
Expand Down Expand Up @@ -350,7 +371,6 @@ pub(crate) fn derive(input: proc_macro2::TokenStream) -> proc_macro2::TokenStrea
quote! {
#root_obj
#impl_resource
#impl_metadata
#impl_default
#impl_crd
}
Expand Down
10 changes: 10 additions & 0 deletions kube/src/api/dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub struct GroupVersionKind {
kind: String,
/// Concatenation of group and version
api_version: String,
/// Optional plural/resource
plural: Option<String>,
}

impl GroupVersionKind {
Expand Down Expand Up @@ -57,6 +59,7 @@ impl GroupVersionKind {
version,
kind,
api_version,
plural: None,
}
}

Expand Down Expand Up @@ -87,8 +90,15 @@ impl GroupVersionKind {
version,
kind,
api_version,
plural: None,
})
}

/// Set an explicit plural/resource value to avoid relying on inferred pluralisation.
pub fn plural(mut self, plural: &str) -> Self {
self.plural = Some(plural.to_string());
self
}
}

/// A dynamic representation of a kubernetes resource
Expand Down
34 changes: 23 additions & 11 deletions kube/src/api/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,45 @@ pub trait Resource {
type DynamicType: Send + Sync + 'static;

/// Returns kind of this object
fn kind(f: &Self::DynamicType) -> Cow<'_, str>;
fn kind(dt: &Self::DynamicType) -> Cow<'_, str>;
/// Returns group of this object
fn group(f: &Self::DynamicType) -> Cow<'_, str>;
fn group(dt: &Self::DynamicType) -> Cow<'_, str>;
/// Returns version of this object
fn version(f: &Self::DynamicType) -> Cow<'_, str>;
fn version(dt: &Self::DynamicType) -> Cow<'_, str>;
/// Returns apiVersion of this object
fn api_version(f: &Self::DynamicType) -> Cow<'_, str> {
let group = Self::group(f);
fn api_version(dt: &Self::DynamicType) -> Cow<'_, str> {
let group = Self::group(dt);
if group.is_empty() {
return Self::version(f);
return Self::version(dt);
}
let mut group = group.into_owned();
group.push('/');
group.push_str(&Self::version(f));
group.push_str(&Self::version(dt));
group.into()
}
/// Returns the plural name of the kind
///
/// This is known as the resource in apimachinery, we rename it for disambiguation.
/// By default, we infer this name through pluralization.
///
/// The pluralization process is not recommended to be relied upon, and is only used for
/// `k8s_openapi` types, where we maintain a list of special pluralisations for compatibility.
///
/// Thus when used with `DynamicObject` or `kube-derive`, we override this with correct values.
fn plural<'a>(dt: &'a Self::DynamicType) -> Cow<'a, str> {
to_plural(&Self::kind(dt).to_ascii_lowercase()).into()
}

/// Creates a url path for http requests for this resource
fn url_path(t: &Self::DynamicType, namespace: Option<&str>) -> String {
fn url_path(dt: &Self::DynamicType, namespace: Option<&str>) -> String {
let n = if let Some(ns) = namespace {
format!("namespaces/{}/", ns)
} else {
"".into()
};
let group = Self::group(t);
let api_version = Self::api_version(t);
let plural = to_plural(&Self::kind(t).to_ascii_lowercase());
let group = Self::group(dt);
let api_version = Self::api_version(dt);
let plural = Self::plural(dt);
format!(
"/{group}/{api_version}/{namespaces}{plural}",
group = if group.is_empty() { "api" } else { "apis" },
Expand Down
5 changes: 5 additions & 0 deletions kube/src/api/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ impl<K: Resource> Api<K> {
pub fn into_client(self) -> Client {
self.into()
}

/// Return a reference to the current resource url path
pub fn resource_url(&self) -> &str {
&self.request.url_path
}
}

/// PUSH/PUT/POST/GET abstractions
Expand Down

0 comments on commit 695db0a

Please sign in to comment.