kube_client_ext/
ext2.rs

1use std::collections::BTreeMap;
2use std::fmt;
3
4use client::ResourceExt as _;
5use k8s::DeploymentGetExt as _;
6use k8s::OwnerReferenceExt as _;
7use k8s::ReplicaSetGetExt as _;
8use k8s::StatefulSetGetExt as _;
9
10use super::*;
11
12/// Async extentions to `kube::Client`
13///
14#[async_trait::async_trait]
15pub trait KubeClientExt2: KubeClientExt {
16    /// Get named configmap from a given (or default) namespace
17    /// Return `None` if not found`
18    ///
19    async fn get_configmap_opt(
20        &self,
21        name: &str,
22        namespace: impl Into<Option<&str>> + Send,
23    ) -> client::Result<Option<corev1::ConfigMap>> {
24        self.configmaps(namespace).get_opt(name).await
25    }
26
27    /// Get named configmap from a given (or default) namespace
28    ///
29    async fn get_configmap(
30        &self,
31        name: &str,
32        namespace: impl Into<Option<&str>> + Send,
33    ) -> client::Result<corev1::ConfigMap> {
34        self.configmaps(namespace).get(name).await
35    }
36
37    /// Get named secret from a given (or default) namespace
38    /// Return `None` if not found`
39    ///
40    async fn get_secret_opt(
41        &self,
42        name: &str,
43        namespace: impl Into<Option<&str>> + Send,
44    ) -> client::Result<Option<corev1::Secret>> {
45        self.secrets(namespace).get_opt(name).await
46    }
47
48    /// Get named secret from a given (or default) namespace
49    ///
50    async fn get_secret(
51        &self,
52        name: &str,
53        namespace: impl Into<Option<&str>> + Send,
54    ) -> client::Result<corev1::Secret> {
55        self.secrets(namespace).get(name).await
56    }
57
58    /// Get named deployment from a given (or default) namespace
59    /// Return `None` if not found
60    ///
61    async fn get_deployment_opt(
62        &self,
63        name: &str,
64        namespace: impl Into<Option<&str>> + Send,
65    ) -> client::Result<Option<appsv1::Deployment>> {
66        self.deployments(namespace).get_opt(name).await
67    }
68
69    /// Get named deployment from a given (or default) namespace
70    ///
71    async fn get_deployment(
72        &self,
73        name: &str,
74        namespace: impl Into<Option<&str>> + Send,
75    ) -> client::Result<appsv1::Deployment> {
76        self.deployments(namespace).get(name).await
77    }
78
79    /// Get named statefulset from a given (or default) namespace
80    /// Return `None` if not found
81    ///
82    async fn get_statefulset_opt(
83        &self,
84        name: &str,
85        namespace: impl Into<Option<&str>> + Send,
86    ) -> client::Result<Option<appsv1::StatefulSet>> {
87        self.statefulsets(namespace).get_opt(name).await
88    }
89
90    /// Get named statefulset from a given (or default) namespace
91    ///
92    async fn get_statefulset(
93        &self,
94        name: &str,
95        namespace: impl Into<Option<&str>> + Send,
96    ) -> client::Result<appsv1::StatefulSet> {
97        self.statefulsets(namespace).get(name).await
98    }
99
100    /// Get named api service
101    /// Return `None` if not found
102    ///
103    async fn get_apiservice_opt(
104        &self,
105        name: &str,
106    ) -> client::Result<Option<apiregistrationv1::APIService>> {
107        self.apiservices().get_opt(name).await
108    }
109
110    /// Get named api service
111    ///
112    async fn get_apiservice(&self, name: &str) -> client::Result<apiregistrationv1::APIService> {
113        self.apiservices().get(name).await
114    }
115
116    /// Get named CRD
117    /// Return `None` if not found
118    ///
119    async fn get_crd_opt(
120        &self,
121        name: &str,
122    ) -> client::Result<Option<apiextensionsv1::CustomResourceDefinition>> {
123        self.crds().get_opt(name).await
124    }
125
126    /// Get named CRD
127    ///
128    async fn get_crd(
129        &self,
130        name: &str,
131    ) -> client::Result<apiextensionsv1::CustomResourceDefinition> {
132        self.crds().get(name).await
133    }
134
135    /// Get owner object from `ownerReference` assuming it is of kind `K`
136    ///
137    async fn get_owner_k<O, K>(&self, o: &O) -> client::Result<Option<K>>
138    where
139        O: client::ResourceExt + Sync,
140        K: Clone
141            + fmt::Debug
142            + k8s::openapi::serde::de::DeserializeOwned
143            + client::Resource<Scope = k8s::openapi::NamespaceResourceScope>,
144        <K as client::Resource>::DynamicType: Default,
145    {
146        let dynamic_default = K::DynamicType::default();
147        let kind = K::kind(&dynamic_default);
148        let namespace = o.namespace();
149        if let Some(name) = o
150            .owner_references()
151            .iter()
152            .find(|owner| owner.kind == kind)
153            .map(|owner| &owner.name)
154        {
155            self.namespaced_k(namespace.as_deref()).get_opt(name).await
156        } else {
157            Ok(None)
158        }
159    }
160
161    /// List all `Pod`s  in a given (or default) namespace
162    ///
163    async fn list_pods(
164        &self,
165        namespace: impl Into<Option<&str>> + Send,
166    ) -> client::Result<Vec<corev1::Pod>> {
167        self.list_k(namespace).await
168    }
169
170    /// List all `Deployment`s in a given (or default) namespace
171    ///
172    async fn list_deployments(
173        &self,
174        namespace: impl Into<Option<&str>> + Send,
175    ) -> client::Result<Vec<appsv1::Deployment>> {
176        self.list_k(namespace).await
177    }
178
179    /// List all `ReplicaSets` in a given (or default) namespace
180    ///
181    async fn list_replicasets(
182        &self,
183        namespace: impl Into<Option<&str>> + Send,
184    ) -> client::Result<Vec<appsv1::ReplicaSet>> {
185        self.list_k(namespace).await
186    }
187
188    /// List all `Job`s in a given (or default) namespace
189    ///
190    async fn list_jobs(
191        &self,
192        namespace: impl Into<Option<&str>> + Send,
193    ) -> client::Result<Vec<batchv1::Job>> {
194        self.list_k(namespace).await
195    }
196
197    /// List all `CronJob`s in a given (or default) namespace
198    ///
199    async fn list_cronjobs(
200        &self,
201        namespace: impl Into<Option<&str>> + Send,
202    ) -> client::Result<Vec<batchv1::CronJob>> {
203        self.list_k(namespace).await
204    }
205
206    /// List all `Secret`s in a given (or default) namespace
207    ///
208    async fn list_secrets(
209        &self,
210        namespace: impl Into<Option<&str>> + Send,
211    ) -> client::Result<Vec<corev1::Secret>> {
212        self.list_k(namespace).await
213    }
214
215    /// List all `Service`s in a given (or default) namespace
216    ///
217    async fn list_services(
218        &self,
219        namespace: impl Into<Option<&str>> + Send,
220    ) -> client::Result<Vec<corev1::Service>> {
221        self.list_k(namespace).await
222    }
223
224    /// List all `StatefulSet`s in a given (or default) namespace
225    ///
226    async fn list_statefulsets(
227        &self,
228        namespace: impl Into<Option<&str>> + Send,
229    ) -> client::Result<Vec<appsv1::StatefulSet>> {
230        self.list_k(namespace).await
231    }
232
233    /// List all `ConfigMap`s in a given (or default) namespace
234    ///
235    async fn list_configmaps(
236        &self,
237        namespace: impl Into<Option<&str>> + Send,
238    ) -> client::Result<Vec<corev1::ConfigMap>> {
239        self.list_k(namespace).await
240    }
241
242    /// List all `ServiceAccount`s in a given (or default) namespace
243    ///
244    async fn list_serviceaccounts(
245        &self,
246        namespace: impl Into<Option<&str>> + Send,
247    ) -> client::Result<Vec<corev1::ServiceAccount>> {
248        self.list_k(namespace).await
249    }
250
251    /// List namespaced objects of kind `K` in a given (or default) namespace
252    ///
253    async fn list_k<K>(&self, namespace: impl Into<Option<&str>> + Send) -> client::Result<Vec<K>>
254    where
255        K: Clone
256            + fmt::Debug
257            + k8s::openapi::serde::de::DeserializeOwned
258            + client::Resource<Scope = k8s::openapi::NamespaceResourceScope>,
259        <K as client::Resource>::DynamicType: Default,
260    {
261        let lp = self.list_params();
262        self.namespaced_k(namespace)
263            .list(&lp)
264            .await
265            .map(|list| list.items)
266    }
267
268    /// Get all the pods associated with the deployment
269    /// The logic is based on what `kubectl describe` does
270    ///
271    async fn get_pods_by_deployment_name(
272        &self,
273        name: &str,
274        namespace: impl Into<Option<&str>> + Send,
275    ) -> client::Result<Option<Vec<corev1::Pod>>> {
276        // Get the deployment
277        let Some(deployment) = self.get_deployment_opt(name, namespace).await? else {
278            return Ok(None);
279        };
280        self.get_pods_by_deployment(&deployment).await
281    }
282
283    /// Get all the pods associated with the `deployment`
284    /// The logic is based on what `kubectl describe` does
285    ///
286    async fn get_pods_by_deployment(
287        &self,
288        deployment: &appsv1::Deployment,
289    ) -> client::Result<Option<Vec<corev1::Pod>>> {
290        let namespace = deployment.namespace();
291        // Get all its replicas
292        let mut replicasets = self
293            .list_replicasets(namespace.as_deref())
294            .await?
295            .into_iter()
296            .filter(|rs| rs.is_controlled_by(deployment))
297            .collect::<Vec<_>>();
298
299        // Find the `NewReplicaSet`
300        replicasets.sort_by_key(|rs| rs.creation_timestamp());
301        let Some(new) = replicasets
302            .iter()
303            .find(|rs| match_template_spec_no_hash(rs, deployment))
304        else {
305            return Ok(None);
306        };
307
308        // Find all the Pods controlled by this ReplicaSet
309        let pods = self
310            .list_pods(namespace.as_deref())
311            .await?
312            .into_iter()
313            .filter(|pod| pod.is_controlled_by(new))
314            .collect();
315
316        Ok(Some(pods))
317    }
318
319    /// Get all the pods controlled by a given statefulset
320    async fn get_pods_by_statefulset(
321        &self,
322        statefulset: &appsv1::StatefulSet,
323    ) -> client::Result<Option<Vec<corev1::Pod>>> {
324        let namespace = statefulset.namespace();
325        let pods = if let Some(revision) = statefulset.current_revision() {
326            let controller_revision = format!(
327                "{}={}",
328                k8s::label::CONTROLLER_REVISION_HASH_LABEL_KEY,
329                revision
330            );
331            let lp = self.list_params().labels(&controller_revision);
332            self.pods(namespace.as_deref()).list(&lp).await?.items
333        } else {
334            vec![]
335        };
336        Ok(Some(pods))
337    }
338
339    async fn get_pods_by_statefulset_name(
340        &self,
341        name: &str,
342        namespace: impl Into<Option<&str>> + Send,
343    ) -> client::Result<Option<Vec<corev1::Pod>>> {
344        let Some(statefulset) = self.get_statefulset_opt(name, namespace).await? else {
345            return Ok(None);
346        };
347        self.get_pods_by_statefulset(&statefulset).await
348    }
349}
350
351impl KubeClientExt2 for client::Client {}
352
353fn match_template_spec_no_hash(rs: &appsv1::ReplicaSet, deployment: &appsv1::Deployment) -> bool {
354    let rs_template = rs.template().map(remove_hash);
355    let deployment_template = deployment.template().map(remove_hash);
356    rs_template == deployment_template
357}
358
359fn remove_hash(template: &corev1::PodTemplateSpec) -> corev1::PodTemplateSpec {
360    let mut template = template.clone();
361    if let Some(labels) = labels_mut(&mut template) {
362        labels.remove(k8s::label::DEFAULT_DEPLOYMENT_UNIQUE_LABEL_KEY);
363    }
364    template
365}
366
367fn labels_mut(template: &mut corev1::PodTemplateSpec) -> Option<&mut BTreeMap<String, String>> {
368    template.metadata.as_mut()?.labels.as_mut()
369}