Skip to main content

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