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}