1use super::*;
2
3pub trait PodSpecExt {
4 fn container(container: corev1::Container) -> Self;
7
8 fn containers(containers: impl IntoIterator<Item = corev1::Container>) -> Self;
11
12 fn active_deadline_seconds(self, seconds: i64) -> Self;
15
16 fn affinity(self, affinity: impl Into<Option<corev1::Affinity>>) -> Self;
19
20 fn automount_service_account_token(self, yes: bool) -> Self;
23
24 fn dns_policy(self, policy: impl ToString) -> Self;
27
28 fn enable_service_links(self, yes: bool) -> Self;
31
32 fn hostname(self, hostname: impl ToString) -> Self;
35
36 fn host_ipc(self, yes: bool) -> Self;
39
40 fn host_network(self, yes: bool) -> Self;
43
44 fn host_pid(self, yes: bool) -> Self;
47
48 fn image_pull_secret(self, name: impl ToString) -> Self;
51
52 fn node_name(self, node_name: impl ToString) -> Self;
55
56 fn node_selector(
59 self,
60 node_selector: impl IntoIterator<Item = (impl ToString, impl ToString)>,
61 ) -> Self;
62
63 fn preemption_policy(self, policy: impl ToString) -> Self;
66
67 fn priority(self, priority: i32) -> Self;
70
71 fn priority_class_name(self, class_name: impl ToString) -> Self;
74
75 fn restart_policy(self, policy: impl ToString) -> Self;
78
79 fn runtime_class_name(self, class_name: impl ToString) -> Self;
82
83 fn scheduler_name(self, scheduler: impl ToString) -> Self;
86
87 fn service_account_name(self, name: impl ToString) -> Self;
90
91 #[deprecated(note = "Use service_account_name instead")]
95 fn service_account(self, name: impl ToString) -> Self;
96
97 fn set_hostname_as_fqdn(self, yes: bool) -> Self;
100
101 fn share_process_namespace(self, yes: bool) -> Self;
104
105 fn subdomain(self, subdomain: impl ToString) -> Self;
108
109 fn termination_grace_period_seconds(self, seconds: i64) -> Self;
112
113 fn tolerations(self, tolerations: impl IntoIterator<Item = corev1::Toleration>) -> Self;
116
117 fn volumes(self, volumes: impl IntoIterator<Item = corev1::Volume>) -> Self;
120}
121
122impl PodSpecExt for corev1::PodSpec {
123 fn container(container: corev1::Container) -> Self {
124 let containers = vec![container];
125 Self {
126 containers,
127 ..default()
162 }
163 }
164
165 fn containers(containers: impl IntoIterator<Item = corev1::Container>) -> Self {
166 let containers = Vec::from_iter(containers);
167 Self {
168 containers,
169 ..default()
170 }
171 }
172
173 fn active_deadline_seconds(self, seconds: i64) -> Self {
174 Self {
175 active_deadline_seconds: Some(seconds),
176 ..self
177 }
178 }
179
180 fn affinity(self, affinity: impl Into<Option<corev1::Affinity>>) -> Self {
181 let affinity = affinity.into();
182 Self { affinity, ..self }
183 }
184
185 fn automount_service_account_token(self, yes: bool) -> Self {
186 Self {
187 automount_service_account_token: Some(yes),
188 ..self
189 }
190 }
191
192 fn dns_policy(self, policy: impl ToString) -> Self {
193 let dns_policy = Some(policy.to_string());
194 Self { dns_policy, ..self }
195 }
196
197 fn enable_service_links(self, yes: bool) -> Self {
198 Self {
199 enable_service_links: Some(yes),
200 ..self
201 }
202 }
203
204 fn hostname(self, hostname: impl ToString) -> Self {
205 let hostname = Some(hostname.to_string());
206 Self { hostname, ..self }
207 }
208
209 fn host_ipc(self, yes: bool) -> Self {
210 Self {
211 host_ipc: Some(yes),
212 ..self
213 }
214 }
215
216 fn host_network(self, yes: bool) -> Self {
217 Self {
218 host_network: Some(yes),
219 ..self
220 }
221 }
222
223 fn host_pid(self, yes: bool) -> Self {
224 Self {
225 host_pid: Some(yes),
226 ..self
227 }
228 }
229
230 fn image_pull_secret(mut self, name: impl ToString) -> Self {
231 let secret = corev1::LocalObjectReference::new(name);
232 self.image_pull_secrets.get_or_insert_default().push(secret);
233 self
234 }
235
236 fn node_name(self, node_name: impl ToString) -> Self {
237 let node_name = Some(node_name.to_string());
238 Self { node_name, ..self }
239 }
240
241 fn node_selector(
242 mut self,
243 node_selector: impl IntoIterator<Item = (impl ToString, impl ToString)>,
244 ) -> Self {
245 let node_selector = node_selector
246 .into_iter()
247 .map(|(key, value)| (key.to_string(), value.to_string()));
248 self.node_selector
249 .get_or_insert_default()
250 .extend(node_selector);
251 self
252 }
253
254 fn preemption_policy(self, policy: impl ToString) -> Self {
255 let preemption_policy = Some(policy.to_string());
256 Self {
257 preemption_policy,
258 ..self
259 }
260 }
261
262 fn priority(self, priority: i32) -> Self {
263 Self {
264 priority: Some(priority),
265 ..self
266 }
267 }
268
269 fn priority_class_name(self, class_name: impl ToString) -> Self {
270 let priority_class_name = Some(class_name.to_string());
271 Self {
272 priority_class_name,
273 ..self
274 }
275 }
276
277 fn restart_policy(self, policy: impl ToString) -> Self {
278 let restart_policy = Some(policy.to_string());
279 Self {
280 restart_policy,
281 ..self
282 }
283 }
284
285 fn runtime_class_name(self, class_name: impl ToString) -> Self {
286 let runtime_class_name = Some(class_name.to_string());
287 Self {
288 runtime_class_name,
289 ..self
290 }
291 }
292
293 fn scheduler_name(self, scheduler: impl ToString) -> Self {
294 let scheduler_name = Some(scheduler.to_string());
295 Self {
296 scheduler_name,
297 ..self
298 }
299 }
300
301 fn service_account_name(self, name: impl ToString) -> Self {
302 let service_account_name = Some(name.to_string());
303 Self {
304 service_account_name,
305 ..self
306 }
307 }
308
309 fn service_account(self, name: impl ToString) -> Self {
310 let service_account = Some(name.to_string());
311 Self {
312 service_account,
313 ..self
314 }
315 }
316
317 fn set_hostname_as_fqdn(self, yes: bool) -> Self {
318 Self {
319 set_hostname_as_fqdn: Some(yes),
320 ..self
321 }
322 }
323
324 fn share_process_namespace(self, yes: bool) -> Self {
325 Self {
326 share_process_namespace: Some(yes),
327 ..self
328 }
329 }
330
331 fn subdomain(self, subdomain: impl ToString) -> Self {
332 let subdomain = Some(subdomain.to_string());
333 Self { subdomain, ..self }
334 }
335
336 fn termination_grace_period_seconds(self, seconds: i64) -> Self {
337 Self {
338 termination_grace_period_seconds: Some(seconds),
339 ..self
340 }
341 }
342
343 fn tolerations(mut self, tolerations: impl IntoIterator<Item = corev1::Toleration>) -> Self {
344 self.tolerations.get_or_insert_default().extend(tolerations);
345 self
346 }
347
348 fn volumes(mut self, volumes: impl IntoIterator<Item = corev1::Volume>) -> Self {
349 self.volumes.get_or_insert_default().extend(volumes);
350 self
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 fn test_spec() -> corev1::PodSpec {
359 corev1::PodSpec::container(corev1::Container::new("test-container"))
360 }
361
362 #[test]
363 fn container() {
364 let container = corev1::Container::new("test-container");
365 let pod_spec = corev1::PodSpec::container(container);
366 assert_eq!(pod_spec.containers.len(), 1);
367 assert_eq!(pod_spec.containers[0].name, "test-container");
368 }
369
370 #[test]
371 fn containers() {
372 let containers_vec = vec![
373 corev1::Container::new("container1"),
374 corev1::Container::new("container2"),
375 ];
376 let pod_spec = corev1::PodSpec::containers(containers_vec);
377 assert_eq!(pod_spec.containers.len(), 2);
378 assert_eq!(pod_spec.containers[0].name, "container1");
379 assert_eq!(pod_spec.containers[1].name, "container2");
380 }
381
382 #[test]
383 fn active_deadline_seconds() {
384 let pod_spec = test_spec().active_deadline_seconds(300);
385 assert_eq!(pod_spec.active_deadline_seconds, Some(300));
386 }
387
388 #[test]
389 fn affinity() {
390 let affinity = corev1::Affinity::default();
391 let pod_spec = test_spec().affinity(affinity);
392 assert!(pod_spec.affinity.is_some());
393
394 let pod_spec_none = test_spec().affinity(None);
395 assert!(pod_spec_none.affinity.is_none());
396 }
397
398 #[test]
399 fn automount_service_account_token() {
400 let pod_spec = test_spec().automount_service_account_token(true);
401 assert_eq!(pod_spec.automount_service_account_token, Some(true));
402
403 let pod_spec_false = test_spec().automount_service_account_token(false);
404 assert_eq!(pod_spec_false.automount_service_account_token, Some(false));
405 }
406
407 #[test]
408 fn dns_policy() {
409 let pod_spec = test_spec().dns_policy("ClusterFirst");
410 assert_eq!(pod_spec.dns_policy.unwrap(), "ClusterFirst");
411 }
412
413 #[test]
414 fn enable_service_links() {
415 let pod_spec = test_spec().enable_service_links(false);
416 assert_eq!(pod_spec.enable_service_links, Some(false));
417
418 let pod_spec_true = test_spec().enable_service_links(true);
419 assert_eq!(pod_spec_true.enable_service_links, Some(true));
420 }
421
422 #[test]
423 fn hostname() {
424 let pod_spec = test_spec().hostname("my-pod");
425 assert_eq!(pod_spec.hostname.unwrap(), "my-pod");
426 }
427
428 #[test]
429 fn host_ipc() {
430 let pod_spec = test_spec().host_ipc(true);
431 assert_eq!(pod_spec.host_ipc, Some(true));
432 }
433
434 #[test]
435 fn host_network() {
436 let pod_spec = test_spec().host_network(true);
437 assert_eq!(pod_spec.host_network, Some(true));
438 }
439
440 #[test]
441 fn host_pid() {
442 let pod_spec = test_spec().host_pid(true);
443 assert_eq!(pod_spec.host_pid, Some(true));
444 }
445
446 #[test]
447 fn image_pull_secret() {
448 let pod_spec = test_spec()
449 .image_pull_secret("secret1")
450 .image_pull_secret("secret2");
451
452 let secrets = pod_spec.image_pull_secrets.unwrap_or_default();
453 assert_eq!(secrets.len(), 2);
454 assert_eq!(secrets[0].name, "secret1");
456 assert_eq!(secrets[1].name, "secret2");
457 }
458
459 #[test]
460 fn node_name() {
461 let pod_spec = test_spec().node_name("worker-node-1");
462 assert_eq!(pod_spec.node_name.unwrap(), "worker-node-1");
463 }
464
465 #[test]
466 fn node_selector() {
467 let pod = corev1::Pod::default().spec(
468 test_spec().node_selector([("kubernetes.io/os", "linux"), ("node-type", "compute")]),
469 );
470
471 let node_selector = pod.node_selector().unwrap();
472 assert_eq!(node_selector.len(), 2);
473 assert_eq!(node_selector.get("kubernetes.io/os").unwrap(), "linux");
474 assert_eq!(node_selector.get("node-type").unwrap(), "compute");
475 }
476
477 #[test]
478 fn preemption_policy() {
479 let pod_spec = test_spec().preemption_policy("Never");
480 assert_eq!(pod_spec.preemption_policy.unwrap(), "Never");
481 }
482
483 #[test]
484 fn priority() {
485 let pod_spec = test_spec().priority(100);
486 assert_eq!(pod_spec.priority, Some(100));
487 }
488
489 #[test]
490 fn priority_class_name() {
491 let pod_spec = test_spec().priority_class_name("high-priority");
492 assert_eq!(pod_spec.priority_class_name.unwrap(), "high-priority");
493 }
494
495 #[test]
496 fn restart_policy() {
497 let pod_spec = test_spec().restart_policy("OnFailure");
498 assert_eq!(pod_spec.restart_policy.unwrap(), "OnFailure");
499 }
500
501 #[test]
502 fn runtime_class_name() {
503 let pod_spec = test_spec().runtime_class_name("gvisor");
504 assert_eq!(pod_spec.runtime_class_name.unwrap(), "gvisor");
505 }
506
507 #[test]
508 fn scheduler_name() {
509 let pod_spec = test_spec().scheduler_name("custom-scheduler");
510 assert_eq!(pod_spec.scheduler_name.unwrap(), "custom-scheduler");
511 }
512
513 #[test]
514 fn service_account_name() {
515 let pod_spec = test_spec().service_account_name("my-service-account");
516 assert_eq!(pod_spec.service_account_name.unwrap(), "my-service-account");
517 }
518
519 #[test]
520 #[expect(deprecated)]
521 fn service_account_deprecated() {
522 let pod_spec = test_spec().service_account("deprecated-account");
523 assert_eq!(pod_spec.service_account.unwrap(), "deprecated-account");
524 }
525
526 #[test]
527 fn set_hostname_as_fqdn() {
528 let pod_spec = test_spec().set_hostname_as_fqdn(true);
529 assert_eq!(pod_spec.set_hostname_as_fqdn, Some(true));
530 }
531
532 #[test]
533 fn share_process_namespace() {
534 let pod_spec = test_spec().share_process_namespace(true);
535 assert_eq!(pod_spec.share_process_namespace, Some(true));
536 }
537
538 #[test]
539 fn subdomain() {
540 let pod_spec = test_spec().subdomain("my-subdomain");
541 assert_eq!(pod_spec.subdomain.unwrap(), "my-subdomain");
542 }
543
544 #[test]
545 fn termination_grace_period_seconds() {
546 let pod_spec = test_spec().termination_grace_period_seconds(60);
547 assert_eq!(pod_spec.termination_grace_period_seconds, Some(60));
548 }
549
550 #[test]
551 fn tolerations() {
552 let toleration1 =
553 corev1::Toleration::no_schedule("node-role.kubernetes.io/master").exists();
554 let toleration2 = corev1::Toleration::no_execute("node.kubernetes.io/not-ready")
555 .exists()
556 .toleration_seconds(300);
557
558 let pod = corev1::Pod::default().spec(test_spec().tolerations([toleration1, toleration2]));
559
560 let tolerations = pod.tolerations().unwrap();
561 assert_eq!(tolerations.len(), 2);
562 let t0 = &tolerations[0];
563 let t1 = &tolerations[1];
564
565 assert_eq!(t0.key.as_ref().unwrap(), "node-role.kubernetes.io/master");
566 assert_eq!(t0.operator.as_ref().unwrap(), "Exists");
567 assert_eq!(t0.effect.as_ref().unwrap(), "NoSchedule");
568
569 assert_eq!(t1.key.as_ref().unwrap(), "node.kubernetes.io/not-ready");
570 assert_eq!(t1.operator.as_ref().unwrap(), "Exists");
571 assert_eq!(t1.effect.as_ref().unwrap(), "NoExecute");
572 assert_eq!(t1.toleration_seconds, Some(300));
573 }
574
575 #[test]
576 fn volumes() {
577 let volume1 = corev1::Volume {
578 name: "config-vol".to_string(),
579 config_map: Some(corev1::ConfigMapVolumeSource {
580 name: "my-config".to_string(),
581 ..default()
582 }),
583 ..default()
584 };
585 let volume2 = corev1::Volume {
586 name: "empty-vol".to_string(),
587 empty_dir: Some(corev1::EmptyDirVolumeSource::default()),
588 ..default()
589 };
590
591 let pod_spec = test_spec().volumes([volume1, volume2]);
592
593 let volumes = pod_spec.volumes.as_ref().unwrap();
594 assert_eq!(volumes.len(), 2);
595 assert_eq!(volumes[0].name, "config-vol");
596 assert_eq!(volumes[1].name, "empty-vol");
597 assert!(volumes[0].config_map.is_some());
598 assert!(volumes[1].empty_dir.is_some());
599 }
600
601 #[test]
602 fn method_chaining() {
603 let container = corev1::Container::new("test-container");
604
605 let pod_spec = corev1::PodSpec::container(container)
606 .hostname("my-pod")
607 .host_network(true)
608 .dns_policy("ClusterFirst")
609 .restart_policy("Always")
610 .priority(100)
611 .service_account_name("my-sa")
612 .image_pull_secret("my-secret")
613 .node_selector([("zone", "us-west-2a")])
614 .termination_grace_period_seconds(30);
615
616 assert_eq!(pod_spec.containers.len(), 1);
617 assert_eq!(pod_spec.hostname.as_ref().unwrap(), "my-pod");
618 assert_eq!(pod_spec.host_network, Some(true));
619 assert_eq!(pod_spec.dns_policy.as_ref().unwrap(), "ClusterFirst");
620 assert_eq!(pod_spec.restart_policy.as_ref().unwrap(), "Always");
621 assert_eq!(pod_spec.priority, Some(100));
622 assert_eq!(pod_spec.service_account_name.as_ref().unwrap(), "my-sa");
623 assert_eq!(pod_spec.image_pull_secrets.as_ref().unwrap().len(), 1);
624
625 let pod = corev1::Pod::default().spec(pod_spec);
627 assert_eq!(
628 pod.node_selector().unwrap().get("zone").unwrap(),
629 "us-west-2a"
630 );
631 assert_eq!(
632 pod.spec.as_ref().unwrap().termination_grace_period_seconds,
633 Some(30)
634 );
635 }
636}