kube_client/api/
core_methods.rs

1use either::Either;
2use futures::Stream;
3use serde::{de::DeserializeOwned, Serialize};
4use std::fmt::Debug;
5
6use crate::{api::Api, Error, Result};
7use kube_core::{
8    metadata::PartialObjectMeta, object::ObjectList, params::*, response::Status, ErrorResponse, WatchEvent,
9};
10
11/// PUSH/PUT/POST/GET abstractions
12impl<K> Api<K>
13where
14    K: Clone + DeserializeOwned + Debug,
15{
16    /// Get a named resource
17    ///
18    /// ```no_run
19    /// # use kube::Api;
20    /// use k8s_openapi::api::core::v1::Pod;
21    ///
22    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
23    /// # let client: kube::Client = todo!();
24    /// let pods: Api<Pod> = Api::namespaced(client, "apps");
25    /// let p: Pod = pods.get("blog").await?;
26    /// # Ok(())
27    /// # }
28    /// ```
29    ///
30    /// # Errors
31    ///
32    /// This function assumes that the object is expected to always exist, and returns [`Error`] if it does not.
33    /// Consider using [`Api::get_opt`] if you need to handle missing objects.
34    pub async fn get(&self, name: &str) -> Result<K> {
35        self.get_with(name, &GetParams::default()).await
36    }
37
38    ///  Get only the metadata for a named resource as [`PartialObjectMeta`]
39    ///
40    /// ```no_run
41    /// use kube::{Api, core::PartialObjectMeta};
42    /// use k8s_openapi::api::core::v1::Pod;
43    ///
44    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
45    /// # let client: kube::Client = todo!();
46    /// let pods: Api<Pod> = Api::namespaced(client, "apps");
47    /// let p: PartialObjectMeta<Pod> = pods.get_metadata("blog").await?;
48    /// # Ok(())
49    /// # }
50    /// ```
51    /// Note that the type may be converted to `ObjectMeta` through the usual
52    /// conversion traits.
53    ///
54    /// # Errors
55    ///
56    /// This function assumes that the object is expected to always exist, and returns [`Error`] if it does not.
57    /// Consider using [`Api::get_metadata_opt`] if you need to handle missing objects.
58    pub async fn get_metadata(&self, name: &str) -> Result<PartialObjectMeta<K>> {
59        self.get_metadata_with(name, &GetParams::default()).await
60    }
61
62    /// [Get](`Api::get`) a named resource with an explicit resourceVersion
63    ///
64    /// This function allows the caller to pass in a [`GetParams`](`super::GetParams`) type containing
65    /// a `resourceVersion` to a [Get](`Api::get`) call.
66    /// For example
67    ///
68    /// ```no_run
69    /// # use kube::{Api, api::GetParams};
70    /// use k8s_openapi::api::core::v1::Pod;
71    ///
72    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
73    /// # let client: kube::Client = todo!();
74    /// let pods: Api<Pod> = Api::namespaced(client, "apps");
75    /// let p: Pod = pods.get_with("blog", &GetParams::any()).await?;
76    /// # Ok(())
77    /// # }
78    /// ```
79    ///
80    /// # Errors
81    ///
82    /// This function assumes that the object is expected to always exist, and returns [`Error`] if it does not.
83    /// Consider using [`Api::get_opt`] if you need to handle missing objects.
84    pub async fn get_with(&self, name: &str, gp: &GetParams) -> Result<K> {
85        let mut req = self.request.get(name, gp).map_err(Error::BuildRequest)?;
86        req.extensions_mut().insert("get");
87        self.client.request::<K>(req).await
88    }
89
90    ///  [Get](`Api::get_metadata`) the metadata of an object using an explicit `resourceVersion`
91    ///
92    /// This function allows the caller to pass in a [`GetParams`](`super::GetParams`) type containing
93    /// a `resourceVersion` to a [Get](`Api::get_metadata`) call.
94    /// For example
95    ///
96    ///
97    /// ```no_run
98    /// use kube::{Api, api::GetParams, core::PartialObjectMeta};
99    /// use k8s_openapi::api::core::v1::Pod;
100    ///
101    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
102    /// # let client: kube::Client = todo!();
103    /// let pods: Api<Pod> = Api::namespaced(client, "apps");
104    /// let p: PartialObjectMeta<Pod> = pods.get_metadata_with("blog", &GetParams::any()).await?;
105    /// # Ok(())
106    /// # }
107    /// ```
108    /// Note that the type may be converted to `ObjectMeta` through the usual
109    /// conversion traits.
110    ///
111    /// # Errors
112    ///
113    /// This function assumes that the object is expected to always exist, and returns [`Error`] if it does not.
114    /// Consider using [`Api::get_metadata_opt`] if you need to handle missing objects.
115    pub async fn get_metadata_with(&self, name: &str, gp: &GetParams) -> Result<PartialObjectMeta<K>> {
116        let mut req = self.request.get_metadata(name, gp).map_err(Error::BuildRequest)?;
117        req.extensions_mut().insert("get_metadata");
118        self.client.request::<PartialObjectMeta<K>>(req).await
119    }
120
121    /// [Get](`Api::get`) a named resource if it exists, returns [`None`] if it doesn't exist
122    ///
123    /// ```no_run
124    /// # use kube::Api;
125    /// use k8s_openapi::api::core::v1::Pod;
126    ///
127    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
128    /// # let client: kube::Client = todo!();
129    /// let pods: Api<Pod> = Api::namespaced(client, "apps");
130    /// if let Some(pod) = pods.get_opt("blog").await? {
131    ///     // Pod was found
132    /// } else {
133    ///     // Pod was not found
134    /// }
135    /// # Ok(())
136    /// # }
137    /// ```
138    pub async fn get_opt(&self, name: &str) -> Result<Option<K>> {
139        match self.get(name).await {
140            Ok(obj) => Ok(Some(obj)),
141            Err(Error::Api(ErrorResponse { reason, .. })) if &reason == "NotFound" => Ok(None),
142            Err(err) => Err(err),
143        }
144    }
145
146    /// [Get Metadata](`Api::get_metadata`) for a named resource if it exists, returns [`None`] if it doesn't exist
147    ///
148    /// ```no_run
149    /// # use kube::Api;
150    /// use k8s_openapi::api::core::v1::Pod;
151    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
152    /// # let client: kube::Client = todo!();
153    /// let pods: Api<Pod> = Api::namespaced(client, "apps");
154    /// if let Some(pod) = pods.get_metadata_opt("blog").await? {
155    ///     // Pod was found
156    /// } else {
157    ///     // Pod was not found
158    /// }
159    /// # Ok(())
160    /// # }
161    /// ```
162    ///
163    /// Note that [`PartialObjectMeta`] embeds the raw `ObjectMeta`.
164    pub async fn get_metadata_opt(&self, name: &str) -> Result<Option<PartialObjectMeta<K>>> {
165        self.get_metadata_opt_with(name, &GetParams::default()).await
166    }
167
168    /// [Get Metadata](`Api::get_metadata`) of an object if it exists, using an explicit `resourceVersion`.
169    /// Returns [`None`] if it doesn't exist.
170    ///
171    /// ```no_run
172    /// # use kube::Api;
173    /// use k8s_openapi::api::core::v1::Pod;
174    /// use kube_core::params::GetParams;
175    ///
176    /// async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
177    /// # let client: kube::Client = todo!();
178    /// let pods: Api<Pod> = Api::namespaced(client, "apps");
179    /// if let Some(pod) = pods.get_metadata_opt_with("blog", &GetParams::any()).await? {
180    ///     // Pod was found
181    /// } else {
182    ///     // Pod was not found
183    /// }
184    /// # Ok(())
185    /// # }
186    /// ```
187    ///
188    /// Note that [`PartialObjectMeta`] embeds the raw `ObjectMeta`.
189    pub async fn get_metadata_opt_with(
190        &self,
191        name: &str,
192        gp: &GetParams,
193    ) -> Result<Option<PartialObjectMeta<K>>> {
194        match self.get_metadata_with(name, gp).await {
195            Ok(meta) => Ok(Some(meta)),
196            Err(Error::Api(ErrorResponse { reason, .. })) if &reason == "NotFound" => Ok(None),
197            Err(err) => Err(err),
198        }
199    }
200
201    /// Get a list of resources
202    ///
203    /// You use this to get everything, or a subset matching fields/labels, say:
204    ///
205    /// ```no_run
206    /// use kube::api::{Api, ListParams, ResourceExt};
207    /// use k8s_openapi::api::core::v1::Pod;
208    ///
209    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
210    /// # let client: kube::Client = todo!();
211    /// let pods: Api<Pod> = Api::namespaced(client, "apps");
212    /// let lp = ListParams::default().labels("app=blog"); // for this app only
213    /// for p in pods.list(&lp).await? {
214    ///     println!("Found Pod: {}", p.name_any());
215    /// }
216    /// # Ok(())
217    /// # }
218    /// ```
219    pub async fn list(&self, lp: &ListParams) -> Result<ObjectList<K>> {
220        let mut req = self.request.list(lp).map_err(Error::BuildRequest)?;
221        req.extensions_mut().insert("list");
222        self.client.request::<ObjectList<K>>(req).await
223    }
224
225    /// Get a list of resources that contains only their metadata as
226    ///
227    /// Similar to [list](`Api::list`), you use this to get everything, or a
228    /// subset matching fields/labels. For example
229    ///
230    /// ```no_run
231    /// use kube::api::{Api, ListParams, ResourceExt};
232    /// use kube::core::{ObjectMeta, ObjectList, PartialObjectMeta};
233    /// use k8s_openapi::api::core::v1::Pod;
234    ///
235    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
236    /// # let client: kube::Client = todo!();
237    /// let pods: Api<Pod> = Api::namespaced(client, "apps");
238    /// let lp = ListParams::default().labels("app=blog"); // for this app only
239    /// let list: ObjectList<PartialObjectMeta<Pod>> = pods.list_metadata(&lp).await?;
240    /// for p in list {
241    ///     println!("Found Pod: {}", p.name_any());
242    /// }
243    /// # Ok(())
244    /// # }
245    /// ```
246    pub async fn list_metadata(&self, lp: &ListParams) -> Result<ObjectList<PartialObjectMeta<K>>> {
247        let mut req = self.request.list_metadata(lp).map_err(Error::BuildRequest)?;
248        req.extensions_mut().insert("list_metadata");
249        self.client.request::<ObjectList<PartialObjectMeta<K>>>(req).await
250    }
251
252    /// Create a resource
253    ///
254    /// This function requires a type that Serializes to `K`, which can be:
255    /// 1. Raw string YAML
256    /// - easy to port from existing files
257    ///     - error prone (run-time errors on typos due to failed serialize attempts)
258    ///     - very error prone (can write invalid YAML)
259    /// 2. An instance of the struct itself
260    ///     - easy to instantiate for CRDs (you define the struct)
261    ///     - dense to instantiate for [`k8s_openapi`] types (due to many optionals)
262    ///     - compile-time safety
263    ///     - but still possible to write invalid native types (validation at apiserver)
264    /// 3. [`serde_json::json!`] macro instantiated [`serde_json::Value`]
265    ///     - Tradeoff between the two
266    ///     - Easy partially filling of native [`k8s_openapi`] types (most fields optional)
267    ///     - Partial safety against runtime errors (at least you must write valid JSON)
268    ///
269    /// Note that this method cannot write to the status object (when it exists) of a resource.
270    /// To set status objects please see [`Api::replace_status`] or [`Api::patch_status`].
271    pub async fn create(&self, pp: &PostParams, data: &K) -> Result<K>
272    where
273        K: Serialize,
274    {
275        let bytes = serde_json::to_vec(&data).map_err(Error::SerdeError)?;
276        let mut req = self.request.create(pp, bytes).map_err(Error::BuildRequest)?;
277        req.extensions_mut().insert("create");
278        self.client.request::<K>(req).await
279    }
280
281    /// Delete a named resource
282    ///
283    /// When you get a `K` via `Left`, your delete has started.
284    /// When you get a `Status` via `Right`, this should be a a 2XX style
285    /// confirmation that the object being gone.
286    ///
287    /// 4XX and 5XX status types are returned as an [`Err(kube_client::Error::Api)`](crate::Error::Api).
288    ///
289    /// ```no_run
290    /// use kube::api::{Api, DeleteParams};
291    /// use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1 as apiexts;
292    /// use apiexts::CustomResourceDefinition;
293    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
294    /// # let client: kube::Client = todo!();
295    /// let crds: Api<CustomResourceDefinition> = Api::all(client);
296    /// crds.delete("foos.clux.dev", &DeleteParams::default()).await?
297    ///     .map_left(|o| println!("Deleting CRD: {:?}", o.status))
298    ///     .map_right(|s| println!("Deleted CRD: {:?}", s));
299    /// # Ok(())
300    /// # }
301    /// ```
302    pub async fn delete(&self, name: &str, dp: &DeleteParams) -> Result<Either<K, Status>> {
303        let mut req = self.request.delete(name, dp).map_err(Error::BuildRequest)?;
304        req.extensions_mut().insert("delete");
305        self.client.request_status::<K>(req).await
306    }
307
308    /// Delete a collection of resources
309    ///
310    /// When you get an `ObjectList<K>` via `Left`, your delete has started.
311    /// When you get a `Status` via `Right`, this should be a a 2XX style
312    /// confirmation that the object being gone.
313    ///
314    /// 4XX and 5XX status types are returned as an [`Err(kube_client::Error::Api)`](crate::Error::Api).
315    ///
316    /// ```no_run
317    /// use kube::api::{Api, DeleteParams, ListParams, ResourceExt};
318    /// use k8s_openapi::api::core::v1::Pod;
319    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
320    /// # let client: kube::Client = todo!();
321    ///
322    /// let pods: Api<Pod> = Api::namespaced(client, "apps");
323    /// match pods.delete_collection(&DeleteParams::default(), &ListParams::default()).await? {
324    ///     either::Left(list) => {
325    ///         let names: Vec<_> = list.iter().map(ResourceExt::name_any).collect();
326    ///         println!("Deleting collection of pods: {:?}", names);
327    ///     },
328    ///     either::Right(status) => {
329    ///         println!("Deleted collection of pods: status={:?}", status);
330    ///     }
331    /// }
332    /// # Ok(())
333    /// # }
334    /// ```
335    pub async fn delete_collection(
336        &self,
337        dp: &DeleteParams,
338        lp: &ListParams,
339    ) -> Result<Either<ObjectList<K>, Status>> {
340        let mut req = self
341            .request
342            .delete_collection(dp, lp)
343            .map_err(Error::BuildRequest)?;
344        req.extensions_mut().insert("delete_collection");
345        self.client.request_status::<ObjectList<K>>(req).await
346    }
347
348    /// Patch a subset of a resource's properties
349    ///
350    /// Takes a [`Patch`] along with [`PatchParams`] for the call.
351    ///
352    /// ```no_run
353    /// use kube::api::{Api, PatchParams, Patch, Resource};
354    /// use k8s_openapi::api::core::v1::Pod;
355    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
356    /// # let client: kube::Client = todo!();
357    ///
358    /// let pods: Api<Pod> = Api::namespaced(client, "apps");
359    /// let patch = serde_json::json!({
360    ///     "apiVersion": "v1",
361    ///     "kind": "Pod",
362    ///     "metadata": {
363    ///         "name": "blog"
364    ///     },
365    ///     "spec": {
366    ///         "activeDeadlineSeconds": 5
367    ///     }
368    /// });
369    /// let params = PatchParams::apply("myapp");
370    /// let patch = Patch::Apply(&patch);
371    /// let o_patched = pods.patch("blog", &params, &patch).await?;
372    /// # Ok(())
373    /// # }
374    /// ```
375    /// [`Patch`]: super::Patch
376    /// [`PatchParams`]: super::PatchParams
377    ///
378    /// Note that this method cannot write to the status object (when it exists) of a resource.
379    /// To set status objects please see [`Api::replace_status`] or [`Api::patch_status`].
380    pub async fn patch<P: Serialize + Debug>(
381        &self,
382        name: &str,
383        pp: &PatchParams,
384        patch: &Patch<P>,
385    ) -> Result<K> {
386        let mut req = self.request.patch(name, pp, patch).map_err(Error::BuildRequest)?;
387        req.extensions_mut().insert("patch");
388        self.client.request::<K>(req).await
389    }
390
391    /// Patch a metadata subset of a resource's properties from [`PartialObjectMeta`]
392    ///
393    /// Takes a [`Patch`] along with [`PatchParams`] for the call.
394    /// Patches can be constructed raw using `serde_json::json!` or from `ObjectMeta` via [`PartialObjectMetaExt`].
395    ///
396    /// ```no_run
397    /// use kube::api::{Api, PatchParams, Patch, Resource};
398    /// use kube::core::{PartialObjectMetaExt, ObjectMeta};
399    /// use k8s_openapi::api::core::v1::Pod;
400    ///
401    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
402    /// # let client: kube::Client = todo!();
403    /// let pods: Api<Pod> = Api::namespaced(client, "apps");
404    /// let metadata = ObjectMeta {
405    ///     labels: Some([("key".to_string(), "value".to_string())].into()),
406    ///     ..Default::default()
407    /// }.into_request_partial::<Pod>();
408    ///
409    /// let params = PatchParams::apply("myapp");
410    /// let o_patched = pods.patch_metadata("blog", &params, &Patch::Apply(&metadata)).await?;
411    /// println!("Patched {}", o_patched.metadata.name.unwrap());
412    /// # Ok(())
413    /// # }
414    /// ```
415    /// [`Patch`]: super::Patch
416    /// [`PatchParams`]: super::PatchParams
417    /// [`PartialObjectMetaExt`]: crate::core::PartialObjectMetaExt
418    ///
419    /// ### Warnings
420    ///
421    /// The `TypeMeta` (apiVersion + kind) of a patch request (required for apply patches)
422    /// must match the underlying type that is being patched (e.g. "v1" + "Pod").
423    /// The returned `TypeMeta` will always be {"meta.k8s.io/v1", "PartialObjectMetadata"}.
424    /// These constraints are encoded into [`PartialObjectMetaExt`].
425    ///
426    /// This method can write to non-metadata fields such as spec if included in the patch.
427    pub async fn patch_metadata<P: Serialize + Debug>(
428        &self,
429        name: &str,
430        pp: &PatchParams,
431        patch: &Patch<P>,
432    ) -> Result<PartialObjectMeta<K>> {
433        let mut req = self
434            .request
435            .patch_metadata(name, pp, patch)
436            .map_err(Error::BuildRequest)?;
437        req.extensions_mut().insert("patch_metadata");
438        self.client.request::<PartialObjectMeta<K>>(req).await
439    }
440
441    /// Replace a resource entirely with a new one
442    ///
443    /// This is used just like [`Api::create`], but with one additional instruction:
444    /// You must set `metadata.resourceVersion` in the provided data because k8s
445    /// will not accept an update unless you actually knew what the last version was.
446    ///
447    /// Thus, to use this function, you need to do a `get` then a `replace` with its result.
448    ///
449    /// ```no_run
450    /// use kube::api::{Api, PostParams, ResourceExt};
451    /// use k8s_openapi::api::batch::v1::Job;
452    ///
453    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
454    /// # let client: kube::Client = todo!();
455    /// let jobs: Api<Job> = Api::namespaced(client, "apps");
456    /// let j = jobs.get("baz").await?;
457    /// let j_new: Job = serde_json::from_value(serde_json::json!({
458    ///     "apiVersion": "batch/v1",
459    ///     "kind": "Job",
460    ///     "metadata": {
461    ///         "name": "baz",
462    ///         "resourceVersion": j.resource_version(),
463    ///     },
464    ///     "spec": {
465    ///         "template": {
466    ///             "metadata": {
467    ///                 "name": "empty-job-pod"
468    ///             },
469    ///             "spec": {
470    ///                 "containers": [{
471    ///                     "name": "empty",
472    ///                     "image": "alpine:latest"
473    ///                 }],
474    ///                 "restartPolicy": "Never",
475    ///             }
476    ///         }
477    ///     }
478    /// }))?;
479    /// jobs.replace("baz", &PostParams::default(), &j_new).await?;
480    /// # Ok(())
481    /// # }
482    /// ```
483    ///
484    /// Consider mutating the result of `api.get` rather than recreating it.
485    ///
486    /// Note that this method cannot write to the status object (when it exists) of a resource.
487    /// To set status objects please see [`Api::replace_status`] or [`Api::patch_status`].
488    pub async fn replace(&self, name: &str, pp: &PostParams, data: &K) -> Result<K>
489    where
490        K: Serialize,
491    {
492        let bytes = serde_json::to_vec(&data).map_err(Error::SerdeError)?;
493        let mut req = self
494            .request
495            .replace(name, pp, bytes)
496            .map_err(Error::BuildRequest)?;
497        req.extensions_mut().insert("replace");
498        self.client.request::<K>(req).await
499    }
500
501    /// Watch a list of resources
502    ///
503    /// This returns a future that awaits the initial response,
504    /// then you can stream the remaining buffered `WatchEvent` objects.
505    ///
506    /// Note that a `watch` call can terminate for many reasons (even before the specified
507    /// [`WatchParams::timeout`] is triggered), and will have to be re-issued
508    /// with the last seen resource version when or if it closes.
509    ///
510    /// Consider using a managed [`watcher`] to deal with automatic re-watches and error cases.
511    ///
512    /// ```no_run
513    /// use kube::api::{Api, WatchParams, ResourceExt, WatchEvent};
514    /// use k8s_openapi::api::batch::v1::Job;
515    /// use futures::{StreamExt, TryStreamExt};
516    ///
517    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
518    /// # let client: kube::Client = todo!();
519    /// let jobs: Api<Job> = Api::namespaced(client, "apps");
520    /// let lp = WatchParams::default()
521    ///     .fields("metadata.name=my_job")
522    ///     .timeout(20); // upper bound of how long we watch for
523    /// let mut stream = jobs.watch(&lp, "0").await?.boxed();
524    /// while let Some(status) = stream.try_next().await? {
525    ///     match status {
526    ///         WatchEvent::Added(s) => println!("Added {}", s.name_any()),
527    ///         WatchEvent::Modified(s) => println!("Modified: {}", s.name_any()),
528    ///         WatchEvent::Deleted(s) => println!("Deleted {}", s.name_any()),
529    ///         WatchEvent::Bookmark(s) => {},
530    ///         WatchEvent::Error(s) => println!("{}", s),
531    ///     }
532    /// }
533    /// # Ok(())
534    /// # }
535    /// ```
536    /// [`WatchParams::timeout`]: super::WatchParams::timeout
537    /// [`watcher`]: https://docs.rs/kube_runtime/*/kube_runtime/watcher/fn.watcher.html
538    pub async fn watch(
539        &self,
540        wp: &WatchParams,
541        version: &str,
542    ) -> Result<impl Stream<Item = Result<WatchEvent<K>>>> {
543        let mut req = self.request.watch(wp, version).map_err(Error::BuildRequest)?;
544        req.extensions_mut().insert("watch");
545        self.client.request_events::<K>(req).await
546    }
547
548    /// Watch a list of metadata for a given resources
549    ///
550    /// This returns a future that awaits the initial response,
551    /// then you can stream the remaining buffered `WatchEvent` objects.
552    ///
553    /// Note that a `watch_metadata` call can terminate for many reasons (even
554    /// before the specified [`WatchParams::timeout`] is triggered), and will
555    /// have to be re-issued with the last seen resource version when or if it
556    /// closes.
557    ///
558    /// Consider using a managed [`metadata_watcher`] to deal with automatic re-watches and error cases.
559    ///
560    /// ```no_run
561    /// use kube::api::{Api, WatchParams, ResourceExt, WatchEvent};
562    /// use k8s_openapi::api::batch::v1::Job;
563    /// use futures::{StreamExt, TryStreamExt};
564    ///
565    /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
566    /// # let client: kube::Client = todo!();
567    /// let jobs: Api<Job> = Api::namespaced(client, "apps");
568    ///
569    /// let lp = WatchParams::default()
570    ///     .fields("metadata.name=my_job")
571    ///     .timeout(20); // upper bound of how long we watch for
572    /// let mut stream = jobs.watch(&lp, "0").await?.boxed();
573    /// while let Some(status) = stream.try_next().await? {
574    ///     match status {
575    ///         WatchEvent::Added(s) => println!("Added {}", s.metadata.name.unwrap()),
576    ///         WatchEvent::Modified(s) => println!("Modified: {}", s.metadata.name.unwrap()),
577    ///         WatchEvent::Deleted(s) => println!("Deleted {}", s.metadata.name.unwrap()),
578    ///         WatchEvent::Bookmark(s) => {},
579    ///         WatchEvent::Error(s) => println!("{}", s),
580    ///     }
581    /// }
582    /// # Ok(())
583    /// # }
584    /// ```
585    /// [`WatchParams::timeout`]: super::WatchParams::timeout
586    /// [`metadata_watcher`]: https://docs.rs/kube_runtime/*/kube_runtime/watcher/fn.metadata_watcher.html
587    pub async fn watch_metadata(
588        &self,
589        wp: &WatchParams,
590        version: &str,
591    ) -> Result<impl Stream<Item = Result<WatchEvent<PartialObjectMeta<K>>>>> {
592        let mut req = self
593            .request
594            .watch_metadata(wp, version)
595            .map_err(Error::BuildRequest)?;
596        req.extensions_mut().insert("watch_metadata");
597        self.client.request_events::<PartialObjectMeta<K>>(req).await
598    }
599}