diff --git a/examples/Cargo.toml b/examples/Cargo.toml index d16979159..d921d96bf 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -236,3 +236,8 @@ path = "secret_syncer.rs" name = "pod_shell_crossterm" path = "pod_shell_crossterm.rs" required-features = ["ws"] + +[[example]] +name = "pod_resize" +path = "pod_resize.rs" +required-features = ["latest"] diff --git a/examples/pod_resize.rs b/examples/pod_resize.rs new file mode 100644 index 000000000..209d7bb44 --- /dev/null +++ b/examples/pod_resize.rs @@ -0,0 +1,71 @@ +use k8s_openapi::api::core::v1::Pod; +use kube::{ + Client, Result, + api::{Api, PostParams, ResourceExt}, +}; + +#[tokio::main] +async fn main() -> Result<()> { + unsafe { + std::env::set_var("RUST_LOG", "info,kube=debug"); + } + tracing_subscriber::fmt::init(); + let client = Client::try_default().await?; + + let name = std::env::args() + .nth(1) + .expect("Usage: cargo run --bin pod_resize "); + + let pods: Api = Api::default_namespaced(client); + + // Resize is only available in Kubernetes 1.33+ + k8s_openapi::k8s_if_ge_1_33! { + tracing::info!("Resizing pod {}", name); + + // Get the current pod + let mut pod = pods.get(&name).await?; + tracing::info!("Current pod: {}", pod.name_any()); + + // Modify the pod's resource requirements + if let Some(ref mut spec) = pod.spec { + if let Some(container) = spec.containers.get_mut(0) { + // Example: Update CPU and memory limits + if container.resources.is_none() { + container.resources = Some(Default::default()); + } + if let Some(ref mut resources) = container.resources { + use k8s_openapi::apimachinery::pkg::api::resource::Quantity; + use std::collections::BTreeMap; + + // Set new resource limits + let mut limits = BTreeMap::new(); + limits.insert("cpu".to_string(), Quantity("500m".to_string())); + limits.insert("memory".to_string(), Quantity("256Mi".to_string())); + resources.limits = Some(limits); + + // Set new resource requests + let mut requests = BTreeMap::new(); + requests.insert("cpu".to_string(), Quantity("250m".to_string())); + requests.insert("memory".to_string(), Quantity("128Mi".to_string())); + resources.requests = Some(requests); + } + } + } + + // Apply the resize + let pp = PostParams::default(); + let updated_pod = pods.resize(&name, &pp, &pod).await?; + tracing::info!("Pod resized successfully: {}", updated_pod.name_any()); + + if let Some(ref spec) = updated_pod.spec { + if let Some(container) = spec.containers.get(0) { + if let Some(ref resources) = container.resources { + tracing::info!("New limits: {:?}", resources.limits); + tracing::info!("New requests: {:?}", resources.requests); + } + } + } + } + + Ok(()) +} diff --git a/kube-client/src/api/subresource.rs b/kube-client/src/api/subresource.rs index cc6b2bdf1..64887ef41 100644 --- a/kube-client/src/api/subresource.rs +++ b/kube-client/src/api/subresource.rs @@ -610,3 +610,78 @@ where Ok(Portforwarder::new(connection.into_stream(), ports)) } } + +// ---------------------------------------------------------------------------- +// Resize subresource +// ---------------------------------------------------------------------------- + +#[test] +fn resize_path() { + k8s_openapi::k8s_if_ge_1_33! { + use kube_core::{request::Request, Resource, params::PostParams}; + use k8s_openapi::api::core::v1 as corev1; + let pp = PostParams::default(); + let url = corev1::Pod::url_path(&(), Some("ns")); + let req = Request::new(url).resize("foo", vec![], &pp).unwrap(); + assert_eq!(req.uri(), "/api/v1/namespaces/ns/pods/foo/resize?"); + } +} + +/// Marker trait for objects that can be resized +/// +/// See [`Api::resize`] for usage +pub trait Resize {} + +k8s_openapi::k8s_if_ge_1_33! { + impl Resize for k8s_openapi::api::core::v1::Pod {} +} + +impl Api +where + K: DeserializeOwned + Resize, +{ + /// Resize a resource + /// + /// This works similarly to [`Api::replace`] but uses the resize subresource. + /// Takes a full Pod object to specify the new resource requirements. + /// + /// ```no_run + /// use kube::api::{Api, PostParams}; + /// use k8s_openapi::api::core::v1::Pod; + /// # async fn wrapper() -> Result<(), Box> { + /// # let client = kube::Client::try_default().await?; + /// let pods: Api = Api::namespaced(client, "apps"); + /// let mut pod = pods.get("mypod").await?; + /// + /// // Modify the pod's resource requirements + /// if let Some(ref mut spec) = pod.spec { + /// if let Some(ref mut containers) = spec.containers.first_mut() { + /// if let Some(ref mut resources) = containers.resources { + /// // Update CPU/memory limits + /// } + /// } + /// } + /// + /// let pp = PostParams::default(); + /// let resized_pod = pods.resize("mypod", &pp, &pod).await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn resize(&self, name: &str, pp: &PostParams, data: &K) -> Result + where + K: serde::Serialize, + { + let mut req = self + .request + .resize( + name, + serde_json::to_vec(data).map_err(Error::SerdeError)?, + pp, + ) + .map_err(Error::BuildRequest)?; + req.extensions_mut().insert("resize"); + self.client.request::(req).await + } +} + +// ---------------------------------------------------------------------------- diff --git a/kube-core/src/subresource.rs b/kube-core/src/subresource.rs index d4dddce4e..29fd09f05 100644 --- a/kube-core/src/subresource.rs +++ b/kube-core/src/subresource.rs @@ -3,7 +3,7 @@ use std::fmt::Debug; use crate::{ params::{DeleteParams, PostParams}, - request::{Error, Request, JSON_MIME}, + request::{Error, JSON_MIME, Request}, }; pub use k8s_openapi::api::autoscaling::v1::{Scale, ScaleSpec, ScaleStatus}; @@ -405,6 +405,28 @@ impl Request { } } +// ---------------------------------------------------------------------------- +// Resize subresource +// ---------------------------------------------------------------------------- + +impl Request { + /// Resize a pod's resources + pub fn resize( + &self, + name: &str, + data: Vec, + pp: &PostParams, + ) -> Result>, Error> { + let target = format!("{}/{}/resize?", self.url_path, name); + pp.validate()?; + let mut qp = form_urlencoded::Serializer::new(target); + pp.populate_qp(&mut qp); + let urlstr = qp.finish(); + let req = http::Request::patch(urlstr).header(http::header::CONTENT_TYPE, JSON_MIME); + req.body(data).map_err(Error::BuildRequest) + } +} + // ---------------------------------------------------------------------------- // tests // ---------------------------------------------------------------------------- @@ -434,7 +456,10 @@ mod test { timestamps: true, }; let req = Request::new(url).logs("mypod", &lp).unwrap(); - assert_eq!(req.uri(), "/api/v1/namespaces/ns/pods/mypod/log?&container=nginx&follow=true&limitBytes=10485760&pretty=true&previous=true&sinceSeconds=3600&tailLines=4096×tamps=true"); + assert_eq!( + req.uri(), + "/api/v1/namespaces/ns/pods/mypod/log?&container=nginx&follow=true&limitBytes=10485760&pretty=true&previous=true&sinceSeconds=3600&tailLines=4096×tamps=true" + ); } #[test]