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_trait::async_trait]
15pub trait KubeClientExt2: KubeClientExt {
16 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 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 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 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 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 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 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 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 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 async fn get_apiservice(&self, name: &str) -> client::Result<apiregistrationv1::APIService> {
113 self.apiservices().get(name).await
114 }
115
116 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 async fn get_crd(
129 &self,
130 name: &str,
131 ) -> client::Result<apiextensionsv1::CustomResourceDefinition> {
132 self.crds().get(name).await
133 }
134
135 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}