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}