kube_client_ext/
ext2.rs

1use std::collections::BTreeMap;
2use std::fmt;
3
4use client::ResourceExt;
5use k8s::OwnerReferenceExt;
6
7use super::*;
8
9/// Async extentions to `kube::Client`
10///
11#[async_trait::async_trait]
12pub trait KubeClientExt2: KubeClientExt {
13    /// Get named secret from a given (or default) namespace
14    /// Return `None` if not found`
15    ///
16    async fn get_secret_opt(
17        &self,
18        name: &str,
19        namespace: impl Into<Option<&str>> + Send,
20    ) -> client::Result<Option<corev1::Secret>> {
21        self.secrets(namespace).get_opt(name).await
22    }
23
24    /// Get named secret from a given (or default) namespace
25    ///
26    async fn get_secret(
27        &self,
28        name: &str,
29        namespace: impl Into<Option<&str>> + Send,
30    ) -> client::Result<corev1::Secret> {
31        self.secrets(namespace).get(name).await
32    }
33
34    /// Get named deployment from a given (or default) namespace
35    /// Return `None` if not found
36    ///
37    async fn get_deployment_opt(
38        &self,
39        name: &str,
40        namespace: impl Into<Option<&str>> + Send,
41    ) -> client::Result<Option<appsv1::Deployment>> {
42        self.deployments(namespace).get_opt(name).await
43    }
44
45    /// Get named deployment from a given (or default) namespace
46    ///
47    async fn get_deployment(
48        &self,
49        name: &str,
50        namespace: impl Into<Option<&str>> + Send,
51    ) -> client::Result<appsv1::Deployment> {
52        self.deployments(namespace).get(name).await
53    }
54
55    /// Get named api service
56    /// Return `None` if not found
57    ///
58    async fn get_apiservice_opt(
59        &self,
60        name: &str,
61    ) -> client::Result<Option<apiregistrationv1::APIService>> {
62        self.apiservices().get_opt(name).await
63    }
64
65    /// Get named api service
66    ///
67    async fn get_apiservice(&self, name: &str) -> client::Result<apiregistrationv1::APIService> {
68        self.apiservices().get(name).await
69    }
70
71    /// Get named CRD
72    /// Return `None` if not found
73    ///
74    async fn get_crd_opt(
75        &self,
76        name: &str,
77    ) -> client::Result<Option<apiextensionsv1::CustomResourceDefinition>> {
78        self.crds().get_opt(name).await
79    }
80
81    /// Get named CRD
82    ///
83    async fn get_crd(
84        &self,
85        name: &str,
86    ) -> client::Result<apiextensionsv1::CustomResourceDefinition> {
87        self.crds().get(name).await
88    }
89
90    /// Get owner object from `ownerReference` assuming it is of kind `K`
91    ///
92    async fn get_owner_k<O, K>(&self, o: &O) -> client::Result<Option<K>>
93    where
94        O: client::ResourceExt + Sync,
95        K: Clone
96            + fmt::Debug
97            + k8s::openapi::serde::de::DeserializeOwned
98            + client::Resource<Scope = k8s::openapi::NamespaceResourceScope>,
99        <K as client::Resource>::DynamicType: Default,
100    {
101        let dynamic_default = K::DynamicType::default();
102        let kind = K::kind(&dynamic_default);
103        let namespace = o.namespace();
104        if let Some(name) = o
105            .owner_references()
106            .iter()
107            .find(|owner| owner.kind == kind)
108            .map(|owner| &owner.name)
109        {
110            self.namespaced_k(namespace.as_deref()).get_opt(name).await
111        } else {
112            Ok(None)
113        }
114    }
115
116    /// List all `Pod`s  in a given (or default) namespace
117    ///
118    async fn list_pods(
119        &self,
120        namespace: impl Into<Option<&str>> + Send,
121    ) -> client::Result<Vec<corev1::Pod>> {
122        self.list_k(namespace).await
123    }
124
125    /// List all `Deployment`s in a given (or default) namespace
126    ///
127    async fn list_deployments(
128        &self,
129        namespace: impl Into<Option<&str>> + Send,
130    ) -> client::Result<Vec<appsv1::Deployment>> {
131        self.list_k(namespace).await
132    }
133
134    /// List all `ReplicaSets` in a given (or default) namespace
135    ///
136    async fn list_replicasets(
137        &self,
138        namespace: impl Into<Option<&str>> + Send,
139    ) -> client::Result<Vec<appsv1::ReplicaSet>> {
140        self.list_k(namespace).await
141    }
142
143    /// List all `Job`s in a given (or default) namespace
144    ///
145    async fn list_jobs(
146        &self,
147        namespace: impl Into<Option<&str>> + Send,
148    ) -> client::Result<Vec<batchv1::Job>> {
149        self.list_k(namespace).await
150    }
151
152    /// List all `CronJob`s in a given (or default) namespace
153    ///
154    async fn list_cronjobs(
155        &self,
156        namespace: impl Into<Option<&str>> + Send,
157    ) -> client::Result<Vec<batchv1::CronJob>> {
158        self.list_k(namespace).await
159    }
160
161    /// List namespaced objects of kind `K` in a given (or default) namespace
162    ///
163    async fn list_k<K>(&self, namespace: impl Into<Option<&str>> + Send) -> client::Result<Vec<K>>
164    where
165        K: Clone
166            + fmt::Debug
167            + k8s::openapi::serde::de::DeserializeOwned
168            + client::Resource<Scope = k8s::openapi::NamespaceResourceScope>,
169        <K as client::Resource>::DynamicType: Default,
170    {
171        let lp = self.list_params();
172        self.namespaced_k(namespace)
173            .list(&lp)
174            .await
175            .map(|list| list.items)
176    }
177
178    /// Get all the pods associated with the deployment
179    /// The logic is based on what `kubectl describe` does
180    ///
181    async fn get_pods_by_deployment_name(
182        &self,
183        name: &str,
184        namespace: impl Into<Option<&str>> + Send,
185    ) -> client::Result<Option<Vec<corev1::Pod>>> {
186        // Get the deployment
187        let Some(deployment) = self.get_deployment_opt(name, namespace).await? else {
188            return Ok(None);
189        };
190
191        self.get_pods_by_deployment(&deployment).await
192    }
193
194    /// Get all the pods associated with the `deployment`
195    /// The logic is based on what `kubectl describe` does
196    ///
197    async fn get_pods_by_deployment(
198        &self,
199        deployment: &appsv1::Deployment,
200    ) -> client::Result<Option<Vec<corev1::Pod>>> {
201        let namespace = deployment.namespace();
202        // Get all its replicas
203        let mut replicasets = self
204            .list_replicasets(namespace.as_deref())
205            .await?
206            .into_iter()
207            .filter(|rs| rs.is_controlled_by(deployment))
208            .collect::<Vec<_>>();
209
210        // Find the `NewReplicaSet`
211        replicasets.sort_by_key(|rs| rs.creation_timestamp());
212        let Some(new) = replicasets
213            .iter()
214            .find(|rs| match_template_spec_no_hash(rs, deployment))
215        else {
216            return Ok(None);
217        };
218
219        // Find all the Pods controlled by this ReplicaSet
220        let pods = self
221            .list_pods(namespace.as_deref())
222            .await?
223            .into_iter()
224            .filter(|pod| pod.is_controlled_by(new))
225            .collect();
226
227        Ok(Some(pods))
228    }
229}
230
231impl KubeClientExt2 for client::Client {}
232
233fn match_template_spec_no_hash(rs: &appsv1::ReplicaSet, deployment: &appsv1::Deployment) -> bool {
234    let rs_template = rs_pod_template(rs).map(remove_hash);
235    let deployment_template = deployment_pod_template(deployment).map(remove_hash);
236    rs_template == deployment_template
237}
238
239fn remove_hash(template: &corev1::PodTemplateSpec) -> corev1::PodTemplateSpec {
240    let mut template = template.clone();
241    if let Some(labels) = labels_mut(&mut template) {
242        labels.remove(k8s::label::DEFAULT_DEPLOYMENT_UNIQUE_LABEL_KEY);
243    }
244    template
245}
246
247fn labels_mut(template: &mut corev1::PodTemplateSpec) -> Option<&mut BTreeMap<String, String>> {
248    template.metadata.as_mut()?.labels.as_mut()
249}
250
251fn rs_pod_template(rs: &appsv1::ReplicaSet) -> Option<&corev1::PodTemplateSpec> {
252    rs.spec.as_ref()?.template.as_ref()
253}
254
255fn deployment_pod_template(deployment: &appsv1::Deployment) -> Option<&corev1::PodTemplateSpec> {
256    deployment.spec.as_ref().map(|spec| &spec.template)
257}