Skip to main content

kube_core/
resource.rs

1pub use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
2use k8s_openapi::{
3    api::core::v1::ObjectReference,
4    apimachinery::pkg::apis::meta::v1::{ManagedFieldsEntry, OwnerReference, Time},
5};
6
7use std::{borrow::Cow, collections::BTreeMap};
8
9pub use k8s_openapi::{ClusterResourceScope, NamespaceResourceScope, ResourceScope, SubResourceScope};
10
11/// Indicates that a [`Resource`] is of an indeterminate dynamic scope.
12pub struct DynamicResourceScope {}
13impl ResourceScope for DynamicResourceScope {}
14
15/// An accessor trait for a kubernetes Resource.
16///
17/// This is for a subset of Kubernetes type that do not end in `List`.
18/// These types, using [`ObjectMeta`], SHOULD all have required properties:
19/// - `.metadata`
20/// - `.metadata.name`
21///
22/// And these optional properties:
23/// - `.metadata.namespace`
24/// - `.metadata.resource_version`
25///
26/// This avoids a bunch of the unnecessary unwrap mechanics for apps.
27pub trait Resource {
28    /// Type information for types that do not know their resource information at compile time.
29    ///
30    /// Types that know their metadata at compile time should select `DynamicType = ()`.
31    /// Types that require some information at runtime should select `DynamicType`
32    /// as type of this information.
33    ///
34    /// See [`DynamicObject`](crate::dynamic::DynamicObject) for a valid implementation of non-k8s-openapi resources.
35    type DynamicType: Send + Sync + 'static;
36    /// Type information for the api scope of the resource when known at compile time
37    ///
38    /// Types from k8s_openapi come with an explicit k8s_openapi::ResourceScope
39    /// Dynamic types should select `Scope = DynamicResourceScope`
40    type Scope;
41
42    /// Returns kind of this object
43    fn kind(dt: &Self::DynamicType) -> Cow<'_, str>;
44    /// Returns group of this object
45    fn group(dt: &Self::DynamicType) -> Cow<'_, str>;
46    /// Returns version of this object
47    fn version(dt: &Self::DynamicType) -> Cow<'_, str>;
48    /// Returns apiVersion of this object
49    fn api_version(dt: &Self::DynamicType) -> Cow<'_, str> {
50        api_version_from_group_version(Self::group(dt), Self::version(dt))
51    }
52    /// Returns the plural name of the kind
53    ///
54    /// This is known as the resource in apimachinery, we rename it for disambiguation.
55    fn plural(dt: &Self::DynamicType) -> Cow<'_, str>;
56
57    /// Returns whether this type should use metadata-only API requests
58    ///
59    /// When `true`, [`Api`] methods like `get`, `list`, `watch`, and `patch` will
60    /// automatically use metadata-optimized Accept headers (e.g.
61    /// `application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1`), so the API
62    /// server only returns object metadata instead of the full object.
63    ///
64    /// This is overridden to return `true` for [`PartialObjectMeta<K>`], making
65    /// `Api<PartialObjectMeta<K>>` automatically efficient without any caller changes.
66    ///
67    /// ```
68    /// # use kube_core::{Resource, metadata::PartialObjectMeta};
69    /// # use k8s_openapi::api::core::v1::Pod;
70    /// // Regular resources use full object requests
71    /// assert!(!Pod::metadata_api());
72    ///
73    /// // PartialObjectMeta wraps any resource for metadata-only requests
74    /// assert!(PartialObjectMeta::<Pod>::metadata_api());
75    /// ```
76    ///
77    /// [`Api`]: https://docs.rs/kube/latest/kube/struct.Api.html
78    /// [`PartialObjectMeta<K>`]: crate::metadata::PartialObjectMeta
79    fn metadata_api() -> bool {
80        false
81    }
82
83    /// Creates a url path for http requests for this resource
84    fn url_path(dt: &Self::DynamicType, namespace: Option<&str>) -> String {
85        let n = if let Some(ns) = namespace {
86            format!("namespaces/{ns}/")
87        } else {
88            "".into()
89        };
90        let group = Self::group(dt);
91        let api_version = Self::api_version(dt);
92        let plural = Self::plural(dt);
93        format!(
94            "/{group}/{api_version}/{namespaces}{plural}",
95            group = if group.is_empty() { "api" } else { "apis" },
96            api_version = api_version,
97            namespaces = n,
98            plural = plural
99        )
100    }
101
102    /// Metadata that all persisted resources must have
103    fn meta(&self) -> &ObjectMeta;
104    /// Metadata that all persisted resources must have
105    fn meta_mut(&mut self) -> &mut ObjectMeta;
106
107    /// Generates an object reference for the resource
108    fn object_ref(&self, dt: &Self::DynamicType) -> ObjectReference {
109        let meta = self.meta();
110        ObjectReference {
111            name: meta.name.clone(),
112            namespace: meta.namespace.clone(),
113            uid: meta.uid.clone(),
114            api_version: Some(Self::api_version(dt).to_string()),
115            kind: Some(Self::kind(dt).to_string()),
116            ..Default::default()
117        }
118    }
119
120    /// Generates a controller owner reference pointing to this resource
121    ///
122    /// Note: this returns an `Option`, but for objects populated from the apiserver,
123    /// this Option can be safely unwrapped.
124    ///
125    /// ```
126    /// use k8s_openapi::api::core::v1::ConfigMap;
127    /// use k8s_openapi::api::core::v1::Pod;
128    /// use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
129    /// use kube_core::Resource;
130    ///
131    /// let p = Pod::default();
132    /// let controller_ref = p.controller_owner_ref(&());
133    /// let cm = ConfigMap {
134    ///     metadata: ObjectMeta {
135    ///         name: Some("pod-configmap".to_string()),
136    ///         owner_references: Some(controller_ref.into_iter().collect()),
137    ///         ..ObjectMeta::default()
138    ///     },
139    ///     ..Default::default()
140    /// };
141    /// ```
142    fn controller_owner_ref(&self, dt: &Self::DynamicType) -> Option<OwnerReference> {
143        Some(OwnerReference {
144            controller: Some(true),
145            ..self.owner_ref(dt)?
146        })
147    }
148
149    /// Generates an owner reference pointing to this resource
150    ///
151    /// Note: this returns an `Option`, but for objects populated from the apiserver,
152    /// this Option can be safely unwrapped.
153    ///
154    /// ```
155    /// use k8s_openapi::api::core::v1::ConfigMap;
156    /// use k8s_openapi::api::core::v1::Pod;
157    /// use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
158    /// use kube_core::Resource;
159    ///
160    /// let p = Pod::default();
161    /// let owner_ref = p.owner_ref(&());
162    /// let cm = ConfigMap {
163    ///     metadata: ObjectMeta {
164    ///         name: Some("pod-configmap".to_string()),
165    ///         owner_references: Some(owner_ref.into_iter().collect()),
166    ///         ..ObjectMeta::default()
167    ///     },
168    ///     ..Default::default()
169    /// };
170    /// ```
171    fn owner_ref(&self, dt: &Self::DynamicType) -> Option<OwnerReference> {
172        let meta = self.meta();
173        Some(OwnerReference {
174            api_version: Self::api_version(dt).to_string(),
175            kind: Self::kind(dt).to_string(),
176            name: meta.name.clone()?,
177            uid: meta.uid.clone()?,
178            ..OwnerReference::default()
179        })
180    }
181}
182
183/// Helper function that creates the `apiVersion` field from the group and version strings.
184pub fn api_version_from_group_version<'a>(group: Cow<'a, str>, version: Cow<'a, str>) -> Cow<'a, str> {
185    if group.is_empty() {
186        return version;
187    }
188
189    let mut output = group;
190    output.to_mut().push('/');
191    output.to_mut().push_str(&version);
192    output
193}
194
195/// Implement accessor trait for any ObjectMeta-using Kubernetes Resource
196impl<K, S> Resource for K
197where
198    K: k8s_openapi::Metadata<Ty = ObjectMeta>,
199    K: k8s_openapi::Resource<Scope = S>,
200{
201    type DynamicType = ();
202    type Scope = S;
203
204    fn kind(_: &()) -> Cow<'_, str> {
205        K::KIND.into()
206    }
207
208    fn group(_: &()) -> Cow<'_, str> {
209        K::GROUP.into()
210    }
211
212    fn version(_: &()) -> Cow<'_, str> {
213        K::VERSION.into()
214    }
215
216    fn api_version(_: &()) -> Cow<'_, str> {
217        K::API_VERSION.into()
218    }
219
220    fn plural(_: &()) -> Cow<'_, str> {
221        K::URL_PATH_SEGMENT.into()
222    }
223
224    fn meta(&self) -> &ObjectMeta {
225        self.metadata()
226    }
227
228    fn meta_mut(&mut self) -> &mut ObjectMeta {
229        self.metadata_mut()
230    }
231}
232
233/// Helper methods for resources.
234pub trait ResourceExt: Resource {
235    /// Returns the name of the resource, panicking if it is unset
236    ///
237    /// Only use this function if you know that name is set; for example when
238    /// the resource was received from the apiserver (post-admission),
239    /// or if you constructed the resource with the name.
240    ///
241    /// At admission, `.metadata.generateName` can be set instead of name
242    /// and in those cases this function can panic.
243    ///
244    /// Prefer using `.meta().name` or [`name_any`](ResourceExt::name_any)
245    /// for the more general cases.
246    fn name_unchecked(&self) -> String;
247
248    /// Returns the most useful name identifier available
249    ///
250    /// This is tries `name`, then `generateName`, and falls back on an empty string when neither is set.
251    /// Generally you always have one of the two unless you are creating the object locally.
252    ///
253    /// This is intended to provide something quick and simple for standard logging purposes.
254    /// For more precise use cases, prefer doing your own defaulting.
255    /// For true uniqueness, prefer [`uid`](ResourceExt::uid).
256    fn name_any(&self) -> String;
257
258    /// The namespace the resource is in
259    fn namespace(&self) -> Option<String>;
260    /// The resource version
261    fn resource_version(&self) -> Option<String>;
262    /// Unique ID (if you delete resource and then create a new
263    /// resource with the same name, it will have different ID)
264    fn uid(&self) -> Option<String>;
265    /// Returns the creation timestamp
266    ///
267    /// This is guaranteed to exist on resources received by the apiserver.
268    fn creation_timestamp(&self) -> Option<Time>;
269    /// Returns resource labels
270    fn labels(&self) -> &BTreeMap<String, String>;
271    /// Provides mutable access to the labels
272    fn labels_mut(&mut self) -> &mut BTreeMap<String, String>;
273    /// Returns resource annotations
274    fn annotations(&self) -> &BTreeMap<String, String>;
275    /// Provider mutable access to the annotations
276    fn annotations_mut(&mut self) -> &mut BTreeMap<String, String>;
277    /// Returns resource owner references
278    fn owner_references(&self) -> &[OwnerReference];
279    /// Provides mutable access to the owner references
280    fn owner_references_mut(&mut self) -> &mut Vec<OwnerReference>;
281    /// Returns resource finalizers
282    fn finalizers(&self) -> &[String];
283    /// Provides mutable access to the finalizers
284    fn finalizers_mut(&mut self) -> &mut Vec<String>;
285    /// Returns managed fields
286    fn managed_fields(&self) -> &[ManagedFieldsEntry];
287    /// Provides mutable access to managed fields
288    fn managed_fields_mut(&mut self) -> &mut Vec<ManagedFieldsEntry>;
289}
290
291static EMPTY_MAP: BTreeMap<String, String> = BTreeMap::new();
292
293impl<K: Resource> ResourceExt for K {
294    fn name_unchecked(&self) -> String {
295        self.meta().name.clone().expect(".metadata.name missing")
296    }
297
298    fn name_any(&self) -> String {
299        self.meta()
300            .name
301            .clone()
302            .or_else(|| self.meta().generate_name.clone())
303            .unwrap_or_default()
304    }
305
306    fn namespace(&self) -> Option<String> {
307        self.meta().namespace.clone()
308    }
309
310    fn resource_version(&self) -> Option<String> {
311        self.meta().resource_version.clone()
312    }
313
314    fn uid(&self) -> Option<String> {
315        self.meta().uid.clone()
316    }
317
318    fn creation_timestamp(&self) -> Option<Time> {
319        self.meta().creation_timestamp.clone()
320    }
321
322    fn labels(&self) -> &BTreeMap<String, String> {
323        self.meta().labels.as_ref().unwrap_or(&EMPTY_MAP)
324    }
325
326    fn labels_mut(&mut self) -> &mut BTreeMap<String, String> {
327        self.meta_mut().labels.get_or_insert_with(BTreeMap::new)
328    }
329
330    fn annotations(&self) -> &BTreeMap<String, String> {
331        self.meta().annotations.as_ref().unwrap_or(&EMPTY_MAP)
332    }
333
334    fn annotations_mut(&mut self) -> &mut BTreeMap<String, String> {
335        self.meta_mut().annotations.get_or_insert_with(BTreeMap::new)
336    }
337
338    fn owner_references(&self) -> &[OwnerReference] {
339        self.meta().owner_references.as_deref().unwrap_or_default()
340    }
341
342    fn owner_references_mut(&mut self) -> &mut Vec<OwnerReference> {
343        self.meta_mut().owner_references.get_or_insert_with(Vec::new)
344    }
345
346    fn finalizers(&self) -> &[String] {
347        self.meta().finalizers.as_deref().unwrap_or_default()
348    }
349
350    fn finalizers_mut(&mut self) -> &mut Vec<String> {
351        self.meta_mut().finalizers.get_or_insert_with(Vec::new)
352    }
353
354    fn managed_fields(&self) -> &[ManagedFieldsEntry] {
355        self.meta().managed_fields.as_deref().unwrap_or_default()
356    }
357
358    fn managed_fields_mut(&mut self) -> &mut Vec<ManagedFieldsEntry> {
359        self.meta_mut().managed_fields.get_or_insert_with(Vec::new)
360    }
361}