1use std::collections::HashMap;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub struct K8sVersion {
10 pub major: u32,
11 pub minor: u32,
12}
13
14impl K8sVersion {
15 pub fn new(major: u32, minor: u32) -> Self {
16 Self { major, minor }
17 }
18
19 pub fn parse(s: &str) -> Option<Self> {
21 let s = s.trim_start_matches('v');
22 let parts: Vec<&str> = s.split('.').collect();
23 if parts.len() >= 2 {
24 let major = parts[0].parse().ok()?;
25 let minor = parts[1].parse().ok()?;
26 Some(Self { major, minor })
27 } else {
28 None
29 }
30 }
31}
32
33impl std::fmt::Display for K8sVersion {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 write!(f, "{}.{}", self.major, self.minor)
36 }
37}
38
39#[derive(Debug, Clone)]
41pub struct DeprecatedApi {
42 pub api_version: &'static str,
44 pub kind: Option<&'static str>,
46 pub replacement: &'static str,
48 pub deprecated_in: K8sVersion,
50 pub removed_in: K8sVersion,
52 pub notes: Option<&'static str>,
54}
55
56static DEPRECATED_APIS: &[DeprecatedApi] = &[
58 DeprecatedApi {
60 api_version: "extensions/v1beta1",
61 kind: Some("Deployment"),
62 replacement: "apps/v1",
63 deprecated_in: K8sVersion { major: 1, minor: 9 },
64 removed_in: K8sVersion {
65 major: 1,
66 minor: 16,
67 },
68 notes: None,
69 },
70 DeprecatedApi {
71 api_version: "extensions/v1beta1",
72 kind: Some("DaemonSet"),
73 replacement: "apps/v1",
74 deprecated_in: K8sVersion { major: 1, minor: 9 },
75 removed_in: K8sVersion {
76 major: 1,
77 minor: 16,
78 },
79 notes: None,
80 },
81 DeprecatedApi {
82 api_version: "extensions/v1beta1",
83 kind: Some("ReplicaSet"),
84 replacement: "apps/v1",
85 deprecated_in: K8sVersion { major: 1, minor: 9 },
86 removed_in: K8sVersion {
87 major: 1,
88 minor: 16,
89 },
90 notes: None,
91 },
92 DeprecatedApi {
93 api_version: "extensions/v1beta1",
94 kind: Some("Ingress"),
95 replacement: "networking.k8s.io/v1",
96 deprecated_in: K8sVersion {
97 major: 1,
98 minor: 14,
99 },
100 removed_in: K8sVersion {
101 major: 1,
102 minor: 22,
103 },
104 notes: None,
105 },
106 DeprecatedApi {
107 api_version: "extensions/v1beta1",
108 kind: Some("NetworkPolicy"),
109 replacement: "networking.k8s.io/v1",
110 deprecated_in: K8sVersion { major: 1, minor: 9 },
111 removed_in: K8sVersion {
112 major: 1,
113 minor: 16,
114 },
115 notes: None,
116 },
117 DeprecatedApi {
118 api_version: "extensions/v1beta1",
119 kind: Some("PodSecurityPolicy"),
120 replacement: "policy/v1beta1",
121 deprecated_in: K8sVersion {
122 major: 1,
123 minor: 10,
124 },
125 removed_in: K8sVersion {
126 major: 1,
127 minor: 16,
128 },
129 notes: Some("PodSecurityPolicy is deprecated entirely in 1.21 and removed in 1.25"),
130 },
131 DeprecatedApi {
133 api_version: "apps/v1beta1",
134 kind: Some("Deployment"),
135 replacement: "apps/v1",
136 deprecated_in: K8sVersion { major: 1, minor: 9 },
137 removed_in: K8sVersion {
138 major: 1,
139 minor: 16,
140 },
141 notes: None,
142 },
143 DeprecatedApi {
144 api_version: "apps/v1beta1",
145 kind: Some("StatefulSet"),
146 replacement: "apps/v1",
147 deprecated_in: K8sVersion { major: 1, minor: 9 },
148 removed_in: K8sVersion {
149 major: 1,
150 minor: 16,
151 },
152 notes: None,
153 },
154 DeprecatedApi {
156 api_version: "apps/v1beta2",
157 kind: Some("Deployment"),
158 replacement: "apps/v1",
159 deprecated_in: K8sVersion { major: 1, minor: 9 },
160 removed_in: K8sVersion {
161 major: 1,
162 minor: 16,
163 },
164 notes: None,
165 },
166 DeprecatedApi {
167 api_version: "apps/v1beta2",
168 kind: Some("DaemonSet"),
169 replacement: "apps/v1",
170 deprecated_in: K8sVersion { major: 1, minor: 9 },
171 removed_in: K8sVersion {
172 major: 1,
173 minor: 16,
174 },
175 notes: None,
176 },
177 DeprecatedApi {
178 api_version: "apps/v1beta2",
179 kind: Some("ReplicaSet"),
180 replacement: "apps/v1",
181 deprecated_in: K8sVersion { major: 1, minor: 9 },
182 removed_in: K8sVersion {
183 major: 1,
184 minor: 16,
185 },
186 notes: None,
187 },
188 DeprecatedApi {
189 api_version: "apps/v1beta2",
190 kind: Some("StatefulSet"),
191 replacement: "apps/v1",
192 deprecated_in: K8sVersion { major: 1, minor: 9 },
193 removed_in: K8sVersion {
194 major: 1,
195 minor: 16,
196 },
197 notes: None,
198 },
199 DeprecatedApi {
201 api_version: "networking.k8s.io/v1beta1",
202 kind: Some("Ingress"),
203 replacement: "networking.k8s.io/v1",
204 deprecated_in: K8sVersion {
205 major: 1,
206 minor: 19,
207 },
208 removed_in: K8sVersion {
209 major: 1,
210 minor: 22,
211 },
212 notes: None,
213 },
214 DeprecatedApi {
215 api_version: "networking.k8s.io/v1beta1",
216 kind: Some("IngressClass"),
217 replacement: "networking.k8s.io/v1",
218 deprecated_in: K8sVersion {
219 major: 1,
220 minor: 19,
221 },
222 removed_in: K8sVersion {
223 major: 1,
224 minor: 22,
225 },
226 notes: None,
227 },
228 DeprecatedApi {
230 api_version: "rbac.authorization.k8s.io/v1beta1",
231 kind: None,
232 replacement: "rbac.authorization.k8s.io/v1",
233 deprecated_in: K8sVersion {
234 major: 1,
235 minor: 17,
236 },
237 removed_in: K8sVersion {
238 major: 1,
239 minor: 22,
240 },
241 notes: Some("Applies to Role, ClusterRole, RoleBinding, ClusterRoleBinding"),
242 },
243 DeprecatedApi {
245 api_version: "admissionregistration.k8s.io/v1beta1",
246 kind: None,
247 replacement: "admissionregistration.k8s.io/v1",
248 deprecated_in: K8sVersion {
249 major: 1,
250 minor: 16,
251 },
252 removed_in: K8sVersion {
253 major: 1,
254 minor: 22,
255 },
256 notes: Some("Applies to MutatingWebhookConfiguration, ValidatingWebhookConfiguration"),
257 },
258 DeprecatedApi {
260 api_version: "apiextensions.k8s.io/v1beta1",
261 kind: Some("CustomResourceDefinition"),
262 replacement: "apiextensions.k8s.io/v1",
263 deprecated_in: K8sVersion {
264 major: 1,
265 minor: 16,
266 },
267 removed_in: K8sVersion {
268 major: 1,
269 minor: 22,
270 },
271 notes: None,
272 },
273 DeprecatedApi {
275 api_version: "policy/v1beta1",
276 kind: Some("PodDisruptionBudget"),
277 replacement: "policy/v1",
278 deprecated_in: K8sVersion {
279 major: 1,
280 minor: 21,
281 },
282 removed_in: K8sVersion {
283 major: 1,
284 minor: 25,
285 },
286 notes: None,
287 },
288 DeprecatedApi {
289 api_version: "policy/v1beta1",
290 kind: Some("PodSecurityPolicy"),
291 replacement: "None (use Pod Security Admission)",
292 deprecated_in: K8sVersion {
293 major: 1,
294 minor: 21,
295 },
296 removed_in: K8sVersion {
297 major: 1,
298 minor: 25,
299 },
300 notes: Some("PodSecurityPolicy is removed. Use Pod Security Admission instead"),
301 },
302 DeprecatedApi {
304 api_version: "batch/v1beta1",
305 kind: Some("CronJob"),
306 replacement: "batch/v1",
307 deprecated_in: K8sVersion {
308 major: 1,
309 minor: 21,
310 },
311 removed_in: K8sVersion {
312 major: 1,
313 minor: 25,
314 },
315 notes: None,
316 },
317 DeprecatedApi {
319 api_version: "certificates.k8s.io/v1beta1",
320 kind: Some("CertificateSigningRequest"),
321 replacement: "certificates.k8s.io/v1",
322 deprecated_in: K8sVersion {
323 major: 1,
324 minor: 19,
325 },
326 removed_in: K8sVersion {
327 major: 1,
328 minor: 22,
329 },
330 notes: None,
331 },
332 DeprecatedApi {
334 api_version: "coordination.k8s.io/v1beta1",
335 kind: Some("Lease"),
336 replacement: "coordination.k8s.io/v1",
337 deprecated_in: K8sVersion {
338 major: 1,
339 minor: 14,
340 },
341 removed_in: K8sVersion {
342 major: 1,
343 minor: 22,
344 },
345 notes: None,
346 },
347 DeprecatedApi {
349 api_version: "storage.k8s.io/v1beta1",
350 kind: Some("CSIDriver"),
351 replacement: "storage.k8s.io/v1",
352 deprecated_in: K8sVersion {
353 major: 1,
354 minor: 19,
355 },
356 removed_in: K8sVersion {
357 major: 1,
358 minor: 22,
359 },
360 notes: None,
361 },
362 DeprecatedApi {
363 api_version: "storage.k8s.io/v1beta1",
364 kind: Some("CSINode"),
365 replacement: "storage.k8s.io/v1",
366 deprecated_in: K8sVersion {
367 major: 1,
368 minor: 17,
369 },
370 removed_in: K8sVersion {
371 major: 1,
372 minor: 22,
373 },
374 notes: None,
375 },
376 DeprecatedApi {
377 api_version: "storage.k8s.io/v1beta1",
378 kind: Some("StorageClass"),
379 replacement: "storage.k8s.io/v1",
380 deprecated_in: K8sVersion { major: 1, minor: 6 },
381 removed_in: K8sVersion {
382 major: 1,
383 minor: 22,
384 },
385 notes: None,
386 },
387 DeprecatedApi {
388 api_version: "storage.k8s.io/v1beta1",
389 kind: Some("VolumeAttachment"),
390 replacement: "storage.k8s.io/v1",
391 deprecated_in: K8sVersion {
392 major: 1,
393 minor: 13,
394 },
395 removed_in: K8sVersion {
396 major: 1,
397 minor: 22,
398 },
399 notes: None,
400 },
401 DeprecatedApi {
403 api_version: "scheduling.k8s.io/v1beta1",
404 kind: Some("PriorityClass"),
405 replacement: "scheduling.k8s.io/v1",
406 deprecated_in: K8sVersion {
407 major: 1,
408 minor: 14,
409 },
410 removed_in: K8sVersion {
411 major: 1,
412 minor: 22,
413 },
414 notes: None,
415 },
416 DeprecatedApi {
418 api_version: "discovery.k8s.io/v1beta1",
419 kind: Some("EndpointSlice"),
420 replacement: "discovery.k8s.io/v1",
421 deprecated_in: K8sVersion {
422 major: 1,
423 minor: 21,
424 },
425 removed_in: K8sVersion {
426 major: 1,
427 minor: 25,
428 },
429 notes: None,
430 },
431 DeprecatedApi {
433 api_version: "events.k8s.io/v1beta1",
434 kind: Some("Event"),
435 replacement: "events.k8s.io/v1",
436 deprecated_in: K8sVersion {
437 major: 1,
438 minor: 19,
439 },
440 removed_in: K8sVersion {
441 major: 1,
442 minor: 25,
443 },
444 notes: None,
445 },
446 DeprecatedApi {
448 api_version: "autoscaling/v2beta1",
449 kind: Some("HorizontalPodAutoscaler"),
450 replacement: "autoscaling/v2",
451 deprecated_in: K8sVersion {
452 major: 1,
453 minor: 23,
454 },
455 removed_in: K8sVersion {
456 major: 1,
457 minor: 26,
458 },
459 notes: None,
460 },
461 DeprecatedApi {
463 api_version: "autoscaling/v2beta2",
464 kind: Some("HorizontalPodAutoscaler"),
465 replacement: "autoscaling/v2",
466 deprecated_in: K8sVersion {
467 major: 1,
468 minor: 23,
469 },
470 removed_in: K8sVersion {
471 major: 1,
472 minor: 26,
473 },
474 notes: None,
475 },
476];
477
478pub fn is_api_deprecated(api_version: &str, kind: Option<&str>) -> Option<&'static DeprecatedApi> {
480 DEPRECATED_APIS
481 .iter()
482 .find(|api| api.api_version == api_version && (api.kind.is_none() || api.kind == kind))
483}
484
485pub fn get_replacement_api(api_version: &str, kind: Option<&str>) -> Option<&'static str> {
487 is_api_deprecated(api_version, kind).map(|api| api.replacement)
488}
489
490pub fn is_api_deprecated_in_version(
492 api_version: &str,
493 kind: Option<&str>,
494 k8s_version: K8sVersion,
495) -> Option<&'static DeprecatedApi> {
496 DEPRECATED_APIS.iter().find(|api| {
497 api.api_version == api_version
498 && (api.kind.is_none() || api.kind == kind)
499 && k8s_version >= api.deprecated_in
500 })
501}
502
503pub fn is_api_removed_in_version(
505 api_version: &str,
506 kind: Option<&str>,
507 k8s_version: K8sVersion,
508) -> Option<&'static DeprecatedApi> {
509 DEPRECATED_APIS.iter().find(|api| {
510 api.api_version == api_version
511 && (api.kind.is_none() || api.kind == kind)
512 && k8s_version >= api.removed_in
513 })
514}
515
516pub fn build_deprecation_map() -> HashMap<String, Vec<&'static DeprecatedApi>> {
518 let mut map: HashMap<String, Vec<&'static DeprecatedApi>> = HashMap::new();
519 for api in DEPRECATED_APIS {
520 map.entry(api.api_version.to_string())
521 .or_default()
522 .push(api);
523 }
524 map
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
532 fn test_k8s_version_parse() {
533 assert_eq!(K8sVersion::parse("1.25"), Some(K8sVersion::new(1, 25)));
534 assert_eq!(K8sVersion::parse("v1.28"), Some(K8sVersion::new(1, 28)));
535 assert_eq!(K8sVersion::parse("invalid"), None);
536 }
537
538 #[test]
539 fn test_k8s_version_ordering() {
540 assert!(K8sVersion::new(1, 25) > K8sVersion::new(1, 20));
541 assert!(K8sVersion::new(1, 25) < K8sVersion::new(1, 26));
542 assert!(K8sVersion::new(1, 25) == K8sVersion::new(1, 25));
543 }
544
545 #[test]
546 fn test_is_api_deprecated() {
547 let result = is_api_deprecated("extensions/v1beta1", Some("Deployment"));
549 assert!(result.is_some());
550 let api = result.unwrap();
551 assert_eq!(api.replacement, "apps/v1");
552
553 let result = is_api_deprecated("apps/v1", Some("Deployment"));
555 assert!(result.is_none());
556 }
557
558 #[test]
559 fn test_get_replacement_api() {
560 assert_eq!(
561 get_replacement_api("extensions/v1beta1", Some("Deployment")),
562 Some("apps/v1")
563 );
564 assert_eq!(
565 get_replacement_api("networking.k8s.io/v1beta1", Some("Ingress")),
566 Some("networking.k8s.io/v1")
567 );
568 assert_eq!(get_replacement_api("apps/v1", Some("Deployment")), None);
569 }
570
571 #[test]
572 fn test_deprecated_in_version() {
573 let result = is_api_deprecated_in_version(
575 "extensions/v1beta1",
576 Some("Deployment"),
577 K8sVersion::new(1, 10),
578 );
579 assert!(result.is_some());
580
581 let result = is_api_deprecated_in_version(
582 "extensions/v1beta1",
583 Some("Deployment"),
584 K8sVersion::new(1, 8),
585 );
586 assert!(result.is_none());
587 }
588
589 #[test]
590 fn test_removed_in_version() {
591 let result = is_api_removed_in_version(
593 "extensions/v1beta1",
594 Some("Deployment"),
595 K8sVersion::new(1, 16),
596 );
597 assert!(result.is_some());
598
599 let result = is_api_removed_in_version(
600 "extensions/v1beta1",
601 Some("Deployment"),
602 K8sVersion::new(1, 15),
603 );
604 assert!(result.is_none());
605 }
606
607 #[test]
608 fn test_build_deprecation_map() {
609 let map = build_deprecation_map();
610 assert!(map.contains_key("extensions/v1beta1"));
611 assert!(map.contains_key("apps/v1beta1"));
612 assert!(!map.contains_key("apps/v1"));
613 }
614}