1use std::{sync::Arc, time::Duration};
2
3#[cfg(feature = "device-requests")]
4use bollard::models::DeviceRequest;
5use bollard::models::{HostConfig, ResourcesUlimits};
6
7use crate::{
8 core::{
9 copy::{CopyDataSource, CopyTargetOptions, CopyToContainer},
10 healthcheck::Healthcheck,
11 logs::consumer::LogConsumer,
12 CgroupnsMode, ContainerPort, Host, Mount, PortMapping, WaitFor,
13 },
14 ContainerRequest, Image,
15};
16
17#[cfg(feature = "reusable-containers")]
18#[derive(Eq, Copy, Clone, Debug, Default, PartialEq)]
19pub enum ReuseDirective {
20 #[default]
21 Never,
22 Always,
23 CurrentSession,
24}
25
26#[cfg(feature = "reusable-containers")]
27impl std::fmt::Display for ReuseDirective {
28 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 formatter.write_str(match self {
30 Self::Never => "never",
31 Self::Always => "always",
32 Self::CurrentSession => "current-session",
33 })
34 }
35}
36
37pub trait ImageExt<I: Image> {
40 fn with_cmd(self, cmd: impl IntoIterator<Item = impl Into<String>>) -> ContainerRequest<I>;
57
58 fn with_name(self, name: impl Into<String>) -> ContainerRequest<I>;
61
62 fn with_tag(self, tag: impl Into<String>) -> ContainerRequest<I>;
67
68 fn with_container_name(self, name: impl Into<String>) -> ContainerRequest<I>;
70
71 fn with_platform(self, platform: impl Into<String>) -> ContainerRequest<I>;
84
85 fn with_network(self, network: impl Into<String>) -> ContainerRequest<I>;
87
88 fn with_label(self, key: impl Into<String>, value: impl Into<String>) -> ContainerRequest<I>;
94
95 fn with_labels(
101 self,
102 labels: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
103 ) -> ContainerRequest<I>;
104
105 fn with_env_var(self, name: impl Into<String>, value: impl Into<String>)
107 -> ContainerRequest<I>;
108
109 fn with_host(self, key: impl Into<String>, value: impl Into<Host>) -> ContainerRequest<I>;
111
112 fn with_hostname(self, hostname: impl Into<String>) -> ContainerRequest<I>;
114
115 fn with_mount(self, mount: impl Into<Mount>) -> ContainerRequest<I>;
117
118 fn with_copy_to(
145 self,
146 target: impl Into<CopyTargetOptions>,
147 source: impl Into<CopyDataSource>,
148 ) -> ContainerRequest<I>;
149
150 fn with_mapped_port(self, host_port: u16, container_port: ContainerPort)
160 -> ContainerRequest<I>;
161
162 #[cfg(feature = "host-port-exposure")]
164 fn with_exposed_host_port(self, port: u16) -> ContainerRequest<I>;
165
166 #[cfg(feature = "host-port-exposure")]
168 fn with_exposed_host_ports(self, ports: impl IntoIterator<Item = u16>) -> ContainerRequest<I>;
169
170 fn with_ulimit(self, name: &str, soft: i64, hard: Option<i64>) -> ContainerRequest<I>;
179
180 fn with_privileged(self, privileged: bool) -> ContainerRequest<I>;
182
183 fn with_cap_add(self, capability: impl Into<String>) -> ContainerRequest<I>;
185
186 fn with_cap_drop(self, capability: impl Into<String>) -> ContainerRequest<I>;
188
189 fn with_cgroupns_mode(self, cgroupns_mode: CgroupnsMode) -> ContainerRequest<I>;
195
196 fn with_userns_mode(self, userns_mode: &str) -> ContainerRequest<I>;
198
199 fn with_shm_size(self, bytes: u64) -> ContainerRequest<I>;
201
202 fn with_startup_timeout(self, timeout: Duration) -> ContainerRequest<I>;
204
205 fn with_working_dir(self, working_dir: impl Into<String>) -> ContainerRequest<I>;
207
208 fn with_log_consumer(self, log_consumer: impl LogConsumer + 'static) -> ContainerRequest<I>;
212
213 fn with_host_config_modifier(
218 self,
219 modifier: impl Fn(&mut HostConfig) + Send + Sync + 'static,
220 ) -> ContainerRequest<I>;
221
222 #[cfg(feature = "reusable-containers")]
230 fn with_reuse(self, reuse: ReuseDirective) -> ContainerRequest<I>;
231
232 fn with_user(self, user: impl Into<String>) -> ContainerRequest<I>;
234
235 fn with_readonly_rootfs(self, readonly_rootfs: bool) -> ContainerRequest<I>;
237
238 fn with_security_opt(self, security_opt: impl Into<String>) -> ContainerRequest<I>;
240
241 fn with_ready_conditions(self, ready_conditions: Vec<WaitFor>) -> ContainerRequest<I>;
246
247 fn with_health_check(self, health_check: Healthcheck) -> ContainerRequest<I>;
268
269 #[cfg(feature = "device-requests")]
296 fn with_device_requests(self, device_requests: Vec<DeviceRequest>) -> ContainerRequest<I>;
297
298 fn with_open_stdin(self, open_stdin: bool) -> ContainerRequest<I>;
300}
301
302impl<RI: Into<ContainerRequest<I>>, I: Image> ImageExt<I> for RI {
304 fn with_cmd(self, cmd: impl IntoIterator<Item = impl Into<String>>) -> ContainerRequest<I> {
305 let container_req = self.into();
306 ContainerRequest {
307 overridden_cmd: cmd.into_iter().map(Into::into).collect(),
308 ..container_req
309 }
310 }
311
312 fn with_name(self, name: impl Into<String>) -> ContainerRequest<I> {
313 let container_req = self.into();
314 ContainerRequest {
315 image_name: Some(name.into()),
316 ..container_req
317 }
318 }
319
320 fn with_tag(self, tag: impl Into<String>) -> ContainerRequest<I> {
321 let container_req = self.into();
322 ContainerRequest {
323 image_tag: Some(tag.into()),
324 ..container_req
325 }
326 }
327
328 fn with_container_name(self, name: impl Into<String>) -> ContainerRequest<I> {
329 let container_req = self.into();
330
331 ContainerRequest {
332 container_name: Some(name.into()),
333 ..container_req
334 }
335 }
336
337 fn with_platform(self, platform: impl Into<String>) -> ContainerRequest<I> {
338 let container_req = self.into();
339
340 ContainerRequest {
341 platform: Some(platform.into()),
342 ..container_req
343 }
344 }
345
346 fn with_network(self, network: impl Into<String>) -> ContainerRequest<I> {
347 let container_req = self.into();
348 ContainerRequest {
349 network: Some(network.into()),
350 ..container_req
351 }
352 }
353
354 fn with_label(self, key: impl Into<String>, value: impl Into<String>) -> ContainerRequest<I> {
355 let mut container_req = self.into();
356
357 container_req.labels.insert(key.into(), value.into());
358
359 container_req
360 }
361
362 fn with_labels(
363 self,
364 labels: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
365 ) -> ContainerRequest<I> {
366 let mut container_req = self.into();
367
368 container_req.labels.extend(
369 labels
370 .into_iter()
371 .map(|(key, value)| (key.into(), value.into())),
372 );
373
374 container_req
375 }
376
377 fn with_env_var(
378 self,
379 name: impl Into<String>,
380 value: impl Into<String>,
381 ) -> ContainerRequest<I> {
382 let mut container_req = self.into();
383 container_req.env_vars.insert(name.into(), value.into());
384 container_req
385 }
386
387 fn with_host(self, key: impl Into<String>, value: impl Into<Host>) -> ContainerRequest<I> {
388 let mut container_req = self.into();
389 container_req.hosts.insert(key.into(), value.into());
390 container_req
391 }
392
393 fn with_hostname(self, hostname: impl Into<String>) -> ContainerRequest<I> {
394 let mut container_req = self.into();
395 container_req.hostname = Some(hostname.into());
396 container_req
397 }
398
399 fn with_mount(self, mount: impl Into<Mount>) -> ContainerRequest<I> {
400 let mut container_req = self.into();
401 container_req.mounts.push(mount.into());
402 container_req
403 }
404
405 fn with_copy_to(
406 self,
407 target: impl Into<CopyTargetOptions>,
408 source: impl Into<CopyDataSource>,
409 ) -> ContainerRequest<I> {
410 let mut container_req = self.into();
411 let target = target.into();
412 container_req
413 .copy_to_sources
414 .push(CopyToContainer::new(source, target));
415 container_req
416 }
417
418 fn with_mapped_port(
419 self,
420 host_port: u16,
421 container_port: ContainerPort,
422 ) -> ContainerRequest<I> {
423 let container_req = self.into();
424 let mut ports = container_req.ports.unwrap_or_default();
425 ports.push(PortMapping::new(host_port, container_port));
426
427 ContainerRequest {
428 ports: Some(ports),
429 ..container_req
430 }
431 }
432
433 #[cfg(feature = "host-port-exposure")]
434 fn with_exposed_host_port(self, port: u16) -> ContainerRequest<I> {
435 self.with_exposed_host_ports([port])
436 }
437
438 #[cfg(feature = "host-port-exposure")]
439 fn with_exposed_host_ports(self, ports: impl IntoIterator<Item = u16>) -> ContainerRequest<I> {
440 let mut container_req = self.into();
441 let exposures = container_req
442 .host_port_exposures
443 .get_or_insert_with(Vec::new);
444
445 exposures.extend(ports);
446 exposures.sort_unstable();
447 exposures.dedup();
448
449 container_req
450 }
451
452 fn with_ulimit(self, name: &str, soft: i64, hard: Option<i64>) -> ContainerRequest<I> {
453 let container_req = self.into();
454 let mut ulimits = container_req.ulimits.unwrap_or_default();
455 ulimits.push(ResourcesUlimits {
456 name: Some(name.into()),
457 soft: Some(soft),
458 hard,
459 });
460
461 ContainerRequest {
462 ulimits: Some(ulimits),
463 ..container_req
464 }
465 }
466
467 fn with_privileged(self, privileged: bool) -> ContainerRequest<I> {
468 let container_req = self.into();
469 ContainerRequest {
470 privileged,
471 ..container_req
472 }
473 }
474
475 fn with_cap_add(self, capability: impl Into<String>) -> ContainerRequest<I> {
476 let mut container_req = self.into();
477 container_req
478 .cap_add
479 .get_or_insert_with(Vec::new)
480 .push(capability.into());
481
482 container_req
483 }
484
485 fn with_cap_drop(self, capability: impl Into<String>) -> ContainerRequest<I> {
486 let mut container_req = self.into();
487 container_req
488 .cap_drop
489 .get_or_insert_with(Vec::new)
490 .push(capability.into());
491
492 container_req
493 }
494
495 fn with_cgroupns_mode(self, cgroupns_mode: CgroupnsMode) -> ContainerRequest<I> {
496 let container_req = self.into();
497 ContainerRequest {
498 cgroupns_mode: Some(cgroupns_mode),
499 ..container_req
500 }
501 }
502
503 fn with_userns_mode(self, userns_mode: &str) -> ContainerRequest<I> {
504 let container_req = self.into();
505 ContainerRequest {
506 userns_mode: Some(String::from(userns_mode)),
507 ..container_req
508 }
509 }
510
511 fn with_shm_size(self, bytes: u64) -> ContainerRequest<I> {
512 let container_req = self.into();
513 ContainerRequest {
514 shm_size: Some(bytes),
515 ..container_req
516 }
517 }
518
519 fn with_startup_timeout(self, timeout: Duration) -> ContainerRequest<I> {
520 let container_req = self.into();
521 ContainerRequest {
522 startup_timeout: Some(timeout),
523 ..container_req
524 }
525 }
526
527 fn with_working_dir(self, working_dir: impl Into<String>) -> ContainerRequest<I> {
528 let container_req = self.into();
529 ContainerRequest {
530 working_dir: Some(working_dir.into()),
531 ..container_req
532 }
533 }
534
535 fn with_log_consumer(self, log_consumer: impl LogConsumer + 'static) -> ContainerRequest<I> {
536 let mut container_req = self.into();
537 container_req.log_consumers.push(Box::new(log_consumer));
538 container_req
539 }
540
541 fn with_host_config_modifier(
542 self,
543 modifier: impl Fn(&mut HostConfig) + Send + Sync + 'static,
544 ) -> ContainerRequest<I> {
545 let container_req = self.into();
546 ContainerRequest {
547 host_config_modifier: Some(Arc::new(modifier)),
548 ..container_req
549 }
550 }
551
552 #[cfg(feature = "reusable-containers")]
553 fn with_reuse(self, reuse: ReuseDirective) -> ContainerRequest<I> {
554 ContainerRequest {
555 reuse,
556 ..self.into()
557 }
558 }
559
560 fn with_user(self, user: impl Into<String>) -> ContainerRequest<I> {
561 let container_req = self.into();
562 ContainerRequest {
563 user: Some(user.into()),
564 ..container_req
565 }
566 }
567
568 fn with_readonly_rootfs(self, readonly_rootfs: bool) -> ContainerRequest<I> {
569 let container_req = self.into();
570 ContainerRequest {
571 readonly_rootfs,
572 ..container_req
573 }
574 }
575
576 fn with_security_opt(self, security_opt: impl Into<String>) -> ContainerRequest<I> {
577 let mut container_req = self.into();
578 container_req
579 .security_opts
580 .get_or_insert_with(Vec::new)
581 .push(security_opt.into());
582
583 container_req
584 }
585
586 fn with_ready_conditions(self, ready_conditions: Vec<WaitFor>) -> ContainerRequest<I> {
587 let mut container_req = self.into();
588 container_req.ready_conditions = Some(ready_conditions);
589 container_req
590 }
591
592 fn with_health_check(self, health_check: Healthcheck) -> ContainerRequest<I> {
593 let mut container_req = self.into();
594 container_req.health_check = Some(health_check);
595 container_req
596 }
597
598 #[cfg(feature = "device-requests")]
599 fn with_device_requests(self, device_requests: Vec<DeviceRequest>) -> ContainerRequest<I> {
600 let container_req = self.into();
601 ContainerRequest {
602 device_requests: Some(device_requests),
603 ..container_req
604 }
605 }
606
607 fn with_open_stdin(self, open_stdin: bool) -> ContainerRequest<I> {
608 let mut container_req = self.into();
609 container_req.open_stdin = Some(open_stdin);
610 container_req
611 }
612}
613
614#[cfg(all(test, feature = "host-port-exposure"))]
615mod tests {
616 use super::*;
617 use crate::images::generic::GenericImage;
618
619 #[test]
620 fn test_with_exposed_host_port_single() {
621 let image = GenericImage::new("test", "latest");
622 let request = image.with_exposed_host_port(8080);
623
624 assert_eq!(request.host_port_exposures, Some(vec![8080]));
625 }
626
627 #[test]
628 fn test_with_exposed_host_ports_multiple() {
629 let image = GenericImage::new("test", "latest");
630 let request = image.with_exposed_host_ports([8080, 9090, 3000]);
631
632 assert_eq!(request.host_port_exposures, Some(vec![3000, 8080, 9090]));
633 }
634
635 #[test]
636 fn test_with_exposed_host_ports_deduplication() {
637 let image = GenericImage::new("test", "latest");
638 let request = image.with_exposed_host_ports([8080, 9090, 8080, 3000, 9090]);
639
640 assert_eq!(request.host_port_exposures, Some(vec![3000, 8080, 9090]));
641 }
642
643 #[test]
644 fn test_with_exposed_host_ports_empty() {
645 let image = GenericImage::new("test", "latest");
646 let request = image.with_exposed_host_ports([]);
647
648 assert_eq!(request.host_port_exposures, Some(vec![]));
649 }
650
651 #[test]
652 fn test_with_exposed_host_ports_chaining() {
653 let image = GenericImage::new("test", "latest");
654 let request = image
655 .with_exposed_host_port(8080)
656 .with_exposed_host_ports([9090, 3000]);
657
658 assert_eq!(request.host_port_exposures, Some(vec![3000, 8080, 9090]));
659 }
660
661 #[test]
662 fn test_with_exposed_host_ports_preserves_existing() {
663 let image = GenericImage::new("test", "latest");
664 let request = image.with_exposed_host_port(8080);
665
666 let request = request.with_exposed_host_ports([9090, 3000]);
669
670 assert_eq!(request.host_port_exposures, Some(vec![3000, 8080, 9090]));
672 }
673}