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}