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    /// Creates a url path for http requests for this resource
58    fn url_path(dt: &Self::DynamicType, namespace: Option<&str>) -> String {
59        let n = if let Some(ns) = namespace {
60            format!("namespaces/{ns}/")
61        } else {
62            "".into()
63        };
64        let group = Self::group(dt);
65        let api_version = Self::api_version(dt);
66        let plural = Self::plural(dt);
67        format!(
68            "/{group}/{api_version}/{namespaces}{plural}",
69            group = if group.is_empty() { "api" } else { "apis" },
70            api_version = api_version,
71            namespaces = n,
72            plural = plural
73        )
74    }
75
76    /// Metadata that all persisted resources must have
77    fn meta(&self) -> &ObjectMeta;
78    /// Metadata that all persisted resources must have
79    fn meta_mut(&mut self) -> &mut ObjectMeta;
80
81    /// Generates an object reference for the resource
82    fn object_ref(&self, dt: &Self::DynamicType) -> ObjectReference {
83        let meta = self.meta();
84        ObjectReference {
85            name: meta.name.clone(),
86            namespace: meta.namespace.clone(),
87            uid: meta.uid.clone(),
88            api_version: Some(Self::api_version(dt).to_string()),
89            kind: Some(Self::kind(dt).to_string()),
90            ..Default::default()
91        }
92    }
93
94    /// Generates a controller owner reference pointing to this resource
95    ///
96    /// Note: this returns an `Option`, but for objects populated from the apiserver,
97    /// this Option can be safely unwrapped.
98    ///
99    /// ```
100    /// use k8s_openapi::api::core::v1::ConfigMap;
101    /// use k8s_openapi::api::core::v1::Pod;
102    /// use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
103    /// use kube_core::Resource;
104    ///
105    /// let p = Pod::default();
106    /// let controller_ref = p.controller_owner_ref(&());
107    /// let cm = ConfigMap {
108    ///     metadata: ObjectMeta {
109    ///         name: Some("pod-configmap".to_string()),
110    ///         owner_references: Some(controller_ref.into_iter().collect()),
111    ///         ..ObjectMeta::default()
112    ///     },
113    ///     ..Default::default()
114    /// };
115    /// ```
116    fn controller_owner_ref(&self, dt: &Self::DynamicType) -> Option<OwnerReference> {
117        Some(OwnerReference {
118            controller: Some(true),
119            ..self.owner_ref(dt)?
120        })
121    }
122
123    /// Generates an owner reference pointing to this resource
124    ///
125    /// Note: this returns an `Option`, but for objects populated from the apiserver,
126    /// this Option can be safely unwrapped.
127    ///
128    /// ```
129    /// use k8s_openapi::api::core::v1::ConfigMap;
130    /// use k8s_openapi::api::core::v1::Pod;
131    /// use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
132    /// use kube_core::Resource;
133    ///
134    /// let p = Pod::default();
135    /// let owner_ref = p.owner_ref(&());
136    /// let cm = ConfigMap {
137    ///     metadata: ObjectMeta {
138    ///         name: Some("pod-configmap".to_string()),
139    ///         owner_references: Some(owner_ref.into_iter().collect()),
140    ///         ..ObjectMeta::default()
141    ///     },
142    ///     ..Default::default()
143    /// };
144    /// ```
145    fn owner_ref(&self, dt: &Self::DynamicType) -> Option<OwnerReference> {
146        let meta = self.meta();
147        Some(OwnerReference {
148            api_version: Self::api_version(dt).to_string(),
149            kind: Self::kind(dt).to_string(),
150            name: meta.name.clone()?,
151            uid: meta.uid.clone()?,
152            ..OwnerReference::default()
153        })
154    }
155}
156
157/// Helper function that creates the `apiVersion` field from the group and version strings.
158pub fn api_version_from_group_version<'a>(group: Cow<'a, str>, version: Cow<'a, str>) -> Cow<'a, str> {
159    if group.is_empty() {
160        return version;
161    }
162
163    let mut output = group;
164    output.to_mut().push('/');
165    output.to_mut().push_str(&version);
166    output
167}
168
169/// Implement accessor trait for any ObjectMeta-using Kubernetes Resource
170impl<K, S> Resource for K
171where
172    K: k8s_openapi::Metadata<Ty = ObjectMeta>,
173    K: k8s_openapi::Resource<Scope = S>,
174{
175    type DynamicType = ();
176    type Scope = S;
177
178    fn kind(_: &()) -> Cow<'_, str> {
179        K::KIND.into()
180    }
181
182    fn group(_: &()) -> Cow<'_, str> {
183        K::GROUP.into()
184    }
185
186    fn version(_: &()) -> Cow<'_, str> {
187        K::VERSION.into()
188    }
189
190    fn api_version(_: &()) -> Cow<'_, str> {
191        K::API_VERSION.into()
192    }
193
194    fn plural(_: &()) -> Cow<'_, str> {
195        K::URL_PATH_SEGMENT.into()
196    }
197
198    fn meta(&self) -> &ObjectMeta {
199        self.metadata()
200    }
201
202    fn meta_mut(&mut self) -> &mut ObjectMeta {
203        self.metadata_mut()
204    }
205}
206
207/// Helper methods for resources.
208pub trait ResourceExt: Resource {
209    /// Returns the name of the resource, panicking if it is unset
210    ///
211    /// Only use this function if you know that name is set; for example when
212    /// the resource was received from the apiserver (post-admission),
213    /// or if you constructed the resource with the name.
214    ///
215    /// At admission, `.metadata.generateName` can be set instead of name
216    /// and in those cases this function can panic.
217    ///
218    /// Prefer using `.meta().name` or [`name_any`](ResourceExt::name_any)
219    /// for the more general cases.
220    fn name_unchecked(&self) -> String;
221
222    /// Returns the most useful name identifier available
223    ///
224    /// This is tries `name`, then `generateName`, and falls back on an empty string when neither is set.
225    /// Generally you always have one of the two unless you are creating the object locally.
226    ///
227    /// This is intended to provide something quick and simple for standard logging purposes.
228    /// For more precise use cases, prefer doing your own defaulting.
229    /// For true uniqueness, prefer [`uid`](ResourceExt::uid).
230    fn name_any(&self) -> String;
231
232    /// The namespace the resource is in
233    fn namespace(&self) -> Option<String>;
234    /// The resource version
235    fn resource_version(&self) -> Option<String>;
236    /// Unique ID (if you delete resource and then create a new
237    /// resource with the same name, it will have different ID)
238    fn uid(&self) -> Option<String>;
239    /// Returns the creation timestamp
240    ///
241    /// This is guaranteed to exist on resources received by the apiserver.
242    fn creation_timestamp(&self) -> Option<Time>;
243    /// Returns resource labels
244    fn labels(&self) -> &BTreeMap<String, String>;
245    /// Provides mutable access to the labels
246    fn labels_mut(&mut self) -> &mut BTreeMap<String, String>;
247    /// Returns resource annotations
248    fn annotations(&self) -> &BTreeMap<String, String>;
249    /// Provider mutable access to the annotations
250    fn annotations_mut(&mut self) -> &mut BTreeMap<String, String>;
251    /// Returns resource owner references
252    fn owner_references(&self) -> &[OwnerReference];
253    /// Provides mutable access to the owner references
254    fn owner_references_mut(&mut self) -> &mut Vec<OwnerReference>;
255    /// Returns resource finalizers
256    fn finalizers(&self) -> &[String];
257    /// Provides mutable access to the finalizers
258    fn finalizers_mut(&mut self) -> &mut Vec<String>;
259    /// Returns managed fields
260    fn managed_fields(&self) -> &[ManagedFieldsEntry];
261    /// Provides mutable access to managed fields
262    fn managed_fields_mut(&mut self) -> &mut Vec<ManagedFieldsEntry>;
263}
264
265static EMPTY_MAP: BTreeMap<String, String> = BTreeMap::new();
266
267impl<K: Resource> ResourceExt for K {
268    fn name_unchecked(&self) -> String {
269        self.meta().name.clone().expect(".metadata.name missing")
270    }
271
272    fn name_any(&self) -> String {
273        self.meta()
274            .name
275            .clone()
276            .or_else(|| self.meta().generate_name.clone())
277            .unwrap_or_default()
278    }
279
280    fn namespace(&self) -> Option<String> {
281        self.meta().namespace.clone()
282    }
283
284    fn resource_version(&self) -> Option<String> {
285        self.meta().resource_version.clone()
286    }
287
288    fn uid(&self) -> Option<String> {
289        self.meta().uid.clone()
290    }
291
292    fn creation_timestamp(&self) -> Option<Time> {
293        self.meta().creation_timestamp.clone()
294    }
295
296    fn labels(&self) -> &BTreeMap<String, String> {
297        self.meta().labels.as_ref().unwrap_or(&EMPTY_MAP)
298    }
299
300    fn labels_mut(&mut self) -> &mut BTreeMap<String, String> {
301        self.meta_mut().labels.get_or_insert_with(BTreeMap::new)
302    }
303
304    fn annotations(&self) -> &BTreeMap<String, String> {
305        self.meta().annotations.as_ref().unwrap_or(&EMPTY_MAP)
306    }
307
308    fn annotations_mut(&mut self) -> &mut BTreeMap<String, String> {
309        self.meta_mut().annotations.get_or_insert_with(BTreeMap::new)
310    }
311
312    fn owner_references(&self) -> &[OwnerReference] {
313        self.meta().owner_references.as_deref().unwrap_or_default()
314    }
315
316    fn owner_references_mut(&mut self) -> &mut Vec<OwnerReference> {
317        self.meta_mut().owner_references.get_or_insert_with(Vec::new)
318    }
319
320    fn finalizers(&self) -> &[String] {
321        self.meta().finalizers.as_deref().unwrap_or_default()
322    }
323
324    fn finalizers_mut(&mut self) -> &mut Vec<String> {
325        self.meta_mut().finalizers.get_or_insert_with(Vec::new)
326    }
327
328    fn managed_fields(&self) -> &[ManagedFieldsEntry] {
329        self.meta().managed_fields.as_deref().unwrap_or_default()
330    }
331
332    fn managed_fields_mut(&mut self) -> &mut Vec<ManagedFieldsEntry> {
333        self.meta_mut().managed_fields.get_or_insert_with(Vec::new)
334    }
335}