diff --git a/kube-client/src/client/mod.rs b/kube-client/src/client/mod.rs index 6f8db1686..78ca0b5ba 100644 --- a/kube-client/src/client/mod.rs +++ b/kube-client/src/client/mod.rs @@ -30,6 +30,7 @@ use crate::{api::WatchEvent, error::ErrorResponse, Config, Error, Result}; mod auth; mod body; mod builder; +#[cfg_attr(docsrs, doc(cfg(feature = "client")))] mod request; // Add `into_stream()` to `http::Body` use body::BodyStreamExt; mod config_ext; diff --git a/kube-client/src/client/request.rs b/kube-client/src/client/request.rs new file mode 100644 index 000000000..645125d2e --- /dev/null +++ b/kube-client/src/client/request.rs @@ -0,0 +1,257 @@ +//! Generic request impls on Client for Resource implementors +use crate::{Client, Error, Result}; +use either::Either; +use futures::Stream; +use serde::{de::DeserializeOwned, Serialize}; +use std::fmt::Debug; + +use kube_core::{ + dynamic::{ApiResource, DynamicObject}, + gvk::{GroupVersionKind, GroupVersionResource}, + metadata::{ListMeta, ObjectMeta, TypeMeta}, + object::{NotUsed, Object, ObjectList}, + params::{ + DeleteParams, ListParams, Patch, PatchParams, PostParams, Preconditions, PropagationPolicy, + ValidationDirective, + }, + request::Request, + response::Status, + watch::WatchEvent, + ClusterResourceScope, DynamicResourceScope, ErrorResponse, NamespaceResourceScope, Resource, ResourceExt, + SubResourceScope, +}; + +/// Newtype wrapper for Namespace +/// +/// TODO: deref and into? +pub struct Namespace(String); + + +// helper constructors for the Request object +fn namespaced_request(ns: &Namespace) -> Request +where + K: Resource, + ::DynamicType: Default, +{ + let url = K::url_path(&K::DynamicType::default(), Some(&ns.0)); + Request::new(url) +} +fn cluster_request() -> Request +where + K: Resource, + ::DynamicType: Default, +{ + let url = K::url_path(&K::DynamicType::default(), None); + Request::new(url) +} + +fn dynamic_namespaced_request(dyntype: &K::DynamicType, ns: &Namespace) -> Request +where + K: Resource, +{ + let url = K::url_path(dyntype, Some(&ns.0)); + Request::new(url) +} +fn dynamic_cluster_request(dyntype: &K::DynamicType) -> Request +where + K: Resource, +{ + let url = K::url_path(dyntype, None); + Request::new(url) +} + +/// Unconstrained private helpers for any Resource implementor +impl Client { + async fn create_raw(&self, r: Request, pp: &PostParams, data: &K) -> Result + where + K: Resource + Serialize + DeserializeOwned + Clone + Debug, + { + let bytes = serde_json::to_vec(&data).map_err(Error::SerdeError)?; + let mut req = r.create(pp, bytes).map_err(Error::BuildRequest)?; + req.extensions_mut().insert("create"); + self.request::(req).await + } + + async fn create_raw_subresource( + &self, + r: Request, + name: &str, + pp: &PostParams, + data: &S, + ) -> Result + where + K: Resource, + S: Resource + Serialize + DeserializeOwned + Clone + Debug, + S::DynamicType: Default, // limited to static queries + { + let bytes = serde_json::to_vec(&data).map_err(Error::SerdeError)?; + let subname = S::plural(&S::DynamicType::default()).to_string(); + let mut req = r + .create_subresource(&subname, name, pp, bytes) + .map_err(Error::BuildRequest)?; + req.extensions_mut().insert("create_subresource"); + self.request::(req).await + } + + async fn delete_raw(&self, r: Request, name: &str, dp: &DeleteParams) -> Result> + where + K: Resource + DeserializeOwned + Clone + Debug, + { + let mut req = r.delete(name, dp).map_err(Error::BuildRequest)?; + req.extensions_mut().insert("delete"); + self.request_status::(req).await + } +} + +/// Methods for NamespaceResourceScope Resource implementors +impl Client { + /// Create a namespaced resource + pub async fn create_namespaced(&self, ns: &Namespace, pp: &PostParams, data: &K) -> Result + where + K: Resource + Serialize + DeserializeOwned + Clone + Debug, + ::DynamicType: Default, + { + let request = namespaced_request::(ns); + self.create_raw(request, pp, data).await + } + + /// Delete a namespaced resource + pub async fn delete_namespaced( + &self, + name: &str, + ns: &Namespace, + dp: &DeleteParams, + ) -> Result> + where + K: Resource + DeserializeOwned + Clone + Debug, + ::DynamicType: Default, + { + let request = namespaced_request::(ns); + self.delete_raw(request, name, dp).await + } +} + +/// Methods for ClusterResourceScope Resource implementors +impl Client { + /// Create a cluster resource + pub async fn create(&self, pp: &PostParams, data: &K) -> Result + where + K: Resource + Serialize + DeserializeOwned + Clone + Debug, + ::DynamicType: Default, + { + let request = cluster_request::(); + self.create_raw(request, pp, data).await + } + + /// Delete a cluster resource + pub async fn delete(&self, name: &str, dp: &DeleteParams) -> Result> + where + K: Resource + DeserializeOwned + Clone + Debug, + ::DynamicType: Default, + { + let request = cluster_request::(); + self.delete_raw(request, name, dp).await + } +} + +/// Methods for SubResourceScope +/// +/// These methods are generic over two Resource types; +/// K: The root type the subresource is attached to (e.g. ServiceAccount) +/// S: The sub type sitting ontop of a resource (e.g. TokenReview) +impl Client { + /// Create an arbitrary subresource under a resource + pub async fn create_subresource(&self, name: &str, pp: &PostParams, data: &S) -> Result + where + K: Resource, + S: Resource + Serialize + DeserializeOwned + Clone + Debug, + ::DynamicType: Default, + ::DynamicType: Default, + { + let request = cluster_request::(); + self.create_raw_subresource::(request, name, pp, data).await + } + + /// Create an arbitrary namespaced subresource under a resource + pub async fn create_namespaced_subresource( + &self, + name: &str, + ns: &Namespace, + pp: &PostParams, + data: &S, + ) -> Result + where + K: Resource, + S: Resource + Serialize + DeserializeOwned + Clone + Debug, + ::DynamicType: Default, + ::DynamicType: Default, + { + let request = namespaced_request::(ns); + self.create_raw_subresource::(request, name, pp, data).await + } +} + + +/// Methods for DynamicResourceScope +/// These resources can be Namespaced or Cluster scoped, so we implement both methods. +/// NB: We do not handle Dynamically scoped subresources at the moment +impl Client { + /// Create a cluster resource + pub async fn create_dyn(&self, pp: &PostParams, data: &K, dt: &K::DynamicType) -> Result + where + K: Resource + Serialize + DeserializeOwned + Clone + Debug, + { + let request = dynamic_cluster_request::(dt); + self.create_raw(request, pp, data).await + } + + /// Delete a cluster resource + pub async fn delete_with( + &self, + name: &str, + dp: &DeleteParams, + dt: &K::DynamicType, + ) -> Result> + where + K: Resource + DeserializeOwned + Clone + Debug, + { + let request = dynamic_cluster_request::(dt); + self.delete_raw(request, name, dp).await + } + + /// Create a namespaced resource + pub async fn create_namespaced_dyn( + &self, + ns: &Namespace, + pp: &PostParams, + data: &K, + dt: &K::DynamicType, + ) -> Result + where + K: Resource + Serialize + DeserializeOwned + Clone + Debug, + { + // TODO: need to block on wrong scope at runtime via DynamicType + // but it's currently hidden in ApiCapabilities + // See https://github.com/kube-rs/kube/issues/1036 + let request = dynamic_namespaced_request::(dt, ns); + self.create_raw(request, pp, data).await + } + + /// Delete a namespaced resource + pub async fn delete_namespaced_dyn( + &self, + name: &str, + ns: &Namespace, + dp: &DeleteParams, + dt: &K::DynamicType, + ) -> Result> + where + K: Resource + DeserializeOwned + Clone + Debug, + { + // TODO: need to block on wrong scope at runtime via DynamicType + // but it's currently hidden in ApiCapabilities + // See https://github.com/kube-rs/kube/issues/1036 + let request = dynamic_namespaced_request::(dt, ns); + self.delete_raw(request, name, dp).await + } +} diff --git a/kube-core/src/resource.rs b/kube-core/src/resource.rs index 6ae3500ed..0f89d0756 100644 --- a/kube-core/src/resource.rs +++ b/kube-core/src/resource.rs @@ -59,6 +59,8 @@ pub trait Resource { /// Returns the plural name of the kind /// /// This is known as the resource in apimachinery, we rename it for disambiguation. + /// In the case where the Resource is of SubResourceScope, this is its resource name. + /// TODO: rename to resource... fn plural(dt: &Self::DynamicType) -> Cow<'_, str>; /// Creates a url path for http requests for this resource