kube_client/api/
mod.rs

1//! API helpers for structured interaction with the Kubernetes API
2
3mod core_methods;
4#[cfg(feature = "ws")] mod remote_command;
5use std::fmt::Debug;
6
7#[cfg(feature = "ws")] pub use remote_command::{AttachedProcess, TerminalSize};
8#[cfg(feature = "ws")] mod portforward;
9#[cfg(feature = "ws")] pub use portforward::Portforwarder;
10
11mod subresource;
12#[cfg(feature = "ws")]
13#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
14pub use subresource::{Attach, AttachParams, Ephemeral, Execute, Portforward};
15pub use subresource::{Evict, EvictParams, Log, LogParams, ScaleSpec, ScaleStatus};
16
17mod util;
18
19pub mod entry;
20
21// Re-exports from kube-core
22#[cfg(feature = "admission")]
23#[cfg_attr(docsrs, doc(cfg(feature = "admission")))]
24pub use kube_core::admission;
25pub(crate) use kube_core::params;
26pub use kube_core::{
27    dynamic::{ApiResource, DynamicObject},
28    gvk::{GroupVersionKind, GroupVersionResource},
29    metadata::{ListMeta, ObjectMeta, PartialObjectMeta, PartialObjectMetaExt, TypeMeta},
30    object::{NotUsed, Object, ObjectList},
31    request::Request,
32    watch::WatchEvent,
33    Resource, ResourceExt,
34};
35use kube_core::{DynamicResourceScope, NamespaceResourceScope};
36pub use params::{
37    DeleteParams, GetParams, ListParams, Patch, PatchParams, PostParams, Preconditions, PropagationPolicy,
38    ValidationDirective, VersionMatch, WatchParams,
39};
40
41use crate::Client;
42/// The generic Api abstraction
43///
44/// This abstracts over a [`Request`] and a type `K` so that
45/// we get automatic serialization/deserialization on the api calls
46/// implemented by the dynamic [`Resource`].
47#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
48#[derive(Clone)]
49pub struct Api<K> {
50    /// The request builder object with its resource dependent url
51    pub(crate) request: Request,
52    /// The client to use (from this library)
53    pub(crate) client: Client,
54    namespace: Option<String>,
55    /// Note: Using `iter::Empty` over `PhantomData`, because we never actually keep any
56    /// `K` objects, so `Empty` better models our constraints (in particular, `Empty<K>`
57    /// is `Send`, even if `K` may not be).
58    pub(crate) _phantom: std::iter::Empty<K>,
59}
60
61/// Api constructors for Resource implementors with custom DynamicTypes
62///
63/// This generally means resources created via [`DynamicObject`](crate::api::DynamicObject).
64impl<K: Resource> Api<K> {
65    /// Return a reference to the namespace of this [`Api`] instance, if any
66    pub fn namespace(&self) -> Option<&str> {
67        self.namespace.as_deref()
68    }
69
70    /// Cluster level resources, or resources viewed across all namespaces
71    ///
72    /// This function accepts `K::DynamicType` so it can be used with dynamic resources.
73    ///
74    /// # Warning
75    ///
76    /// This variant **can only `list` and `watch` namespaced resources** and is commonly used with a `watcher`.
77    /// If you need to create/patch/replace/get on a namespaced resource, you need a separate `Api::namespaced`.
78    pub fn all_with(client: Client, dyntype: &K::DynamicType) -> Self {
79        let url = K::url_path(dyntype, None);
80        Self {
81            client,
82            request: Request::new(url),
83            namespace: None,
84            _phantom: std::iter::empty(),
85        }
86    }
87
88    /// Namespaced resource within a given namespace
89    ///
90    /// This function accepts `K::DynamicType` so it can be used with dynamic resources.
91    pub fn namespaced_with(client: Client, ns: &str, dyntype: &K::DynamicType) -> Self
92    where
93        K: Resource<Scope = DynamicResourceScope>,
94    {
95        // TODO: inspect dyntype scope to verify somehow?
96        let url = K::url_path(dyntype, Some(ns));
97        Self {
98            client,
99            request: Request::new(url),
100            namespace: Some(ns.to_string()),
101            _phantom: std::iter::empty(),
102        }
103    }
104
105    /// Namespaced resource within the default namespace
106    ///
107    /// This function accepts `K::DynamicType` so it can be used with dynamic resources.
108    ///
109    /// The namespace is either configured on `context` in the kubeconfig
110    /// or falls back to `default` when running locally, and it's using the service account's
111    /// namespace when deployed in-cluster.
112    pub fn default_namespaced_with(client: Client, dyntype: &K::DynamicType) -> Self
113    where
114        K: Resource<Scope = DynamicResourceScope>,
115    {
116        let ns = client.default_namespace().to_string();
117        Self::namespaced_with(client, &ns, dyntype)
118    }
119
120    /// Consume self and return the [`Client`]
121    pub fn into_client(self) -> Client {
122        self.into()
123    }
124
125    /// Return a reference to the current resource url path
126    pub fn resource_url(&self) -> &str {
127        &self.request.url_path
128    }
129}
130
131/// Api constructors for Resource implementors with Default DynamicTypes
132///
133/// This generally means structs implementing `k8s_openapi::Resource`.
134impl<K: Resource> Api<K>
135where
136    <K as Resource>::DynamicType: Default,
137{
138    /// Cluster level resources, or resources viewed across all namespaces
139    ///
140    /// Namespace scoped resource allowing querying across all namespaces:
141    ///
142    /// ```no_run
143    /// # use kube::{Api, Client};
144    /// # let client: Client = todo!();
145    /// use k8s_openapi::api::core::v1::Pod;
146    /// let api: Api<Pod> = Api::all(client);
147    /// ```
148    ///
149    /// Cluster scoped resources also use this entrypoint:
150    ///
151    /// ```no_run
152    /// # use kube::{Api, Client};
153    /// # let client: Client = todo!();
154    /// use k8s_openapi::api::core::v1::Node;
155    /// let api: Api<Node> = Api::all(client);
156    /// ```
157    ///
158    /// # Warning
159    ///
160    /// This variant **can only `list` and `watch` namespaced resources** and is commonly used with a `watcher`.
161    /// If you need to create/patch/replace/get on a namespaced resource, you need a separate `Api::namespaced`.
162    pub fn all(client: Client) -> Self {
163        Self::all_with(client, &K::DynamicType::default())
164    }
165
166    /// Namespaced resource within a given namespace
167    ///
168    /// ```no_run
169    /// # use kube::{Api, Client};
170    /// # let client: Client = todo!();
171    /// use k8s_openapi::api::core::v1::Pod;
172    /// let api: Api<Pod> = Api::namespaced(client, "default");
173    /// ```
174    ///
175    /// This will ONLY work on namespaced resources as set by `Scope`:
176    ///
177    /// ```compile_fail
178    /// # use kube::{Api, Client};
179    /// # let client: Client = todo!();
180    /// use k8s_openapi::api::core::v1::Node;
181    /// let api: Api<Node> = Api::namespaced(client, "default"); // resource not namespaced!
182    /// ```
183    ///
184    /// For dynamic type information, use [`Api::namespaced_with`] variants.
185    pub fn namespaced(client: Client, ns: &str) -> Self
186    where
187        K: Resource<Scope = NamespaceResourceScope>,
188    {
189        let dyntype = K::DynamicType::default();
190        let url = K::url_path(&dyntype, Some(ns));
191        Self {
192            client,
193            request: Request::new(url),
194            namespace: Some(ns.to_string()),
195            _phantom: std::iter::empty(),
196        }
197    }
198
199    /// Namespaced resource within the default namespace
200    ///
201    /// The namespace is either configured on `context` in the kubeconfig
202    /// or falls back to `default` when running locally, and it's using the service account's
203    /// namespace when deployed in-cluster.
204    ///
205    /// ```no_run
206    /// # use kube::{Api, Client};
207    /// # let client: Client = todo!();
208    /// use k8s_openapi::api::core::v1::Pod;
209    /// let api: Api<Pod> = Api::default_namespaced(client);
210    /// ```
211    ///
212    /// This will ONLY work on namespaced resources as set by `Scope`:
213    ///
214    /// ```compile_fail
215    /// # use kube::{Api, Client};
216    /// # let client: Client = todo!();
217    /// use k8s_openapi::api::core::v1::Node;
218    /// let api: Api<Node> = Api::default_namespaced(client); // resource not namespaced!
219    /// ```
220    pub fn default_namespaced(client: Client) -> Self
221    where
222        K: Resource<Scope = NamespaceResourceScope>,
223    {
224        let ns = client.default_namespace().to_string();
225        Self::namespaced(client, &ns)
226    }
227}
228
229impl<K> From<Api<K>> for Client {
230    fn from(api: Api<K>) -> Self {
231        api.client
232    }
233}
234
235impl<K> Debug for Api<K> {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        // Intentionally destructuring, to cause compile errors when new fields are added
238        let Self {
239            request,
240            client: _,
241            namespace,
242            _phantom,
243        } = self;
244        f.debug_struct("Api")
245            .field("request", &request)
246            .field("client", &"...")
247            .field("namespace", &namespace)
248            .finish()
249    }
250}
251
252/// Sanity test on scope restrictions
253#[cfg(test)]
254mod test {
255    use crate::{client::Body, Api, Client};
256    use k8s_openapi::api::core::v1 as corev1;
257
258    use http::{Request, Response};
259    use tower_test::mock;
260
261    #[tokio::test]
262    async fn scopes_should_allow_correct_interface() {
263        let (mock_service, _handle) = mock::pair::<Request<Body>, Response<Body>>();
264        let client = Client::new(mock_service, "default");
265
266        let _: Api<corev1::Node> = Api::all(client.clone());
267        let _: Api<corev1::Pod> = Api::default_namespaced(client.clone());
268        let _: Api<corev1::PersistentVolume> = Api::all(client.clone());
269        let _: Api<corev1::ConfigMap> = Api::namespaced(client, "default");
270    }
271}