diff --git a/kube-client/src/client/client_ext.rs b/kube-client/src/client/client_ext.rs index ced2df626..9ed48d447 100644 --- a/kube-client/src/client/client_ext.rs +++ b/kube-client/src/client/client_ext.rs @@ -5,9 +5,10 @@ use k8s_openapi::{ }; use kube_core::{ object::ObjectList, - params::{GetParams, ListParams}, + params::{GetParams, ListParams, Patch, PatchParams}, request::Request, - ApiResource, ClusterResourceScope, DynamicResourceScope, NamespaceResourceScope, Resource, + ApiResource, ClusterResourceScope, DynamicResourceScope, NamespaceResourceScope, Resource, ResourceExt, + TypeMeta, }; use serde::{de::DeserializeOwned, Serialize}; use std::fmt::Debug; @@ -230,6 +231,18 @@ pub enum NamespaceError { MissingName, } +#[derive(Serialize, Clone, Debug)] +/// ApplyObject allows to wrap an object into Patch::Apply compatible structure, +/// with populated TypeMeta. +pub struct ApplyObject { + /// Contains the API version and type of the request. + #[serde(flatten)] + pub types: TypeMeta, + /// Contains the object data. + #[serde(flatten)] + pub data: R, +} + /// Generic client extensions for the `unstable-client` feature /// /// These methods allow users to query across a wide-array of resources without needing @@ -383,6 +396,88 @@ impl Client { req.extensions_mut().insert("list"); self.request::>(req).await } + + /// Perform apply patch on the provided `Resource` implementing type `K` + /// + /// ```no_run + /// # use k8s_openapi::api::core::v1::Pod; + /// # use k8s_openapi::api::core::v1::Service; + /// # use kube::client::scope::Namespace; + /// # use kube::prelude::*; + /// # use kube::api::{PatchParams, Patch}; + /// # async fn wrapper() -> Result<(), Box> { + /// # let client: kube::Client = todo!(); + /// let pod: Pod = client.get("some_pod", &Namespace::from("default")).await?; + /// let pp = &PatchParams::apply("controller").force(); + /// // Perform an apply patch on the resource + /// client.apply(&pod, pp).await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn apply(&self, resource: &K, pp: &PatchParams) -> Result + where + K: ResourceExt + Serialize + DeserializeOwned + Clone + Debug, + ::DynamicType: Default, + { + let meta = resource.meta(); + let name = meta.name.as_ref().ok_or(Error::NameResolve)?; + let url = K::url_path(&Default::default(), meta.namespace.as_deref()); + let req = Request::new(url); + + let apply = ApplyObject:: { + types: TypeMeta::resource::(), + data: { + let mut resource = resource.clone(); + resource.meta_mut().managed_fields = None; + resource + }, + }; + let req = req + .patch(name, pp, &Patch::Apply(apply)) + .map_err(Error::BuildRequest)?; + self.request::(req).await + } + + /// Perform apply patch on the provided `Resource` status, implementing type `K` + /// + /// ```no_run + /// # use k8s_openapi::api::core::v1::Pod; + /// # use k8s_openapi::api::core::v1::Service; + /// # use kube::client::scope::Namespace; + /// # use kube::prelude::*; + /// # use kube::api::{PatchParams, Patch}; + /// # async fn wrapper() -> Result<(), Box> { + /// # let client: kube::Client = todo!(); + /// let pod: Pod = client.get("some_pod", &Namespace::from("default")).await?; + /// let pp = &PatchParams::apply("controller").force(); + /// // Perform an apply patch on the resource status + /// client.apply_status(&pod, pp).await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn apply_status(&self, resource: &K, pp: &PatchParams) -> Result + where + K: ResourceExt + Serialize + DeserializeOwned + Clone + Debug, + ::DynamicType: Default, + { + let meta = resource.meta(); + let name = meta.name.as_ref().ok_or(Error::NameResolve)?; + let url = K::url_path(&Default::default(), meta.namespace.as_deref()); + let req = Request::new(url); + + let apply = ApplyObject:: { + types: TypeMeta::resource::(), + data: { + let mut resource = resource.clone(); + resource.meta_mut().managed_fields = None; + resource + }, + }; + let req = req + .patch_subresource("status", name, pp, &Patch::Apply(apply)) + .map_err(Error::BuildRequest)?; + self.request::(req).await + } } // Resource url_path resolver diff --git a/kube-client/src/error.rs b/kube-client/src/error.rs index 23bd2a6a9..e8cb91328 100644 --- a/kube-client/src/error.rs +++ b/kube-client/src/error.rs @@ -92,6 +92,12 @@ pub enum Error { #[cfg_attr(docsrs, doc(cfg(feature = "unstable-client")))] #[error("Reference resolve error: {0}")] RefResolve(String), + + /// Error resolving resource name + #[cfg(feature = "unstable-client")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-client")))] + #[error("Resource has no name")] + NameResolve, } #[derive(Error, Debug)] diff --git a/kube-core/src/metadata.rs b/kube-core/src/metadata.rs index 67edf6e16..300b25e1c 100644 --- a/kube-core/src/metadata.rs +++ b/kube-core/src/metadata.rs @@ -28,10 +28,10 @@ impl TypeMeta { /// assert_eq!(type_meta.kind, "PodList"); /// assert_eq!(type_meta.api_version, "v1"); /// ``` - pub fn list>() -> Self { + pub fn list>() -> Self { TypeMeta { - api_version: K::api_version(&()).into(), - kind: K::kind(&()).to_string() + "List", + api_version: K::api_version(&Default::default()).into(), + kind: K::kind(&Default::default()).to_string() + "List", } } @@ -45,10 +45,10 @@ impl TypeMeta { /// assert_eq!(type_meta.kind, "Pod"); /// assert_eq!(type_meta.api_version, "v1"); /// ``` - pub fn resource>() -> Self { + pub fn resource>() -> Self { TypeMeta { - api_version: K::api_version(&()).into(), - kind: K::kind(&()).into(), + api_version: K::api_version(&Default::default()).into(), + kind: K::kind(&Default::default()).into(), } } }