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}