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