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    /// Whether requests should use metadata-only Accept headers
58    /// (cached from `K::metadata_api()` at construction so that `impl<K> Api<K>`
59    /// method blocks don't have to tighten to `K: Resource`).
60    pub(crate) metadata_api: bool,
61    /// Note: Using `iter::Empty` over `PhantomData`, because we never actually keep any
62    /// `K` objects, so `Empty` better models our constraints (in particular, `Empty<K>`
63    /// is `Send`, even if `K` may not be).
64    pub(crate) _phantom: std::iter::Empty<K>,
65}
66
67/// Api constructors for Resource implementors with custom DynamicTypes
68///
69/// This generally means resources created via [`DynamicObject`](crate::api::DynamicObject).
70impl<K: Resource> Api<K> {
71    /// Return a reference to the namespace of this [`Api`] instance, if any
72    pub fn namespace(&self) -> Option<&str> {
73        self.namespace.as_deref()
74    }
75
76    /// Cluster level resources, or resources viewed across all namespaces
77    ///
78    /// This function accepts `K::DynamicType` so it can be used with dynamic resources.
79    ///
80    /// # Warning
81    ///
82    /// This variant **can only `list` and `watch` namespaced resources** and is commonly used with a `watcher`.
83    /// If you need to create/patch/replace/get on a namespaced resource, you need a separate `Api::namespaced`.
84    pub fn all_with(client: Client, dyntype: &K::DynamicType) -> Self {
85        let url = K::url_path(dyntype, None);
86        Self {
87            client,
88            request: Request::new(url),
89            namespace: None,
90            metadata_api: K::metadata_api(),
91            _phantom: std::iter::empty(),
92        }
93    }
94
95    /// Namespaced resource within a given namespace
96    ///
97    /// This function accepts `K::DynamicType` so it can be used with dynamic resources.
98    pub fn namespaced_with(client: Client, ns: &str, dyntype: &K::DynamicType) -> Self
99    where
100        K: Resource<Scope = DynamicResourceScope>,
101    {
102        // TODO: inspect dyntype scope to verify somehow?
103        let url = K::url_path(dyntype, Some(ns));
104        Self {
105            client,
106            request: Request::new(url),
107            namespace: Some(ns.to_string()),
108            metadata_api: K::metadata_api(),
109            _phantom: std::iter::empty(),
110        }
111    }
112
113    /// Namespaced resource within the default namespace
114    ///
115    /// This function accepts `K::DynamicType` so it can be used with dynamic resources.
116    ///
117    /// The namespace is either configured on `context` in the kubeconfig
118    /// or falls back to `default` when running locally, and it's using the service account's
119    /// namespace when deployed in-cluster.
120    pub fn default_namespaced_with(client: Client, dyntype: &K::DynamicType) -> Self
121    where
122        K: Resource<Scope = DynamicResourceScope>,
123    {
124        let ns = client.default_namespace().to_string();
125        Self::namespaced_with(client, &ns, dyntype)
126    }
127
128    /// Consume self and return the [`Client`]
129    pub fn into_client(self) -> Client {
130        self.into()
131    }
132
133    /// Return a reference to the current resource url path
134    pub fn resource_url(&self) -> &str {
135        &self.request.url_path
136    }
137}
138
139/// Api constructors for Resource implementors with Default DynamicTypes
140///
141/// This generally means structs implementing `k8s_openapi::Resource`.
142impl<K: Resource> Api<K>
143where
144    <K as Resource>::DynamicType: Default,
145{
146    /// Cluster level resources, or resources viewed across all namespaces
147    ///
148    /// Namespace scoped resource allowing querying across all namespaces:
149    ///
150    /// ```no_run
151    /// # use kube::{Api, Client};
152    /// # let client: Client = todo!();
153    /// use k8s_openapi::api::core::v1::Pod;
154    /// let api: Api<Pod> = Api::all(client);
155    /// ```
156    ///
157    /// Cluster scoped resources also use this entrypoint:
158    ///
159    /// ```no_run
160    /// # use kube::{Api, Client};
161    /// # let client: Client = todo!();
162    /// use k8s_openapi::api::core::v1::Node;
163    /// let api: Api<Node> = Api::all(client);
164    /// ```
165    ///
166    /// # Warning
167    ///
168    /// This variant **can only `list` and `watch` namespaced resources** and is commonly used with a `watcher`.
169    /// If you need to create/patch/replace/get on a namespaced resource, you need a separate `Api::namespaced`.
170    pub fn all(client: Client) -> Self {
171        Self::all_with(client, &K::DynamicType::default())
172    }
173
174    /// Namespaced resource within a given namespace
175    ///
176    /// ```no_run
177    /// # use kube::{Api, Client};
178    /// # let client: Client = todo!();
179    /// use k8s_openapi::api::core::v1::Pod;
180    /// let api: Api<Pod> = Api::namespaced(client, "default");
181    /// ```
182    ///
183    /// This will ONLY work on namespaced resources as set by `Scope`:
184    ///
185    /// ```compile_fail
186    /// # use kube::{Api, Client};
187    /// # let client: Client = todo!();
188    /// use k8s_openapi::api::core::v1::Node;
189    /// let api: Api<Node> = Api::namespaced(client, "default"); // resource not namespaced!
190    /// ```
191    ///
192    /// For dynamic type information, use [`Api::namespaced_with`] variants.
193    pub fn namespaced(client: Client, ns: &str) -> Self
194    where
195        K: Resource<Scope = NamespaceResourceScope>,
196    {
197        let dyntype = K::DynamicType::default();
198        let url = K::url_path(&dyntype, Some(ns));
199        Self {
200            client,
201            request: Request::new(url),
202            namespace: Some(ns.to_string()),
203            metadata_api: K::metadata_api(),
204            _phantom: std::iter::empty(),
205        }
206    }
207
208    /// Namespaced resource within the default namespace
209    ///
210    /// The namespace is either configured on `context` in the kubeconfig
211    /// or falls back to `default` when running locally, and it's using the service account's
212    /// namespace when deployed in-cluster.
213    ///
214    /// ```no_run
215    /// # use kube::{Api, Client};
216    /// # let client: Client = todo!();
217    /// use k8s_openapi::api::core::v1::Pod;
218    /// let api: Api<Pod> = Api::default_namespaced(client);
219    /// ```
220    ///
221    /// This will ONLY work on namespaced resources as set by `Scope`:
222    ///
223    /// ```compile_fail
224    /// # use kube::{Api, Client};
225    /// # let client: Client = todo!();
226    /// use k8s_openapi::api::core::v1::Node;
227    /// let api: Api<Node> = Api::default_namespaced(client); // resource not namespaced!
228    /// ```
229    pub fn default_namespaced(client: Client) -> Self
230    where
231        K: Resource<Scope = NamespaceResourceScope>,
232    {
233        let ns = client.default_namespace().to_string();
234        Self::namespaced(client, &ns)
235    }
236}
237
238impl<K> From<Api<K>> for Client {
239    fn from(api: Api<K>) -> Self {
240        api.client
241    }
242}
243
244impl<K> Debug for Api<K> {
245    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246        // Intentionally destructuring, to cause compile errors when new fields are added
247        let Self {
248            request,
249            client: _,
250            namespace,
251            metadata_api: _,
252            _phantom,
253        } = self;
254        f.debug_struct("Api")
255            .field("request", &request)
256            .field("client", &"...")
257            .field("namespace", &namespace)
258            .finish()
259    }
260}
261
262/// Sanity test on scope restrictions
263#[cfg(test)]
264mod test {
265    use crate::{Api, Client, client::Body};
266    use k8s_openapi::api::core::v1 as corev1;
267
268    use http::{Request, Response};
269    use tower_test::mock;
270
271    #[tokio::test]
272    async fn scopes_should_allow_correct_interface() {
273        let (mock_service, _handle) = mock::pair::<Request<Body>, Response<Body>>();
274        let client = Client::new(mock_service, "default");
275
276        let _: Api<corev1::Node> = Api::all(client.clone());
277        let _: Api<corev1::Pod> = Api::default_namespaced(client.clone());
278        let _: Api<corev1::PersistentVolume> = Api::all(client.clone());
279        let _: Api<corev1::ConfigMap> = Api::namespaced(client, "default");
280    }
281}