kube_client/api/subresource.rs
1use futures::AsyncBufRead;
2use serde::{Serialize, de::DeserializeOwned};
3use std::fmt::Debug;
4
5use crate::{
6 Error, Result,
7 api::{Api, Patch, PatchParams, PostParams},
8};
9
10use kube_core::response::Status;
11pub use kube_core::subresource::{EvictParams, LogParams};
12
13#[cfg(feature = "ws")]
14#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
15pub use kube_core::subresource::AttachParams;
16
17pub use k8s_openapi::api::autoscaling::v1::{Scale, ScaleSpec, ScaleStatus};
18
19#[cfg(feature = "ws")] use crate::api::portforward::Portforwarder;
20#[cfg(feature = "ws")] use crate::api::remote_command::AttachedProcess;
21
22/// Methods for [scale subresource](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#scale-subresource).
23impl<K> Api<K>
24where
25 K: Clone + DeserializeOwned,
26{
27 /// Fetch the scale subresource
28 pub async fn get_scale(&self, name: &str) -> Result<Scale> {
29 let mut req = self
30 .request
31 .get_subresource("scale", name)
32 .map_err(Error::BuildRequest)?;
33 req.extensions_mut().insert("get_scale");
34 self.client.request::<Scale>(req).await
35 }
36
37 /// Update the scale subresource
38 pub async fn patch_scale<P: serde::Serialize + Debug>(
39 &self,
40 name: &str,
41 pp: &PatchParams,
42 patch: &Patch<P>,
43 ) -> Result<Scale> {
44 let mut req = self
45 .request
46 .patch_subresource("scale", name, pp, patch)
47 .map_err(Error::BuildRequest)?;
48 req.extensions_mut().insert("patch_scale");
49 self.client.request::<Scale>(req).await
50 }
51
52 /// Replace the scale subresource
53 pub async fn replace_scale(&self, name: &str, pp: &PostParams, data: &Scale) -> Result<Scale> {
54 let mut req = self
55 .request
56 .replace_subresource(
57 "scale",
58 name,
59 pp,
60 serde_json::to_vec(data).map_err(Error::SerdeError)?,
61 )
62 .map_err(Error::BuildRequest)?;
63 req.extensions_mut().insert("replace_scale");
64 self.client.request::<Scale>(req).await
65 }
66}
67
68/// Arbitrary subresources
69impl<K> Api<K>
70where
71 K: Clone + DeserializeOwned + Debug,
72{
73 /// Display one or many sub-resources.
74 pub async fn get_subresource(&self, subresource_name: &str, name: &str) -> Result<K> {
75 let mut req = self
76 .request
77 .get_subresource(subresource_name, name)
78 .map_err(Error::BuildRequest)?;
79 req.extensions_mut().insert("get_subresource");
80 self.client.request::<K>(req).await
81 }
82
83 /// Create an instance of the subresource
84 pub async fn create_subresource<I, T>(
85 &self,
86 subresource_name: &str,
87 name: &str,
88 pp: &PostParams,
89 data: &I,
90 ) -> Result<T>
91 where
92 I: Serialize,
93 T: DeserializeOwned,
94 {
95 let mut req = self
96 .request
97 .create_subresource(
98 subresource_name,
99 name,
100 pp,
101 serde_json::to_vec(data).map_err(Error::SerdeError)?,
102 )
103 .map_err(Error::BuildRequest)?;
104 req.extensions_mut().insert("create_subresource");
105 self.client.request::<T>(req).await
106 }
107
108 /// Patch an instance of the subresource
109 pub async fn patch_subresource<P: serde::Serialize + Debug>(
110 &self,
111 subresource_name: &str,
112 name: &str,
113 pp: &PatchParams,
114 patch: &Patch<P>,
115 ) -> Result<K> {
116 let mut req = self
117 .request
118 .patch_subresource(subresource_name, name, pp, patch)
119 .map_err(Error::BuildRequest)?;
120 req.extensions_mut().insert("patch_subresource");
121 self.client.request::<K>(req).await
122 }
123
124 /// Replace an instance of the subresource
125 pub async fn replace_subresource<I>(
126 &self,
127 subresource_name: &str,
128 name: &str,
129 pp: &PostParams,
130 data: &I,
131 ) -> Result<K>
132 where
133 I: Serialize,
134 {
135 let mut req = self
136 .request
137 .replace_subresource(
138 subresource_name,
139 name,
140 pp,
141 serde_json::to_vec(data).map_err(Error::SerdeError)?,
142 )
143 .map_err(Error::BuildRequest)?;
144 req.extensions_mut().insert("replace_subresource");
145 self.client.request::<K>(req).await
146 }
147}
148
149// ----------------------------------------------------------------------------
150// Ephemeral containers
151// ----------------------------------------------------------------------------
152
153/// Marker trait for objects that support the ephemeral containers sub resource.
154///
155/// See [`Api::get_ephemeral_containers`] et al.
156pub trait Ephemeral {}
157
158impl Ephemeral for k8s_openapi::api::core::v1::Pod {}
159
160impl<K> Api<K>
161where
162 K: Clone + DeserializeOwned + Ephemeral,
163{
164 /// Replace the ephemeral containers sub resource entirely.
165 ///
166 /// This functions in the same way as [`Api::replace`] except only `.spec.ephemeralcontainers` is replaced, everything else is ignored.
167 ///
168 /// Note that ephemeral containers may **not** be changed or removed once attached to a pod.
169 ///
170 ///
171 /// You way want to patch the underlying resource to gain access to the main container process,
172 /// see the [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) for `sharedProcessNamespace`.
173 ///
174 /// See the Kubernetes [documentation](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/#what-is-an-ephemeral-container) for more details.
175 ///
176 /// [`Api::patch_ephemeral_containers`] may be more ergonomic, as you can will avoid having to first fetch the
177 /// existing subresources with an approriate merge strategy, see the examples for more details.
178 ///
179 /// Example of using `replace_ephemeral_containers`:
180 ///
181 /// ```no_run
182 /// use k8s_openapi::api::core::v1::Pod;
183 /// use kube::{Api, api::PostParams};
184 /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
185 /// # let client = kube::Client::try_default().await?;
186 /// let pods: Api<Pod> = Api::namespaced(client, "apps");
187 /// let pp = PostParams::default();
188 ///
189 /// // Get pod object with ephemeral containers.
190 /// let mut mypod = pods.get_ephemeral_containers("mypod").await?;
191 ///
192 /// // If there were existing ephemeral containers, we would have to append
193 /// // new containers to the list before calling replace_ephemeral_containers.
194 /// assert_eq!(mypod.spec.as_mut().unwrap().ephemeral_containers, None);
195 ///
196 /// // Add an ephemeral container to the pod object.
197 /// mypod.spec.as_mut().unwrap().ephemeral_containers = Some(serde_json::from_value(serde_json::json!([
198 /// {
199 /// "name": "myephemeralcontainer",
200 /// "image": "busybox:1.34.1",
201 /// "command": ["sh", "-c", "sleep 20"],
202 /// },
203 /// ]))?);
204 ///
205 /// pods.replace_ephemeral_containers("mypod", &pp, &mypod).await?;
206 ///
207 /// # Ok(())
208 /// # }
209 /// ```
210 pub async fn replace_ephemeral_containers(&self, name: &str, pp: &PostParams, data: &K) -> Result<K>
211 where
212 K: Serialize,
213 {
214 let mut req = self
215 .request
216 .replace_subresource(
217 "ephemeralcontainers",
218 name,
219 pp,
220 serde_json::to_vec(data).map_err(Error::SerdeError)?,
221 )
222 .map_err(Error::BuildRequest)?;
223 req.extensions_mut().insert("replace_ephemeralcontainers");
224 self.client.request::<K>(req).await
225 }
226
227 /// Patch the ephemeral containers sub resource
228 ///
229 /// Any partial object containing the ephemeral containers
230 /// sub resource is valid as long as the complete structure
231 /// for the object is present, as shown below.
232 ///
233 /// You way want to patch the underlying resource to gain access to the main container process,
234 /// see the [docs](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) for `sharedProcessNamespace`.
235 ///
236 /// Ephemeral containers may **not** be changed or removed once attached to a pod.
237 /// Therefore if the chosen merge strategy overwrites the existing ephemeral containers,
238 /// you will have to fetch the existing ephemeral containers first.
239 /// In order to append your new ephemeral containers to the existing list before patching. See some examples and
240 /// discussion related to merge strategies in Kubernetes
241 /// [here](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment). The example below uses a strategic merge patch which does not require
242 ///
243 /// See the `Kubernetes` [documentation](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/)
244 /// for more information about ephemeral containers.
245 ///
246 ///
247 /// Example of using `patch_ephemeral_containers`:
248 ///
249 /// ```no_run
250 /// use kube::api::{Api, PatchParams, Patch};
251 /// use k8s_openapi::api::core::v1::Pod;
252 /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
253 /// # let client = kube::Client::try_default().await?;
254 /// let pods: Api<Pod> = Api::namespaced(client, "apps");
255 /// let pp = PatchParams::default(); // stratetgic merge patch
256 ///
257 /// // Note that the strategic merge patch will concatenate the
258 /// // lists of ephemeral containers so we avoid having to fetch the
259 /// // current list and append to it manually.
260 /// let patch = serde_json::json!({
261 /// "spec":{
262 /// "ephemeralContainers": [
263 /// {
264 /// "name": "myephemeralcontainer",
265 /// "image": "busybox:1.34.1",
266 /// "command": ["sh", "-c", "sleep 20"],
267 /// },
268 /// ]
269 /// }});
270 ///
271 /// pods.patch_ephemeral_containers("mypod", &pp, &Patch::Strategic(patch)).await?;
272 ///
273 /// # Ok(())
274 /// # }
275 /// ```
276 pub async fn patch_ephemeral_containers<P: serde::Serialize>(
277 &self,
278 name: &str,
279 pp: &PatchParams,
280 patch: &Patch<P>,
281 ) -> Result<K> {
282 let mut req = self
283 .request
284 .patch_subresource("ephemeralcontainers", name, pp, patch)
285 .map_err(Error::BuildRequest)?;
286
287 req.extensions_mut().insert("patch_ephemeralcontainers");
288 self.client.request::<K>(req).await
289 }
290
291 /// Get the named resource with the ephemeral containers subresource.
292 ///
293 /// This returns the whole K, with metadata and spec.
294 pub async fn get_ephemeral_containers(&self, name: &str) -> Result<K> {
295 let mut req = self
296 .request
297 .get_subresource("ephemeralcontainers", name)
298 .map_err(Error::BuildRequest)?;
299
300 req.extensions_mut().insert("get_ephemeralcontainers");
301 self.client.request::<K>(req).await
302 }
303}
304
305// ----------------------------------------------------------------------------
306// Resize subresource
307// ----------------------------------------------------------------------------
308
309/// Marker trait for objects that support the resize sub resource.
310///
311/// The resize subresource allows updating container resource requests/limits
312/// without restarting the pod. This is available in Kubernetes 1.33+.
313///
314/// See [`Api::get_resize`], [`Api::patch_resize`], and [`Api::replace_resize`].
315///
316/// See the Kubernetes [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/)
317/// and [limitations](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/#limitations)
318/// for more details.
319#[cfg_attr(docsrs, doc(cfg(feature = "k8s_if_ge_1_33")))]
320pub trait Resize {}
321
322k8s_openapi::k8s_if_ge_1_33! {
323 impl Resize for k8s_openapi::api::core::v1::Pod {}
324}
325
326k8s_openapi::k8s_if_ge_1_33! {
327 impl<K> Api<K>
328 where
329 K: Clone + DeserializeOwned + Resize,
330 {
331 /// Get the named resource with the resize subresource.
332 ///
333 /// This returns the whole Pod object with current resource allocations.
334 ///
335 /// See the Kubernetes [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/)
336 /// and [limitations](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/#limitations)
337 /// for more details.
338 pub async fn get_resize(&self, name: &str) -> Result<K> {
339 let mut req = self
340 .request
341 .get_subresource("resize", name)
342 .map_err(Error::BuildRequest)?;
343 req.extensions_mut().insert("get_resize");
344 self.client.request::<K>(req).await
345 }
346
347 /// Patch the resize sub resource.
348 ///
349 /// This allows you to update specific container resource requirements
350 /// without fetching the entire Pod object first.
351 ///
352 /// Note that only certain container resource fields can be modified. See the
353 /// [limitations](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/#limitations)
354 /// for details on what can be changed.
355 ///
356 /// # Example
357 ///
358 /// ```no_run
359 /// use kube::api::{Api, PatchParams, Patch};
360 /// use k8s_openapi::api::core::v1::Pod;
361 /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
362 /// # let client = kube::Client::try_default().await?;
363 /// let pods: Api<Pod> = Api::namespaced(client, "default");
364 /// let pp = PatchParams::default();
365 ///
366 /// let patch = serde_json::json!({
367 /// "spec": {
368 /// "containers": [{
369 /// "name": "mycontainer",
370 /// "resources": {
371 /// "requests": {
372 /// "cpu": "200m",
373 /// "memory": "512Mi"
374 /// }
375 /// }
376 /// }]
377 /// }
378 /// });
379 ///
380 /// pods.patch_resize("mypod", &pp, &Patch::Strategic(patch)).await?;
381 /// # Ok(())
382 /// # }
383 /// ```
384 pub async fn patch_resize<P: serde::Serialize>(
385 &self,
386 name: &str,
387 pp: &PatchParams,
388 patch: &Patch<P>,
389 ) -> Result<K> {
390 let mut req = self
391 .request
392 .patch_subresource("resize", name, pp, patch)
393 .map_err(Error::BuildRequest)?;
394 req.extensions_mut().insert("patch_resize");
395 self.client.request::<K>(req).await
396 }
397
398 /// Replace the resize sub resource entirely.
399 ///
400 /// This works similarly to [`Api::replace`] but uses the resize subresource.
401 /// Takes a full Pod object with updated container resource requirements.
402 ///
403 /// Note that only certain container resource fields can be modified. See the
404 /// [limitations](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/#limitations)
405 /// for details on what can be changed.
406 ///
407 /// # Example
408 ///
409 /// ```no_run
410 /// use k8s_openapi::api::core::v1::Pod;
411 /// use kube::{Api, api::PostParams};
412 /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
413 /// # let client = kube::Client::try_default().await?;
414 /// let pods: Api<Pod> = Api::namespaced(client, "default");
415 /// let pp = PostParams::default();
416 ///
417 /// // Get current pod
418 /// let mut pod = pods.get("mypod").await?;
419 ///
420 /// // Modify resource requirements
421 /// if let Some(spec) = &mut pod.spec &&
422 /// let Some(container) = spec.containers.get_mut(0) &&
423 /// let Some(resources) = &mut container.resources {
424 /// // Update CPU/memory limits or requests
425 /// // ...
426 /// }
427 ///
428 /// pods.replace_resize("mypod", &pp, &pod).await?;
429 /// # Ok(())
430 /// # }
431 /// ```
432 pub async fn replace_resize(&self, name: &str, pp: &PostParams, data: &K) -> Result<K>
433 where
434 K: Serialize,
435 {
436 let mut req = self
437 .request
438 .replace_subresource(
439 "resize",
440 name,
441 pp,
442 serde_json::to_vec(data).map_err(Error::SerdeError)?,
443 )
444 .map_err(Error::BuildRequest)?;
445 req.extensions_mut().insert("replace_resize");
446 self.client.request::<K>(req).await
447 }
448 }
449}
450
451// ----------------------------------------------------------------------------
452
453// TODO: Replace examples with owned custom resources. Bad practice to write to owned objects
454// These examples work, but the job controller will totally overwrite what we do.
455/// Methods for [status subresource](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#status-subresource).
456impl<K> Api<K>
457where
458 K: DeserializeOwned,
459{
460 /// Get the named resource with a status subresource
461 ///
462 /// This actually returns the whole K, with metadata, and spec.
463 pub async fn get_status(&self, name: &str) -> Result<K> {
464 let mut req = self
465 .request
466 .get_subresource("status", name)
467 .map_err(Error::BuildRequest)?;
468 req.extensions_mut().insert("get_status");
469 self.client.request::<K>(req).await
470 }
471
472 /// Patch fields on the status object
473 ///
474 /// NB: Requires that the resource has a status subresource.
475 ///
476 /// ```no_run
477 /// use kube::api::{Api, PatchParams, Patch};
478 /// use k8s_openapi::api::batch::v1::Job;
479 /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
480 /// # let client = kube::Client::try_default().await?;
481 /// let jobs: Api<Job> = Api::namespaced(client, "apps");
482 /// let mut j = jobs.get("baz").await?;
483 /// let pp = PatchParams::default(); // json merge patch
484 /// let data = serde_json::json!({
485 /// "status": {
486 /// "succeeded": 2
487 /// }
488 /// });
489 /// let o = jobs.patch_status("baz", &pp, &Patch::Merge(data)).await?;
490 /// assert_eq!(o.status.unwrap().succeeded, Some(2));
491 /// # Ok(())
492 /// # }
493 /// ```
494 pub async fn patch_status<P: serde::Serialize + Debug>(
495 &self,
496 name: &str,
497 pp: &PatchParams,
498 patch: &Patch<P>,
499 ) -> Result<K> {
500 let mut req = self
501 .request
502 .patch_subresource("status", name, pp, patch)
503 .map_err(Error::BuildRequest)?;
504 req.extensions_mut().insert("patch_status");
505 self.client.request::<K>(req).await
506 }
507
508 /// Replace every field on the status object
509 ///
510 /// This works similarly to the [`Api::replace`] method, but `.spec` is ignored.
511 /// You can leave out the `.spec` entirely from the serialized output.
512 ///
513 /// ```no_run
514 /// use kube::api::{Api, PostParams};
515 /// use k8s_openapi::api::batch::v1::{Job, JobStatus};
516 /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
517 /// # let client = kube::Client::try_default().await?;
518 /// let jobs: Api<Job> = Api::namespaced(client, "apps");
519 /// let mut o = jobs.get_status("baz").await?; // retrieve partial object
520 /// o.status = Some(JobStatus::default()); // update the job part
521 /// let pp = PostParams::default();
522 /// let o = jobs.replace_status("baz", &pp, &o).await?;
523 /// # Ok(())
524 /// # }
525 /// ```
526 pub async fn replace_status(&self, name: &str, pp: &PostParams, data: &K) -> Result<K>
527 where
528 K: Serialize,
529 {
530 let mut req = self
531 .request
532 .replace_subresource(
533 "status",
534 name,
535 pp,
536 serde_json::to_vec(data).map_err(Error::SerdeError)?,
537 )
538 .map_err(Error::BuildRequest)?;
539 req.extensions_mut().insert("replace_status");
540 self.client.request::<K>(req).await
541 }
542}
543
544// ----------------------------------------------------------------------------
545// Log subresource
546// ----------------------------------------------------------------------------
547
548#[test]
549fn log_path() {
550 use crate::api::{Request, Resource};
551 use k8s_openapi::api::core::v1 as corev1;
552 let lp = LogParams {
553 container: Some("blah".into()),
554 ..LogParams::default()
555 };
556 let url = corev1::Pod::url_path(&(), Some("ns"));
557 let req = Request::new(url).logs("foo", &lp).unwrap();
558 assert_eq!(req.uri(), "/api/v1/namespaces/ns/pods/foo/log?&container=blah");
559}
560
561/// Marker trait for objects that has logs
562///
563/// See [`Api::logs`] and [`Api::log_stream`] for usage.
564pub trait Log {}
565
566impl Log for k8s_openapi::api::core::v1::Pod {}
567
568impl<K> Api<K>
569where
570 K: DeserializeOwned + Log,
571{
572 /// Fetch logs as a string
573 pub async fn logs(&self, name: &str, lp: &LogParams) -> Result<String> {
574 let mut req = self.request.logs(name, lp).map_err(Error::BuildRequest)?;
575 req.extensions_mut().insert("logs");
576 self.client.request_text(req).await
577 }
578
579 /// Stream the logs via [`AsyncBufRead`].
580 ///
581 /// Log stream can be processsed using [`AsyncReadExt`](futures::AsyncReadExt)
582 /// and [`AsyncBufReadExt`](futures::AsyncBufReadExt).
583 ///
584 /// # Example
585 ///
586 /// ```no_run
587 /// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
588 /// # use k8s_openapi::api::core::v1::Pod;
589 /// # use kube::{api::{Api, LogParams}, Client};
590 /// # let client: Client = todo!();
591 /// use futures::{AsyncBufReadExt, TryStreamExt};
592 ///
593 /// let pods: Api<Pod> = Api::default_namespaced(client);
594 /// let mut logs = pods
595 /// .log_stream("my-pod", &LogParams::default()).await?
596 /// .lines();
597 ///
598 /// while let Some(line) = logs.try_next().await? {
599 /// println!("{}", line);
600 /// }
601 /// # Ok(())
602 /// # }
603 /// ```
604 pub async fn log_stream(&self, name: &str, lp: &LogParams) -> Result<impl AsyncBufRead + use<K>> {
605 let mut req = self.request.logs(name, lp).map_err(Error::BuildRequest)?;
606 req.extensions_mut().insert("log_stream");
607 self.client.request_stream(req).await
608 }
609}
610
611// ----------------------------------------------------------------------------
612// Eviction subresource
613// ----------------------------------------------------------------------------
614
615#[test]
616fn evict_path() {
617 use crate::api::{Request, Resource};
618 use k8s_openapi::api::core::v1 as corev1;
619 let ep = EvictParams::default();
620 let url = corev1::Pod::url_path(&(), Some("ns"));
621 let req = Request::new(url).evict("foo", &ep).unwrap();
622 assert_eq!(req.uri(), "/api/v1/namespaces/ns/pods/foo/eviction?");
623}
624
625/// Marker trait for objects that can be evicted
626///
627/// See [`Api::evic`] for usage
628pub trait Evict {}
629
630impl Evict for k8s_openapi::api::core::v1::Pod {}
631
632impl<K> Api<K>
633where
634 K: DeserializeOwned + Evict,
635{
636 /// Create an eviction
637 pub async fn evict(&self, name: &str, ep: &EvictParams) -> Result<Status> {
638 let mut req = self.request.evict(name, ep).map_err(Error::BuildRequest)?;
639 req.extensions_mut().insert("evict");
640 self.client.request::<Status>(req).await
641 }
642}
643
644// ----------------------------------------------------------------------------
645// Attach subresource
646// ----------------------------------------------------------------------------
647
648#[cfg(feature = "ws")]
649#[test]
650fn attach_path() {
651 use crate::api::{Request, Resource};
652 use k8s_openapi::api::core::v1 as corev1;
653 let ap = AttachParams {
654 container: Some("blah".into()),
655 ..AttachParams::default()
656 };
657 let url = corev1::Pod::url_path(&(), Some("ns"));
658 let req = Request::new(url).attach("foo", &ap).unwrap();
659 assert_eq!(
660 req.uri(),
661 "/api/v1/namespaces/ns/pods/foo/attach?&stdout=true&stderr=true&container=blah"
662 );
663}
664
665/// Marker trait for objects that has attach
666///
667/// See [`Api::attach`] for usage
668#[cfg(feature = "ws")]
669#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
670pub trait Attach {}
671
672#[cfg(feature = "ws")]
673#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
674impl Attach for k8s_openapi::api::core::v1::Pod {}
675
676#[cfg(feature = "ws")]
677#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
678impl<K> Api<K>
679where
680 K: Clone + DeserializeOwned + Attach,
681{
682 /// Attach to pod
683 pub async fn attach(&self, name: &str, ap: &AttachParams) -> Result<AttachedProcess> {
684 let mut req = self.request.attach(name, ap).map_err(Error::BuildRequest)?;
685 req.extensions_mut().insert("attach");
686 let stream = self.client.connect(req).await?;
687 Ok(AttachedProcess::new(stream, ap))
688 }
689}
690
691// ----------------------------------------------------------------------------
692// Exec subresource
693// ----------------------------------------------------------------------------
694#[cfg(feature = "ws")]
695#[test]
696fn exec_path() {
697 use crate::api::{Request, Resource};
698 use k8s_openapi::api::core::v1 as corev1;
699 let ap = AttachParams {
700 container: Some("blah".into()),
701 ..AttachParams::default()
702 };
703 let url = corev1::Pod::url_path(&(), Some("ns"));
704 let req = Request::new(url)
705 .exec("foo", vec!["echo", "foo", "bar"], &ap)
706 .unwrap();
707 assert_eq!(
708 req.uri(),
709 "/api/v1/namespaces/ns/pods/foo/exec?&stdout=true&stderr=true&container=blah&command=echo&command=foo&command=bar"
710 );
711}
712
713/// Marker trait for objects that has exec
714///
715/// See [`Api::exec`] for usage.
716#[cfg(feature = "ws")]
717#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
718pub trait Execute {}
719
720#[cfg(feature = "ws")]
721#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
722impl Execute for k8s_openapi::api::core::v1::Pod {}
723
724#[cfg(feature = "ws")]
725#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
726impl<K> Api<K>
727where
728 K: Clone + DeserializeOwned + Execute,
729{
730 /// Execute a command in a pod
731 pub async fn exec<I, T>(&self, name: &str, command: I, ap: &AttachParams) -> Result<AttachedProcess>
732 where
733 I: IntoIterator<Item = T> + Debug,
734 T: Into<String>,
735 {
736 let mut req = self
737 .request
738 .exec(name, command, ap)
739 .map_err(Error::BuildRequest)?;
740 req.extensions_mut().insert("exec");
741 let stream = self.client.connect(req).await?;
742 Ok(AttachedProcess::new(stream, ap))
743 }
744}
745
746// ----------------------------------------------------------------------------
747// Portforward subresource
748// ----------------------------------------------------------------------------
749#[cfg(feature = "ws")]
750#[test]
751fn portforward_path() {
752 use crate::api::{Request, Resource};
753 use k8s_openapi::api::core::v1 as corev1;
754 let url = corev1::Pod::url_path(&(), Some("ns"));
755 let req = Request::new(url).portforward("foo", &[80, 1234]).unwrap();
756 assert_eq!(
757 req.uri(),
758 "/api/v1/namespaces/ns/pods/foo/portforward?&ports=80%2C1234"
759 );
760}
761
762/// Marker trait for objects that has portforward
763///
764/// See [`Api::portforward`] for usage.
765#[cfg(feature = "ws")]
766pub trait Portforward {}
767
768#[cfg(feature = "ws")]
769impl Portforward for k8s_openapi::api::core::v1::Pod {}
770
771#[cfg(feature = "ws")]
772impl<K> Api<K>
773where
774 K: Clone + DeserializeOwned + Portforward,
775{
776 /// Forward ports of a pod
777 pub async fn portforward(&self, name: &str, ports: &[u16]) -> Result<Portforwarder> {
778 let req = self
779 .request
780 .portforward(name, ports)
781 .map_err(Error::BuildRequest)?;
782 let connection = self.client.connect(req).await?;
783 Ok(Portforwarder::new(connection.into_stream(), ports))
784 }
785}
786
787// ----------------------------------------------------------------------------
788// Resize subresource tests
789// ----------------------------------------------------------------------------
790
791#[test]
792fn resize_path() {
793 use crate::api::{Request, Resource};
794 use k8s_openapi::api::core::v1 as corev1;
795
796 k8s_openapi::k8s_if_ge_1_33! {
797 let url = corev1::Pod::url_path(&(), Some("ns"));
798 let req = Request::new(url).get_subresource("resize", "mypod").unwrap();
799 assert_eq!(req.uri(), "/api/v1/namespaces/ns/pods/mypod/resize");
800 }
801}