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}