diff --git a/kube-core/src/lib.rs b/kube-core/src/lib.rs index 20a4dbff6..a0371c169 100644 --- a/kube-core/src/lib.rs +++ b/kube-core/src/lib.rs @@ -43,8 +43,8 @@ pub use request::Request; mod resource; pub use resource::{ - ClusterResourceScope, DynamicResourceScope, NamespaceResourceScope, Resource, ResourceExt, ResourceScope, - SubResourceScope, + api_version_from_group_version, ClusterResourceScope, DynamicResourceScope, NamespaceResourceScope, + Resource, ResourceExt, ResourceScope, SubResourceScope, }; pub mod response; diff --git a/kube-core/src/resource.rs b/kube-core/src/resource.rs index 6ae3500ed..4b159b3bc 100644 --- a/kube-core/src/resource.rs +++ b/kube-core/src/resource.rs @@ -47,14 +47,7 @@ pub trait Resource { fn version(dt: &Self::DynamicType) -> Cow<'_, str>; /// Returns apiVersion of this object fn api_version(dt: &Self::DynamicType) -> Cow<'_, str> { - let group = Self::group(dt); - if group.is_empty() { - return Self::version(dt); - } - let mut group = group.into_owned(); - group.push('/'); - group.push_str(&Self::version(dt)); - group.into() + api_version_from_group_version(Self::group(dt), Self::version(dt)) } /// Returns the plural name of the kind /// @@ -115,6 +108,18 @@ pub trait Resource { } } +/// Helper function that creates the `apiVersion` field from the group and version strings. +pub fn api_version_from_group_version<'a>(group: Cow<'a, str>, version: Cow<'a, str>) -> Cow<'a, str> { + if group.is_empty() { + return version; + } + + let mut output = group; + output.to_mut().push('/'); + output.to_mut().push_str(&version); + output +} + /// Implement accessor trait for any ObjectMeta-using Kubernetes Resource impl Resource for K where diff --git a/kube-runtime/src/reflector/mod.rs b/kube-runtime/src/reflector/mod.rs index d0a724b53..ca457f3d2 100644 --- a/kube-runtime/src/reflector/mod.rs +++ b/kube-runtime/src/reflector/mod.rs @@ -3,10 +3,9 @@ mod object_ref; pub mod store; -pub use self::object_ref::{Extra as ObjectRefExtra, ObjectRef}; +pub use self::object_ref::{Extra as ObjectRefExtra, ObjectRef, ToObjectRef}; use crate::watcher; use futures::{Stream, TryStreamExt}; -use kube_client::Resource; use std::hash::Hash; pub use store::{store, Store}; @@ -91,7 +90,7 @@ pub use store::{store, Store}; /// Additionally, only `labels`, `annotations` and `managed_fields` are safe to drop from `ObjectMeta`. pub fn reflector(mut writer: store::Writer, stream: W) -> impl Stream where - K: Resource + Clone, + K: ToObjectRef + Clone, K::DynamicType: Eq + Hash + Clone, W: Stream>>, { diff --git a/kube-runtime/src/reflector/object_ref.rs b/kube-runtime/src/reflector/object_ref.rs index cc2049ca1..9132081b9 100644 --- a/kube-runtime/src/reflector/object_ref.rs +++ b/kube-runtime/src/reflector/object_ref.rs @@ -2,14 +2,102 @@ use derivative::Derivative; use k8s_openapi::{api::core::v1::ObjectReference, apimachinery::pkg::apis::meta::v1::OwnerReference}; use kube_client::{ api::{DynamicObject, Resource}, - core::ObjectMeta, - ResourceExt, + core::{api_version_from_group_version, ObjectMeta}, }; use std::{ + borrow::Cow, fmt::{Debug, Display}, hash::Hash, }; +/// An object that can be indexed by a [reflector store](super::Store). +/// +/// This trait is blanket-implemented for all [`Resource`] objects. +#[allow(clippy::module_name_repetitions)] +pub trait ToObjectRef { + /// Type information for types that do not know their resource information at compile time. + /// This is equivalent to [`Resource::DynamicType`]. + type DynamicType; + + /// The [kind](Resource::kind) for this object. + fn kind(dyntype: &Self::DynamicType) -> Cow<'_, str>; + + /// The [group](Resource::group) for this object. + fn group(dyntype: &Self::DynamicType) -> Cow<'_, str>; + + /// The [version](Resource::version) for this object. + fn version(dyntype: &Self::DynamicType) -> Cow<'_, str>; + + /// The [apiVersion](Resource::_version) for this object. + fn api_version(dyntype: &Self::DynamicType) -> Cow<'_, str> { + api_version_from_group_version(Self::group(dyntype), Self::version(dyntype)) + } + + /// The [plural](Resource::plural) for this object. + fn plural(dyntype: &Self::DynamicType) -> Cow<'_, str>; + + /// The [name](ObjectMeta#structfield.name) of the object. + fn name(&self) -> Option>; + + /// The [namespace](ObjectMeta#structfield.namespace) of the object. + fn namespace(&self) -> Option>; + + /// The [resource version](ObjectMeta#structfield.resource_version) of the object. + fn resource_version(&self) -> Option>; + + /// The [UID](ObjectMeta#structfield.uid) of the object. + fn uid(&self) -> Option>; + + /// Constructs an [`ObjectRef`] for this object. + fn to_object_ref(&self, dyntype: Self::DynamicType) -> ObjectRef { + ObjectRef { + dyntype, + name: self.name().expect(".metadata.name missing").into_owned(), + namespace: self.namespace().map(Cow::into_owned), + extra: Extra { + resource_version: self.resource_version().map(Cow::into_owned), + uid: self.uid().map(Cow::into_owned), + }, + } + } +} + +impl ToObjectRef for K { + type DynamicType = K::DynamicType; + + fn kind(dyntype: &Self::DynamicType) -> Cow<'_, str> { + K::kind(dyntype) + } + + fn version(dyntype: &Self::DynamicType) -> Cow<'_, str> { + K::version(dyntype) + } + + fn group(dyntype: &Self::DynamicType) -> Cow<'_, str> { + K::group(dyntype) + } + + fn plural(dyntype: &Self::DynamicType) -> Cow<'_, str> { + K::plural(dyntype) + } + + fn name(&self) -> Option> { + self.meta().name.as_deref().map(Cow::Borrowed) + } + + fn namespace(&self) -> Option> { + self.meta().namespace.as_deref().map(Cow::Borrowed) + } + + fn resource_version(&self) -> Option> { + self.meta().resource_version.as_deref().map(Cow::Borrowed) + } + + fn uid(&self) -> Option> { + self.meta().uid.as_deref().map(Cow::Borrowed) + } +} + #[derive(Derivative)] #[derivative( Debug(bound = "K::DynamicType: Debug"), @@ -33,7 +121,7 @@ use std::{ /// ); /// ``` #[non_exhaustive] -pub struct ObjectRef { +pub struct ObjectRef { pub dyntype: K::DynamicType, /// The name of the object pub name: String, @@ -69,7 +157,7 @@ pub struct Extra { pub uid: Option, } -impl ObjectRef +impl ObjectRef where K::DynamicType: Default, { @@ -81,13 +169,13 @@ where #[must_use] pub fn from_obj(obj: &K) -> Self where - K: Resource, + K: ToObjectRef, { - Self::from_obj_with(obj, Default::default()) + obj.to_object_ref(Default::default()) } } -impl ObjectRef { +impl ObjectRef { #[must_use] pub fn new_with(name: &str, dyntype: K::DynamicType) -> Self { Self { @@ -108,15 +196,9 @@ impl ObjectRef { #[must_use] pub fn from_obj_with(obj: &K, dyntype: K::DynamicType) -> Self where - K: Resource, + K: ToObjectRef, { - let meta = obj.meta(); - Self { - dyntype, - name: obj.name_unchecked(), - namespace: meta.namespace.clone(), - extra: Extra::from_obj_meta(meta), - } + obj.to_object_ref(dyntype) } /// Create an `ObjectRef` from an `OwnerReference` @@ -148,7 +230,7 @@ impl ObjectRef { /// Note that no checking is done on whether this conversion makes sense. For example, every `Service` /// has a corresponding `Endpoints`, but it wouldn't make sense to convert a `Pod` into a `Deployment`. #[must_use] - pub fn into_kind_unchecked(self, dt2: K2::DynamicType) -> ObjectRef { + pub fn into_kind_unchecked(self, dt2: K2::DynamicType) -> ObjectRef { ObjectRef { dyntype: dt2, name: self.name, @@ -159,7 +241,13 @@ impl ObjectRef { pub fn erase(self) -> ObjectRef { ObjectRef { - dyntype: kube_client::api::ApiResource::erase::(&self.dyntype), + dyntype: kube_client::api::ApiResource { + group: K::group(&self.dyntype).to_string(), + version: K::version(&self.dyntype).to_string(), + api_version: K::api_version(&self.dyntype).to_string(), + kind: K::kind(&self.dyntype).to_string(), + plural: K::plural(&self.dyntype).to_string(), + }, name: self.name, namespace: self.namespace, extra: self.extra, @@ -167,7 +255,7 @@ impl ObjectRef { } } -impl From> for ObjectReference { +impl From> for ObjectReference { fn from(val: ObjectRef) -> Self { let ObjectRef { dyntype: dt, @@ -190,7 +278,7 @@ impl From> for ObjectReference { } } -impl Display for ObjectRef { +impl Display for ObjectRef { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, @@ -207,15 +295,6 @@ impl Display for ObjectRef { } } -impl Extra { - fn from_obj_meta(obj_meta: &ObjectMeta) -> Self { - Self { - resource_version: obj_meta.resource_version.clone(), - uid: obj_meta.uid.clone(), - } - } -} - #[cfg(test)] mod tests { use std::{ diff --git a/kube-runtime/src/reflector/store.rs b/kube-runtime/src/reflector/store.rs index 9085ab69b..acebb8360 100644 --- a/kube-runtime/src/reflector/store.rs +++ b/kube-runtime/src/reflector/store.rs @@ -1,11 +1,10 @@ -use super::ObjectRef; +use super::{ObjectRef, ToObjectRef}; use crate::{ utils::delayed_init::{self, DelayedInit}, watcher, }; use ahash::AHashMap; use derivative::Derivative; -use kube_client::Resource; use parking_lot::RwLock; use std::{fmt::Debug, hash::Hash, sync::Arc}; use thiserror::Error; @@ -17,7 +16,7 @@ type Cache = Arc, Arc>>>; /// This is exclusive since it's not safe to share a single `Store` between multiple reflectors. /// In particular, `Restarted` events will clobber the state of other connected reflectors. #[derive(Debug)] -pub struct Writer +pub struct Writer where K::DynamicType: Eq + Hash, { @@ -27,7 +26,7 @@ where ready_rx: Arc>, } -impl Writer +impl Writer where K::DynamicType: Eq + Hash + Clone, { @@ -61,23 +60,18 @@ where pub fn apply_watcher_event(&mut self, event: &watcher::Event) { match event { watcher::Event::Applied(obj) => { - let key = ObjectRef::from_obj_with(obj, self.dyntype.clone()); + let key = obj.to_object_ref(self.dyntype.clone()); let obj = Arc::new(obj.clone()); self.store.write().insert(key, obj); } watcher::Event::Deleted(obj) => { - let key = ObjectRef::from_obj_with(obj, self.dyntype.clone()); + let key = obj.to_object_ref(self.dyntype.clone()); self.store.write().remove(&key); } watcher::Event::Restarted(new_objs) => { let new_objs = new_objs .iter() - .map(|obj| { - ( - ObjectRef::from_obj_with(obj, self.dyntype.clone()), - Arc::new(obj.clone()), - ) - }) + .map(|obj| (obj.to_object_ref(self.dyntype.clone()), Arc::new(obj.clone()))) .collect::>(); *self.store.write() = new_objs; } @@ -91,7 +85,7 @@ where } impl Default for Writer where - K: Resource + Clone + 'static, + K: ToObjectRef + Clone + 'static, K::DynamicType: Default + Eq + Hash + Clone, { fn default() -> Self { @@ -107,7 +101,7 @@ where /// use `Writer::as_reader()` instead. #[derive(Derivative)] #[derivative(Debug(bound = "K: Debug, K::DynamicType: Debug"), Clone)] -pub struct Store +pub struct Store where K::DynamicType: Hash + Eq, { @@ -119,7 +113,7 @@ where #[error("writer was dropped before store became ready")] pub struct WriterDropped(delayed_init::InitDropped); -impl Store +impl Store where K::DynamicType: Eq + Hash + Clone, { @@ -201,7 +195,7 @@ where #[must_use] pub fn store() -> (Store, Writer) where - K: Resource + Clone + 'static, + K: ToObjectRef + Clone + 'static, K::DynamicType: Eq + Hash + Clone + Default, { let w = Writer::::default();