From 24372bdeb92790d5cd56beb6189d6d5ccceabef8 Mon Sep 17 00:00:00 2001 From: clux Date: Thu, 29 Sep 2022 22:52:13 +0100 Subject: [PATCH 1/2] WIP: Core methods on `Client` Signed-off-by: clux --- kube-client/src/client/mod.rs | 1 + kube-client/src/client/request.rs | 273 ++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 kube-client/src/client/request.rs diff --git a/kube-client/src/client/mod.rs b/kube-client/src/client/mod.rs index 87bcc5259..25fd062b1 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..da335c8bc --- /dev/null +++ b/kube-client/src/client/request.rs @@ -0,0 +1,273 @@ +//! 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) +} + +// TODO: remove these i think they are not necessary +fn cluster_subresource_request() -> Request +where + K: Resource, + S: Resource, + ::DynamicType: Default, +{ + let url = K::url_path(&K::DynamicType::default(), None); + Request::new(url) +} +fn namespaced_subresource_request(ns: &Namespace) -> Request +where + K: Resource, + S: Resource, + ::DynamicType: Default, +{ + let url = K::url_path(&K::DynamicType::default(), Some(&ns.0)); + 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, + subresource_name: &str, + pp: &PostParams, + data: &K, + ) -> Result + where + K: Resource + Serialize + DeserializeOwned + Clone + Debug, + { + let bytes = serde_json::to_vec(&data).map_err(Error::SerdeError)?; + // TODO: figure out why create_subresource needs a name, but create does not + let mut req = r + .create_subresource(subresource_name, 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 DynamicResourceScope +/// These resources can be Namespaced or Cluster scoped, so we implement both methods. +impl Client { + /// Create a cluster resource + pub async fn create_with(&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_with( + &self, + ns: &Namespace, + pp: &PostParams, + data: &K, + dt: &K::DynamicType, + ) -> Result + where + K: Resource + Serialize + DeserializeOwned + Clone + Debug, + { + let request = dynamic_namespaced_request::(dt, ns); + self.create_raw(request, pp, data).await + } + + /// Delete a namespaced resource + pub async fn delete_namespaced_with( + &self, + name: &str, + ns: &Namespace, + dp: &DeleteParams, + dt: &K::DynamicType, + ) -> Result> + where + K: Resource + DeserializeOwned + Clone + Debug, + { + let request = dynamic_namespaced_request::(dt, ns); + self.delete_raw(request, name, dp).await + } +} + + +/// Methods for DynamicResourceScope +/// NB: Currently not handling Dynamically scoped subresources... +/// ...maybe this is a sign that Dynamic scopes are disjoint from scopes +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::(); + let subresource_name = S::plural(&S::DynamicType::default()).to_string(); + self.create_raw_subresource(request, &subresource_name, 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); + // this is the subresource name in subresource scope + let subresource_name = S::plural(&S::DynamicType::default()).to_string(); + self.create_raw_subresource(request, &subresource_name, name, pp, data) + .await + } +} From ab7841f617126831bbb7e9b1478ef2b04a1fa711 Mon Sep 17 00:00:00 2001 From: clux Date: Fri, 30 Sep 2022 08:11:12 +0100 Subject: [PATCH 2/2] simplify and limit subresource queries to static types Signed-off-by: clux --- kube-client/src/client/request.rs | 130 +++++++++++++----------------- kube-core/src/resource.rs | 2 + 2 files changed, 59 insertions(+), 73 deletions(-) diff --git a/kube-client/src/client/request.rs b/kube-client/src/client/request.rs index da335c8bc..645125d2e 100644 --- a/kube-client/src/client/request.rs +++ b/kube-client/src/client/request.rs @@ -60,27 +60,6 @@ where Request::new(url) } -// TODO: remove these i think they are not necessary -fn cluster_subresource_request() -> Request -where - K: Resource, - S: Resource, - ::DynamicType: Default, -{ - let url = K::url_path(&K::DynamicType::default(), None); - Request::new(url) -} -fn namespaced_subresource_request(ns: &Namespace) -> Request -where - K: Resource, - S: Resource, - ::DynamicType: Default, -{ - let url = K::url_path(&K::DynamicType::default(), Some(&ns.0)); - Request::new(url) -} - - /// Unconstrained private helpers for any Resource implementor impl Client { async fn create_raw(&self, r: Request, pp: &PostParams, data: &K) -> Result @@ -93,24 +72,25 @@ impl Client { self.request::(req).await } - async fn create_raw_subresource( + async fn create_raw_subresource( &self, r: Request, name: &str, - subresource_name: &str, pp: &PostParams, - data: &K, - ) -> Result + data: &S, + ) -> Result where - K: Resource + Serialize + DeserializeOwned + Clone + Debug, + 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)?; - // TODO: figure out why create_subresource needs a name, but create does not + let subname = S::plural(&S::DynamicType::default()).to_string(); let mut req = r - .create_subresource(subresource_name, name, pp, bytes) + .create_subresource(&subname, name, pp, bytes) .map_err(Error::BuildRequest)?; req.extensions_mut().insert("create_subresource"); - self.request::(req).await + self.request::(req).await } async fn delete_raw(&self, r: Request, name: &str, dp: &DeleteParams) -> Result> @@ -174,11 +154,50 @@ impl Client { } } +/// 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_with(&self, pp: &PostParams, data: &K, dt: &K::DynamicType) -> Result + pub async fn create_dyn(&self, pp: &PostParams, data: &K, dt: &K::DynamicType) -> Result where K: Resource + Serialize + DeserializeOwned + Clone + Debug, { @@ -201,7 +220,7 @@ impl Client { } /// Create a namespaced resource - pub async fn create_namespaced_with( + pub async fn create_namespaced_dyn( &self, ns: &Namespace, pp: &PostParams, @@ -211,12 +230,15 @@ impl Client { 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_with( + pub async fn delete_namespaced_dyn( &self, name: &str, ns: &Namespace, @@ -226,48 +248,10 @@ impl Client { 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 } } - - -/// Methods for DynamicResourceScope -/// NB: Currently not handling Dynamically scoped subresources... -/// ...maybe this is a sign that Dynamic scopes are disjoint from scopes -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::(); - let subresource_name = S::plural(&S::DynamicType::default()).to_string(); - self.create_raw_subresource(request, &subresource_name, 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); - // this is the subresource name in subresource scope - let subresource_name = S::plural(&S::DynamicType::default()).to_string(); - self.create_raw_subresource(request, &subresource_name, name, pp, data) - .await - } -} diff --git a/kube-core/src/resource.rs b/kube-core/src/resource.rs index dcec84578..d224e55f6 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