1use std::time::Duration;
2
3#[cfg(feature = "device-requests")]
4use bollard::models::DeviceRequest;
5use bollard::models::ResourcesUlimits;
6
7use crate::{
8 core::{
9 copy::{CopyDataSource, 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(
120 self,
121 target: impl Into<String>,
122 source: impl Into<CopyDataSource>,
123 ) -> ContainerRequest<I>;
124
125 fn with_mapped_port(self, host_port: u16, container_port: ContainerPort)
135 -> ContainerRequest<I>;
136
137 #[cfg(feature = "host-port-exposure")]
139 fn with_exposed_host_port(self, port: u16) -> ContainerRequest<I>;
140
141 #[cfg(feature = "host-port-exposure")]
143 fn with_exposed_host_ports(self, ports: impl IntoIterator<Item = u16>) -> ContainerRequest<I>;
144
145 fn with_ulimit(self, name: &str, soft: i64, hard: Option<i64>) -> ContainerRequest<I>;
154
155 fn with_privileged(self, privileged: bool) -> ContainerRequest<I>;
157
158 fn with_cap_add(self, capability: impl Into<String>) -> ContainerRequest<I>;
160
161 fn with_cap_drop(self, capability: impl Into<String>) -> ContainerRequest<I>;
163
164 fn with_cgroupns_mode(self, cgroupns_mode: CgroupnsMode) -> ContainerRequest<I>;
170
171 fn with_userns_mode(self, userns_mode: &str) -> ContainerRequest<I>;
173
174 fn with_shm_size(self, bytes: u64) -> ContainerRequest<I>;
176
177 fn with_startup_timeout(self, timeout: Duration) -> ContainerRequest<I>;
179
180 fn with_working_dir(self, working_dir: impl Into<String>) -> ContainerRequest<I>;
182
183 fn with_log_consumer(self, log_consumer: impl LogConsumer + 'static) -> ContainerRequest<I>;
187
188 #[cfg(feature = "reusable-containers")]
196 fn with_reuse(self, reuse: ReuseDirective) -> ContainerRequest<I>;
197
198 fn with_user(self, user: impl Into<String>) -> ContainerRequest<I>;
200
201 fn with_readonly_rootfs(self, readonly_rootfs: bool) -> ContainerRequest<I>;
203
204 fn with_security_opt(self, security_opt: impl Into<String>) -> ContainerRequest<I>;
206
207 fn with_ready_conditions(self, ready_conditions: Vec<WaitFor>) -> ContainerRequest<I>;
212
213 fn with_health_check(self, health_check: Healthcheck) -> ContainerRequest<I>;
234
235 #[cfg(feature = "device-requests")]
262 fn with_device_requests(self, device_requests: Vec<DeviceRequest>) -> ContainerRequest<I>;
263}
264
265impl<RI: Into<ContainerRequest<I>>, I: Image> ImageExt<I> for RI {
267 fn with_cmd(self, cmd: impl IntoIterator<Item = impl Into<String>>) -> ContainerRequest<I> {
268 let container_req = self.into();
269 ContainerRequest {
270 overridden_cmd: cmd.into_iter().map(Into::into).collect(),
271 ..container_req
272 }
273 }
274
275 fn with_name(self, name: impl Into<String>) -> ContainerRequest<I> {
276 let container_req = self.into();
277 ContainerRequest {
278 image_name: Some(name.into()),
279 ..container_req
280 }
281 }
282
283 fn with_tag(self, tag: impl Into<String>) -> ContainerRequest<I> {
284 let container_req = self.into();
285 ContainerRequest {
286 image_tag: Some(tag.into()),
287 ..container_req
288 }
289 }
290
291 fn with_container_name(self, name: impl Into<String>) -> ContainerRequest<I> {
292 let container_req = self.into();
293
294 ContainerRequest {
295 container_name: Some(name.into()),
296 ..container_req
297 }
298 }
299
300 fn with_platform(self, platform: impl Into<String>) -> ContainerRequest<I> {
301 let container_req = self.into();
302
303 ContainerRequest {
304 platform: Some(platform.into()),
305 ..container_req
306 }
307 }
308
309 fn with_network(self, network: impl Into<String>) -> ContainerRequest<I> {
310 let container_req = self.into();
311 ContainerRequest {
312 network: Some(network.into()),
313 ..container_req
314 }
315 }
316
317 fn with_label(self, key: impl Into<String>, value: impl Into<String>) -> ContainerRequest<I> {
318 let mut container_req = self.into();
319
320 container_req.labels.insert(key.into(), value.into());
321
322 container_req
323 }
324
325 fn with_labels(
326 self,
327 labels: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
328 ) -> ContainerRequest<I> {
329 let mut container_req = self.into();
330
331 container_req.labels.extend(
332 labels
333 .into_iter()
334 .map(|(key, value)| (key.into(), value.into())),
335 );
336
337 container_req
338 }
339
340 fn with_env_var(
341 self,
342 name: impl Into<String>,
343 value: impl Into<String>,
344 ) -> ContainerRequest<I> {
345 let mut container_req = self.into();
346 container_req.env_vars.insert(name.into(), value.into());
347 container_req
348 }
349
350 fn with_host(self, key: impl Into<String>, value: impl Into<Host>) -> ContainerRequest<I> {
351 let mut container_req = self.into();
352 container_req.hosts.insert(key.into(), value.into());
353 container_req
354 }
355
356 fn with_hostname(self, hostname: impl Into<String>) -> ContainerRequest<I> {
357 let mut container_req = self.into();
358 container_req.hostname = Some(hostname.into());
359 container_req
360 }
361
362 fn with_mount(self, mount: impl Into<Mount>) -> ContainerRequest<I> {
363 let mut container_req = self.into();
364 container_req.mounts.push(mount.into());
365 container_req
366 }
367
368 fn with_copy_to(
369 self,
370 target: impl Into<String>,
371 source: impl Into<CopyDataSource>,
372 ) -> ContainerRequest<I> {
373 let mut container_req = self.into();
374 let target: String = target.into();
375 container_req
376 .copy_to_sources
377 .push(CopyToContainer::new(source, target));
378 container_req
379 }
380
381 fn with_mapped_port(
382 self,
383 host_port: u16,
384 container_port: ContainerPort,
385 ) -> ContainerRequest<I> {
386 let container_req = self.into();
387 let mut ports = container_req.ports.unwrap_or_default();
388 ports.push(PortMapping::new(host_port, container_port));
389
390 ContainerRequest {
391 ports: Some(ports),
392 ..container_req
393 }
394 }
395
396 #[cfg(feature = "host-port-exposure")]
397 fn with_exposed_host_port(self, port: u16) -> ContainerRequest<I> {
398 self.with_exposed_host_ports([port])
399 }
400
401 #[cfg(feature = "host-port-exposure")]
402 fn with_exposed_host_ports(self, ports: impl IntoIterator<Item = u16>) -> ContainerRequest<I> {
403 let mut container_req = self.into();
404 let exposures = container_req
405 .host_port_exposures
406 .get_or_insert_with(Vec::new);
407
408 exposures.extend(ports);
409 exposures.sort_unstable();
410 exposures.dedup();
411
412 container_req
413 }
414
415 fn with_ulimit(self, name: &str, soft: i64, hard: Option<i64>) -> ContainerRequest<I> {
416 let container_req = self.into();
417 let mut ulimits = container_req.ulimits.unwrap_or_default();
418 ulimits.push(ResourcesUlimits {
419 name: Some(name.into()),
420 soft: Some(soft),
421 hard,
422 });
423
424 ContainerRequest {
425 ulimits: Some(ulimits),
426 ..container_req
427 }
428 }
429
430 fn with_privileged(self, privileged: bool) -> ContainerRequest<I> {
431 let container_req = self.into();
432 ContainerRequest {
433 privileged,
434 ..container_req
435 }
436 }
437
438 fn with_cap_add(self, capability: impl Into<String>) -> ContainerRequest<I> {
439 let mut container_req = self.into();
440 container_req
441 .cap_add
442 .get_or_insert_with(Vec::new)
443 .push(capability.into());
444
445 container_req
446 }
447
448 fn with_cap_drop(self, capability: impl Into<String>) -> ContainerRequest<I> {
449 let mut container_req = self.into();
450 container_req
451 .cap_drop
452 .get_or_insert_with(Vec::new)
453 .push(capability.into());
454
455 container_req
456 }
457
458 fn with_cgroupns_mode(self, cgroupns_mode: CgroupnsMode) -> ContainerRequest<I> {
459 let container_req = self.into();
460 ContainerRequest {
461 cgroupns_mode: Some(cgroupns_mode),
462 ..container_req
463 }
464 }
465
466 fn with_userns_mode(self, userns_mode: &str) -> ContainerRequest<I> {
467 let container_req = self.into();
468 ContainerRequest {
469 userns_mode: Some(String::from(userns_mode)),
470 ..container_req
471 }
472 }
473
474 fn with_shm_size(self, bytes: u64) -> ContainerRequest<I> {
475 let container_req = self.into();
476 ContainerRequest {
477 shm_size: Some(bytes),
478 ..container_req
479 }
480 }
481
482 fn with_startup_timeout(self, timeout: Duration) -> ContainerRequest<I> {
483 let container_req = self.into();
484 ContainerRequest {
485 startup_timeout: Some(timeout),
486 ..container_req
487 }
488 }
489
490 fn with_working_dir(self, working_dir: impl Into<String>) -> ContainerRequest<I> {
491 let container_req = self.into();
492 ContainerRequest {
493 working_dir: Some(working_dir.into()),
494 ..container_req
495 }
496 }
497
498 fn with_log_consumer(self, log_consumer: impl LogConsumer + 'static) -> ContainerRequest<I> {
499 let mut container_req = self.into();
500 container_req.log_consumers.push(Box::new(log_consumer));
501 container_req
502 }
503
504 #[cfg(feature = "reusable-containers")]
505 fn with_reuse(self, reuse: ReuseDirective) -> ContainerRequest<I> {
506 ContainerRequest {
507 reuse,
508 ..self.into()
509 }
510 }
511
512 fn with_user(self, user: impl Into<String>) -> ContainerRequest<I> {
513 let container_req = self.into();
514 ContainerRequest {
515 user: Some(user.into()),
516 ..container_req
517 }
518 }
519
520 fn with_readonly_rootfs(self, readonly_rootfs: bool) -> ContainerRequest<I> {
521 let container_req = self.into();
522 ContainerRequest {
523 readonly_rootfs,
524 ..container_req
525 }
526 }
527
528 fn with_security_opt(self, security_opt: impl Into<String>) -> ContainerRequest<I> {
529 let mut container_req = self.into();
530 container_req
531 .security_opts
532 .get_or_insert_with(Vec::new)
533 .push(security_opt.into());
534
535 container_req
536 }
537
538 fn with_ready_conditions(self, ready_conditions: Vec<WaitFor>) -> ContainerRequest<I> {
539 let mut container_req = self.into();
540 container_req.ready_conditions = Some(ready_conditions);
541 container_req
542 }
543
544 fn with_health_check(self, health_check: Healthcheck) -> ContainerRequest<I> {
545 let mut container_req = self.into();
546 container_req.health_check = Some(health_check);
547 container_req
548 }
549
550 #[cfg(feature = "device-requests")]
551 fn with_device_requests(self, device_requests: Vec<DeviceRequest>) -> ContainerRequest<I> {
552 let container_req = self.into();
553 ContainerRequest {
554 device_requests: Some(device_requests),
555 ..container_req
556 }
557 }
558}
559
560#[cfg(all(test, feature = "host-port-exposure"))]
561mod tests {
562 use super::*;
563 use crate::images::generic::GenericImage;
564
565 #[test]
566 fn test_with_exposed_host_port_single() {
567 let image = GenericImage::new("test", "latest");
568 let request = image.with_exposed_host_port(8080);
569
570 assert_eq!(request.host_port_exposures, Some(vec![8080]));
571 }
572
573 #[test]
574 fn test_with_exposed_host_ports_multiple() {
575 let image = GenericImage::new("test", "latest");
576 let request = image.with_exposed_host_ports([8080, 9090, 3000]);
577
578 assert_eq!(request.host_port_exposures, Some(vec![3000, 8080, 9090]));
579 }
580
581 #[test]
582 fn test_with_exposed_host_ports_deduplication() {
583 let image = GenericImage::new("test", "latest");
584 let request = image.with_exposed_host_ports([8080, 9090, 8080, 3000, 9090]);
585
586 assert_eq!(request.host_port_exposures, Some(vec![3000, 8080, 9090]));
587 }
588
589 #[test]
590 fn test_with_exposed_host_ports_empty() {
591 let image = GenericImage::new("test", "latest");
592 let request = image.with_exposed_host_ports([]);
593
594 assert_eq!(request.host_port_exposures, Some(vec![]));
595 }
596
597 #[test]
598 fn test_with_exposed_host_ports_chaining() {
599 let image = GenericImage::new("test", "latest");
600 let request = image
601 .with_exposed_host_port(8080)
602 .with_exposed_host_ports([9090, 3000]);
603
604 assert_eq!(request.host_port_exposures, Some(vec![3000, 8080, 9090]));
605 }
606
607 #[test]
608 fn test_with_exposed_host_ports_preserves_existing() {
609 let image = GenericImage::new("test", "latest");
610 let request = image.with_exposed_host_port(8080);
611
612 let request = request.with_exposed_host_ports([9090, 3000]);
615
616 assert_eq!(request.host_port_exposures, Some(vec![3000, 8080, 9090]));
618 }
619}