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