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", ¶ms, &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", ¶ms, &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}