1use super::{CommandExecutor, DockerCommand, EnvironmentBuilder, PortBuilder};
7use crate::error::{Error, Result};
8use async_trait::async_trait;
9use std::ffi::OsStr;
10use std::path::PathBuf;
11
12#[derive(Debug, Clone)]
14#[allow(clippy::struct_excessive_bools)]
15pub struct RunCommand {
16 image: String,
18 executor: CommandExecutor,
20 name: Option<String>,
22 detach: bool,
24 environment: EnvironmentBuilder,
26 ports: PortBuilder,
28 volumes: Vec<VolumeMount>,
30 workdir: Option<PathBuf>,
32 entrypoint: Option<String>,
34 command: Option<Vec<String>>,
36 interactive: bool,
38 tty: bool,
40 remove: bool,
42
43 memory: Option<String>,
46 cpus: Option<String>,
48 cpu_shares: Option<i64>,
50 cpu_period: Option<i64>,
52 cpu_quota: Option<i64>,
54 cpuset_cpus: Option<String>,
56 cpuset_mems: Option<String>,
58 memory_swap: Option<String>,
60 memory_reservation: Option<String>,
62
63 user: Option<String>,
66 privileged: bool,
68 hostname: Option<String>,
70
71 restart: Option<String>,
74
75 platform: Option<String>,
78 runtime: Option<String>,
80 isolation: Option<String>,
82 pull: Option<String>,
84 cidfile: Option<String>,
86 domainname: Option<String>,
88 mac_address: Option<String>,
90
91 log_driver: Option<String>,
94 volume_driver: Option<String>,
96
97 userns: Option<String>,
100 uts: Option<String>,
102 pid: Option<String>,
104 ipc: Option<String>,
106 cgroupns: Option<String>,
108 cgroup_parent: Option<String>,
110
111 kernel_memory: Option<String>,
114 memory_swappiness: Option<i32>,
116 oom_score_adj: Option<i32>,
118 pids_limit: Option<i64>,
120 shm_size: Option<String>,
122
123 stop_signal: Option<String>,
126 stop_timeout: Option<i32>,
128 detach_keys: Option<String>,
130
131 sig_proxy: bool,
134 read_only: bool,
136 init: bool,
138 oom_kill_disable: bool,
140 no_healthcheck: bool,
142 disable_content_trust: bool,
144 publish_all: bool,
146 quiet: bool,
148
149 dns: Vec<String>,
153 dns_option: Vec<String>,
155 dns_search: Vec<String>,
157 add_host: Vec<String>,
159
160 cap_add: Vec<String>,
163 cap_drop: Vec<String>,
165 security_opt: Vec<String>,
167
168 device: Vec<String>,
171 tmpfs: Vec<String>,
173 expose: Vec<String>,
175
176 env_file: Vec<PathBuf>,
179 label: Vec<String>,
181 label_file: Vec<PathBuf>,
183
184 network_alias: Vec<String>,
187 group_add: Vec<String>,
189 attach: Vec<String>,
191 log_opt: Vec<String>,
193 storage_opt: Vec<String>,
195 ulimit: Vec<String>,
197 volumes_from: Vec<String>,
199 link: Vec<String>,
201 link_local_ip: Vec<String>,
203
204 health_cmd: Option<String>,
208 health_interval: Option<String>,
210 health_retries: Option<i32>,
212 health_timeout: Option<String>,
214 health_start_period: Option<String>,
216 health_start_interval: Option<String>,
218
219 mount: Vec<String>,
222 network: Vec<String>,
224 gpus: Option<String>,
226
227 annotation: Vec<String>,
230 sysctl: Vec<String>,
232
233 blkio_weight: Option<u16>,
237 blkio_weight_device: Vec<String>,
239 device_read_bps: Vec<String>,
241 device_write_bps: Vec<String>,
243 device_read_iops: Vec<String>,
245 device_write_iops: Vec<String>,
247
248 cpu_rt_period: Option<i64>,
251 cpu_rt_runtime: Option<i64>,
253
254 ip: Option<String>,
257 ip6: Option<String>,
259
260 device_cgroup_rule: Vec<String>,
263}
264
265#[derive(Debug, Clone)]
267pub struct VolumeMount {
268 pub source: String,
270 pub target: String,
272 pub mount_type: MountType,
274 pub readonly: bool,
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280pub enum MountType {
281 Bind,
283 Volume,
285 Tmpfs,
287}
288
289impl std::fmt::Display for VolumeMount {
290 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291 let readonly_suffix = if self.readonly { ":ro" } else { "" };
292 write!(f, "{}:{}{}", self.source, self.target, readonly_suffix)
293 }
294}
295
296#[derive(Debug, Clone, PartialEq, Eq)]
298pub struct ContainerId(pub String);
299
300impl ContainerId {
301 #[must_use]
303 pub fn as_str(&self) -> &str {
304 &self.0
305 }
306
307 #[must_use]
309 pub fn short(&self) -> &str {
310 if self.0.len() >= 12 {
311 &self.0[..12]
312 } else {
313 &self.0
314 }
315 }
316}
317
318impl std::fmt::Display for ContainerId {
319 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320 write!(f, "{}", self.0)
321 }
322}
323
324impl RunCommand {
325 #[allow(clippy::too_many_lines)]
327 pub fn new(image: impl Into<String>) -> Self {
328 Self {
329 image: image.into(),
330 executor: CommandExecutor::new(),
331 name: None,
332 detach: false,
333 environment: EnvironmentBuilder::new(),
334 ports: PortBuilder::new(),
335 volumes: Vec::new(),
336 workdir: None,
337 entrypoint: None,
338 command: None,
339 interactive: false,
340 tty: false,
341 remove: false,
342
343 memory: None,
345 cpus: None,
346 cpu_shares: None,
347 cpu_period: None,
348 cpu_quota: None,
349 cpuset_cpus: None,
350 cpuset_mems: None,
351 memory_swap: None,
352 memory_reservation: None,
353
354 user: None,
356 privileged: false,
357 hostname: None,
358
359 restart: None,
361
362 platform: None,
364 runtime: None,
365 isolation: None,
366 pull: None,
367 cidfile: None,
368 domainname: None,
369 mac_address: None,
370
371 log_driver: None,
373 volume_driver: None,
374
375 userns: None,
377 uts: None,
378 pid: None,
379 ipc: None,
380 cgroupns: None,
381 cgroup_parent: None,
382
383 kernel_memory: None,
385 memory_swappiness: None,
386 oom_score_adj: None,
387 pids_limit: None,
388 shm_size: None,
389
390 stop_signal: None,
392 stop_timeout: None,
393 detach_keys: None,
394
395 sig_proxy: true, read_only: false,
398 init: false,
399 oom_kill_disable: false,
400 no_healthcheck: false,
401 disable_content_trust: true, publish_all: false,
403 quiet: false,
404
405 dns: Vec::new(),
408 dns_option: Vec::new(),
409 dns_search: Vec::new(),
410 add_host: Vec::new(),
411
412 cap_add: Vec::new(),
414 cap_drop: Vec::new(),
415 security_opt: Vec::new(),
416
417 device: Vec::new(),
419 tmpfs: Vec::new(),
420 expose: Vec::new(),
421
422 env_file: Vec::new(),
424 label: Vec::new(),
425 label_file: Vec::new(),
426
427 network_alias: Vec::new(),
429 group_add: Vec::new(),
430 attach: Vec::new(),
431 log_opt: Vec::new(),
432 storage_opt: Vec::new(),
433 ulimit: Vec::new(),
434 volumes_from: Vec::new(),
435 link: Vec::new(),
436 link_local_ip: Vec::new(),
437
438 health_cmd: None,
440 health_interval: None,
441 health_retries: None,
442 health_timeout: None,
443 health_start_period: None,
444 health_start_interval: None,
445 mount: Vec::new(),
446 network: Vec::new(),
447 gpus: None,
448 annotation: Vec::new(),
449 sysctl: Vec::new(),
450
451 blkio_weight: None,
453 blkio_weight_device: Vec::new(),
454 device_read_bps: Vec::new(),
455 device_write_bps: Vec::new(),
456 device_read_iops: Vec::new(),
457 device_write_iops: Vec::new(),
458 cpu_rt_period: None,
459 cpu_rt_runtime: None,
460 ip: None,
461 ip6: None,
462 device_cgroup_rule: Vec::new(),
463 }
464 }
465
466 #[must_use]
468 pub fn name(mut self, name: impl Into<String>) -> Self {
469 self.name = Some(name.into());
470 self
471 }
472
473 #[must_use]
475 pub fn detach(mut self) -> Self {
476 self.detach = true;
477 self
478 }
479
480 #[must_use]
482 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
483 self.environment = self.environment.var(key, value);
484 self
485 }
486
487 #[must_use]
489 pub fn envs(mut self, vars: std::collections::HashMap<String, String>) -> Self {
490 self.environment = self.environment.vars(vars);
491 self
492 }
493
494 #[must_use]
496 pub fn port(mut self, host_port: u16, container_port: u16) -> Self {
497 self.ports = self.ports.port(host_port, container_port);
498 self
499 }
500
501 #[must_use]
503 pub fn dynamic_port(mut self, container_port: u16) -> Self {
504 self.ports = self.ports.dynamic_port(container_port);
505 self
506 }
507
508 #[must_use]
510 pub fn volume(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
511 self.volumes.push(VolumeMount {
512 source: source.into(),
513 target: target.into(),
514 mount_type: MountType::Volume,
515 readonly: false,
516 });
517 self
518 }
519
520 #[must_use]
522 pub fn bind(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
523 self.volumes.push(VolumeMount {
524 source: source.into(),
525 target: target.into(),
526 mount_type: MountType::Bind,
527 readonly: false,
528 });
529 self
530 }
531
532 #[must_use]
534 pub fn volume_ro(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
535 self.volumes.push(VolumeMount {
536 source: source.into(),
537 target: target.into(),
538 mount_type: MountType::Volume,
539 readonly: true,
540 });
541 self
542 }
543
544 #[must_use]
546 pub fn workdir(mut self, workdir: impl Into<PathBuf>) -> Self {
547 self.workdir = Some(workdir.into());
548 self
549 }
550
551 #[must_use]
553 pub fn entrypoint(mut self, entrypoint: impl Into<String>) -> Self {
554 self.entrypoint = Some(entrypoint.into());
555 self
556 }
557
558 #[must_use]
560 pub fn cmd(mut self, command: Vec<String>) -> Self {
561 self.command = Some(command);
562 self
563 }
564
565 #[must_use]
567 pub fn interactive(mut self) -> Self {
568 self.interactive = true;
569 self
570 }
571
572 #[must_use]
574 pub fn tty(mut self) -> Self {
575 self.tty = true;
576 self
577 }
578
579 #[must_use]
581 pub fn remove(mut self) -> Self {
582 self.remove = true;
583 self
584 }
585
586 #[must_use]
588 pub fn it(self) -> Self {
589 self.interactive().tty()
590 }
591
592 #[must_use]
595 pub fn memory(mut self, memory: impl Into<String>) -> Self {
596 self.memory = Some(memory.into());
597 self
598 }
599
600 #[must_use]
602 pub fn cpus(mut self, cpus: impl Into<String>) -> Self {
603 self.cpus = Some(cpus.into());
604 self
605 }
606
607 #[must_use]
609 pub fn cpu_shares(mut self, shares: i64) -> Self {
610 self.cpu_shares = Some(shares);
611 self
612 }
613
614 #[must_use]
616 pub fn cpu_period(mut self, period: i64) -> Self {
617 self.cpu_period = Some(period);
618 self
619 }
620
621 #[must_use]
623 pub fn cpu_quota(mut self, quota: i64) -> Self {
624 self.cpu_quota = Some(quota);
625 self
626 }
627
628 #[must_use]
630 pub fn cpuset_cpus(mut self, cpus: impl Into<String>) -> Self {
631 self.cpuset_cpus = Some(cpus.into());
632 self
633 }
634
635 #[must_use]
637 pub fn cpuset_mems(mut self, mems: impl Into<String>) -> Self {
638 self.cpuset_mems = Some(mems.into());
639 self
640 }
641
642 #[must_use]
644 pub fn memory_swap(mut self, swap: impl Into<String>) -> Self {
645 self.memory_swap = Some(swap.into());
646 self
647 }
648
649 #[must_use]
651 pub fn memory_reservation(mut self, reservation: impl Into<String>) -> Self {
652 self.memory_reservation = Some(reservation.into());
653 self
654 }
655
656 #[must_use]
659 pub fn user(mut self, user: impl Into<String>) -> Self {
660 self.user = Some(user.into());
661 self
662 }
663
664 #[must_use]
666 pub fn privileged(mut self) -> Self {
667 self.privileged = true;
668 self
669 }
670
671 #[must_use]
673 pub fn hostname(mut self, hostname: impl Into<String>) -> Self {
674 self.hostname = Some(hostname.into());
675 self
676 }
677
678 #[must_use]
681 pub fn restart(mut self, restart: impl Into<String>) -> Self {
682 self.restart = Some(restart.into());
683 self
684 }
685
686 #[must_use]
689 pub fn platform(mut self, platform: impl Into<String>) -> Self {
690 self.platform = Some(platform.into());
691 self
692 }
693
694 #[must_use]
696 pub fn runtime(mut self, runtime: impl Into<String>) -> Self {
697 self.runtime = Some(runtime.into());
698 self
699 }
700
701 #[must_use]
703 pub fn isolation(mut self, isolation: impl Into<String>) -> Self {
704 self.isolation = Some(isolation.into());
705 self
706 }
707
708 #[must_use]
710 pub fn pull(mut self, pull: impl Into<String>) -> Self {
711 self.pull = Some(pull.into());
712 self
713 }
714
715 #[must_use]
717 pub fn cidfile(mut self, cidfile: impl Into<String>) -> Self {
718 self.cidfile = Some(cidfile.into());
719 self
720 }
721
722 #[must_use]
724 pub fn domainname(mut self, domainname: impl Into<String>) -> Self {
725 self.domainname = Some(domainname.into());
726 self
727 }
728
729 #[must_use]
731 pub fn mac_address(mut self, mac: impl Into<String>) -> Self {
732 self.mac_address = Some(mac.into());
733 self
734 }
735
736 #[must_use]
739 pub fn log_driver(mut self, driver: impl Into<String>) -> Self {
740 self.log_driver = Some(driver.into());
741 self
742 }
743
744 #[must_use]
746 pub fn volume_driver(mut self, driver: impl Into<String>) -> Self {
747 self.volume_driver = Some(driver.into());
748 self
749 }
750
751 #[must_use]
754 pub fn userns(mut self, userns: impl Into<String>) -> Self {
755 self.userns = Some(userns.into());
756 self
757 }
758
759 #[must_use]
761 pub fn uts(mut self, uts: impl Into<String>) -> Self {
762 self.uts = Some(uts.into());
763 self
764 }
765
766 #[must_use]
768 pub fn pid(mut self, pid: impl Into<String>) -> Self {
769 self.pid = Some(pid.into());
770 self
771 }
772
773 #[must_use]
775 pub fn ipc(mut self, ipc: impl Into<String>) -> Self {
776 self.ipc = Some(ipc.into());
777 self
778 }
779
780 #[must_use]
782 pub fn cgroupns(mut self, cgroupns: impl Into<String>) -> Self {
783 self.cgroupns = Some(cgroupns.into());
784 self
785 }
786
787 #[must_use]
789 pub fn cgroup_parent(mut self, parent: impl Into<String>) -> Self {
790 self.cgroup_parent = Some(parent.into());
791 self
792 }
793
794 #[must_use]
797 pub fn kernel_memory(mut self, memory: impl Into<String>) -> Self {
798 self.kernel_memory = Some(memory.into());
799 self
800 }
801
802 #[must_use]
804 pub fn memory_swappiness(mut self, swappiness: i32) -> Self {
805 self.memory_swappiness = Some(swappiness);
806 self
807 }
808
809 #[must_use]
811 pub fn oom_score_adj(mut self, score: i32) -> Self {
812 self.oom_score_adj = Some(score);
813 self
814 }
815
816 #[must_use]
818 pub fn pids_limit(mut self, limit: i64) -> Self {
819 self.pids_limit = Some(limit);
820 self
821 }
822
823 #[must_use]
825 pub fn shm_size(mut self, size: impl Into<String>) -> Self {
826 self.shm_size = Some(size.into());
827 self
828 }
829
830 #[must_use]
833 pub fn stop_signal(mut self, signal: impl Into<String>) -> Self {
834 self.stop_signal = Some(signal.into());
835 self
836 }
837
838 #[must_use]
840 pub fn stop_timeout(mut self, timeout: i32) -> Self {
841 self.stop_timeout = Some(timeout);
842 self
843 }
844
845 #[must_use]
847 pub fn detach_keys(mut self, keys: impl Into<String>) -> Self {
848 self.detach_keys = Some(keys.into());
849 self
850 }
851
852 #[must_use]
855 pub fn no_sig_proxy(mut self) -> Self {
856 self.sig_proxy = false;
857 self
858 }
859
860 #[must_use]
862 pub fn read_only(mut self) -> Self {
863 self.read_only = true;
864 self
865 }
866
867 #[must_use]
869 pub fn init(mut self) -> Self {
870 self.init = true;
871 self
872 }
873
874 #[must_use]
876 pub fn oom_kill_disable(mut self) -> Self {
877 self.oom_kill_disable = true;
878 self
879 }
880
881 #[must_use]
883 pub fn no_healthcheck(mut self) -> Self {
884 self.no_healthcheck = true;
885 self
886 }
887
888 #[must_use]
890 pub fn enable_content_trust(mut self) -> Self {
891 self.disable_content_trust = false;
892 self
893 }
894
895 #[must_use]
897 pub fn publish_all(mut self) -> Self {
898 self.publish_all = true;
899 self
900 }
901
902 #[must_use]
904 pub fn quiet(mut self) -> Self {
905 self.quiet = true;
906 self
907 }
908
909 #[must_use]
914 pub fn dns(mut self, dns: impl Into<String>) -> Self {
915 self.dns.push(dns.into());
916 self
917 }
918
919 #[must_use]
921 pub fn dns_servers(mut self, servers: Vec<String>) -> Self {
922 self.dns.extend(servers);
923 self
924 }
925
926 #[must_use]
928 pub fn dns_option(mut self, option: impl Into<String>) -> Self {
929 self.dns_option.push(option.into());
930 self
931 }
932
933 #[must_use]
935 pub fn dns_search(mut self, domain: impl Into<String>) -> Self {
936 self.dns_search.push(domain.into());
937 self
938 }
939
940 #[must_use]
942 pub fn add_host(mut self, mapping: impl Into<String>) -> Self {
943 self.add_host.push(mapping.into());
944 self
945 }
946
947 #[must_use]
950 pub fn cap_add(mut self, capability: impl Into<String>) -> Self {
951 self.cap_add.push(capability.into());
952 self
953 }
954
955 #[must_use]
957 pub fn cap_drop(mut self, capability: impl Into<String>) -> Self {
958 self.cap_drop.push(capability.into());
959 self
960 }
961
962 #[must_use]
964 pub fn security_opt(mut self, option: impl Into<String>) -> Self {
965 self.security_opt.push(option.into());
966 self
967 }
968
969 #[must_use]
972 pub fn device(mut self, device: impl Into<String>) -> Self {
973 self.device.push(device.into());
974 self
975 }
976
977 #[must_use]
979 pub fn tmpfs(mut self, path: impl Into<String>) -> Self {
980 self.tmpfs.push(path.into());
981 self
982 }
983
984 #[must_use]
986 pub fn expose(mut self, port: impl Into<String>) -> Self {
987 self.expose.push(port.into());
988 self
989 }
990
991 #[must_use]
994 pub fn env_file(mut self, file: impl Into<PathBuf>) -> Self {
995 self.env_file.push(file.into());
996 self
997 }
998
999 #[must_use]
1001 pub fn label(mut self, label: impl Into<String>) -> Self {
1002 self.label.push(label.into());
1003 self
1004 }
1005
1006 #[must_use]
1008 pub fn label_file(mut self, file: impl Into<PathBuf>) -> Self {
1009 self.label_file.push(file.into());
1010 self
1011 }
1012
1013 #[must_use]
1017 pub fn network_alias(mut self, alias: impl Into<String>) -> Self {
1018 self.network_alias.push(alias.into());
1019 self
1020 }
1021
1022 #[must_use]
1024 pub fn group_add(mut self, group: impl Into<String>) -> Self {
1025 self.group_add.push(group.into());
1026 self
1027 }
1028
1029 #[must_use]
1031 pub fn attach(mut self, stream: impl Into<String>) -> Self {
1032 self.attach.push(stream.into());
1033 self
1034 }
1035
1036 #[must_use]
1038 pub fn log_opt(mut self, option: impl Into<String>) -> Self {
1039 self.log_opt.push(option.into());
1040 self
1041 }
1042
1043 #[must_use]
1045 pub fn storage_opt(mut self, option: impl Into<String>) -> Self {
1046 self.storage_opt.push(option.into());
1047 self
1048 }
1049
1050 #[must_use]
1052 pub fn ulimit(mut self, limit: impl Into<String>) -> Self {
1053 self.ulimit.push(limit.into());
1054 self
1055 }
1056
1057 #[must_use]
1059 pub fn volumes_from(mut self, container: impl Into<String>) -> Self {
1060 self.volumes_from.push(container.into());
1061 self
1062 }
1063
1064 #[must_use]
1066 pub fn link(mut self, link: impl Into<String>) -> Self {
1067 self.link.push(link.into());
1068 self
1069 }
1070
1071 #[must_use]
1073 pub fn link_local_ip(mut self, ip: impl Into<String>) -> Self {
1074 self.link_local_ip.push(ip.into());
1075 self
1076 }
1077
1078 #[must_use]
1083 pub fn health_cmd(mut self, cmd: impl Into<String>) -> Self {
1084 self.health_cmd = Some(cmd.into());
1085 self
1086 }
1087
1088 #[must_use]
1090 pub fn health_interval(mut self, interval: impl Into<String>) -> Self {
1091 self.health_interval = Some(interval.into());
1092 self
1093 }
1094
1095 #[must_use]
1097 pub fn health_retries(mut self, retries: i32) -> Self {
1098 self.health_retries = Some(retries);
1099 self
1100 }
1101
1102 #[must_use]
1104 pub fn health_timeout(mut self, timeout: impl Into<String>) -> Self {
1105 self.health_timeout = Some(timeout.into());
1106 self
1107 }
1108
1109 #[must_use]
1111 pub fn health_start_period(mut self, period: impl Into<String>) -> Self {
1112 self.health_start_period = Some(period.into());
1113 self
1114 }
1115
1116 #[must_use]
1118 pub fn health_start_interval(mut self, interval: impl Into<String>) -> Self {
1119 self.health_start_interval = Some(interval.into());
1120 self
1121 }
1122
1123 #[must_use]
1126 pub fn mount(mut self, mount: impl Into<String>) -> Self {
1127 self.mount.push(mount.into());
1128 self
1129 }
1130
1131 #[must_use]
1133 pub fn network(mut self, network: impl Into<String>) -> Self {
1134 self.network.push(network.into());
1135 self
1136 }
1137
1138 #[must_use]
1140 pub fn gpus(mut self, gpus: impl Into<String>) -> Self {
1141 self.gpus = Some(gpus.into());
1142 self
1143 }
1144
1145 #[must_use]
1147 pub fn annotation(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1148 self.annotation
1149 .push(format!("{}={}", key.into(), value.into()));
1150 self
1151 }
1152
1153 #[must_use]
1155 pub fn sysctl(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1156 self.sysctl.push(format!("{}={}", key.into(), value.into()));
1157 self
1158 }
1159
1160 #[must_use]
1165 pub fn blkio_weight(mut self, weight: u16) -> Self {
1166 self.blkio_weight = Some(weight);
1167 self
1168 }
1169
1170 #[must_use]
1172 pub fn blkio_weight_device(mut self, device_weight: impl Into<String>) -> Self {
1173 self.blkio_weight_device.push(device_weight.into());
1174 self
1175 }
1176
1177 #[must_use]
1179 pub fn device_read_bps(mut self, device_rate: impl Into<String>) -> Self {
1180 self.device_read_bps.push(device_rate.into());
1181 self
1182 }
1183
1184 #[must_use]
1186 pub fn device_write_bps(mut self, device_rate: impl Into<String>) -> Self {
1187 self.device_write_bps.push(device_rate.into());
1188 self
1189 }
1190
1191 #[must_use]
1193 pub fn device_read_iops(mut self, device_rate: impl Into<String>) -> Self {
1194 self.device_read_iops.push(device_rate.into());
1195 self
1196 }
1197
1198 #[must_use]
1200 pub fn device_write_iops(mut self, device_rate: impl Into<String>) -> Self {
1201 self.device_write_iops.push(device_rate.into());
1202 self
1203 }
1204
1205 #[must_use]
1208 pub fn cpu_rt_period(mut self, period: i64) -> Self {
1209 self.cpu_rt_period = Some(period);
1210 self
1211 }
1212
1213 #[must_use]
1215 pub fn cpu_rt_runtime(mut self, runtime: i64) -> Self {
1216 self.cpu_rt_runtime = Some(runtime);
1217 self
1218 }
1219
1220 #[must_use]
1223 pub fn ip(mut self, ip: impl Into<String>) -> Self {
1224 self.ip = Some(ip.into());
1225 self
1226 }
1227
1228 #[must_use]
1230 pub fn ip6(mut self, ip6: impl Into<String>) -> Self {
1231 self.ip6 = Some(ip6.into());
1232 self
1233 }
1234
1235 #[must_use]
1237 pub fn device_cgroup_rule(mut self, rule: impl Into<String>) -> Self {
1238 self.device_cgroup_rule.push(rule.into());
1239 self
1240 }
1241}
1242
1243#[async_trait]
1244impl DockerCommand for RunCommand {
1245 type Output = ContainerId;
1246
1247 fn command_name(&self) -> &'static str {
1248 "run"
1249 }
1250
1251 #[allow(clippy::too_many_lines)]
1252 fn build_args(&self) -> Vec<String> {
1253 let mut args = Vec::new();
1254
1255 if self.detach {
1257 args.push("--detach".to_string());
1258 }
1259 if self.interactive {
1260 args.push("--interactive".to_string());
1261 }
1262 if self.tty {
1263 args.push("--tty".to_string());
1264 }
1265 if self.remove {
1266 args.push("--rm".to_string());
1267 }
1268
1269 if let Some(ref name) = self.name {
1271 args.push("--name".to_string());
1272 args.push(name.clone());
1273 }
1274
1275 if let Some(ref workdir) = self.workdir {
1277 args.push("--workdir".to_string());
1278 args.push(workdir.to_string_lossy().to_string());
1279 }
1280
1281 if let Some(ref entrypoint) = self.entrypoint {
1283 args.push("--entrypoint".to_string());
1284 args.push(entrypoint.clone());
1285 }
1286
1287 args.extend(self.environment.build_args());
1289
1290 args.extend(self.ports.build_args());
1292
1293 for volume in &self.volumes {
1295 args.push("--volume".to_string());
1296 args.push(volume.to_string());
1297 }
1298
1299 if let Some(ref memory) = self.memory {
1301 args.push("--memory".to_string());
1302 args.push(memory.clone());
1303 }
1304 if let Some(ref cpus) = self.cpus {
1305 args.push("--cpus".to_string());
1306 args.push(cpus.clone());
1307 }
1308 if let Some(cpu_shares) = self.cpu_shares {
1309 args.push("--cpu-shares".to_string());
1310 args.push(cpu_shares.to_string());
1311 }
1312 if let Some(cpu_period) = self.cpu_period {
1313 args.push("--cpu-period".to_string());
1314 args.push(cpu_period.to_string());
1315 }
1316 if let Some(cpu_quota) = self.cpu_quota {
1317 args.push("--cpu-quota".to_string());
1318 args.push(cpu_quota.to_string());
1319 }
1320 if let Some(ref cpuset_cpus) = self.cpuset_cpus {
1321 args.push("--cpuset-cpus".to_string());
1322 args.push(cpuset_cpus.clone());
1323 }
1324 if let Some(ref cpuset_mems) = self.cpuset_mems {
1325 args.push("--cpuset-mems".to_string());
1326 args.push(cpuset_mems.clone());
1327 }
1328 if let Some(ref memory_swap) = self.memory_swap {
1329 args.push("--memory-swap".to_string());
1330 args.push(memory_swap.clone());
1331 }
1332 if let Some(ref memory_reservation) = self.memory_reservation {
1333 args.push("--memory-reservation".to_string());
1334 args.push(memory_reservation.clone());
1335 }
1336
1337 if let Some(ref user) = self.user {
1339 args.push("--user".to_string());
1340 args.push(user.clone());
1341 }
1342 if self.privileged {
1343 args.push("--privileged".to_string());
1344 }
1345 if let Some(ref hostname) = self.hostname {
1346 args.push("--hostname".to_string());
1347 args.push(hostname.clone());
1348 }
1349
1350 if let Some(ref restart) = self.restart {
1352 args.push("--restart".to_string());
1353 args.push(restart.clone());
1354 }
1355
1356 if let Some(ref platform) = self.platform {
1358 args.push("--platform".to_string());
1359 args.push(platform.clone());
1360 }
1361 if let Some(ref runtime) = self.runtime {
1362 args.push("--runtime".to_string());
1363 args.push(runtime.clone());
1364 }
1365 if let Some(ref isolation) = self.isolation {
1366 args.push("--isolation".to_string());
1367 args.push(isolation.clone());
1368 }
1369 if let Some(ref pull) = self.pull {
1370 args.push("--pull".to_string());
1371 args.push(pull.clone());
1372 }
1373 if let Some(ref cidfile) = self.cidfile {
1374 args.push("--cidfile".to_string());
1375 args.push(cidfile.clone());
1376 }
1377 if let Some(ref domainname) = self.domainname {
1378 args.push("--domainname".to_string());
1379 args.push(domainname.clone());
1380 }
1381 if let Some(ref mac_address) = self.mac_address {
1382 args.push("--mac-address".to_string());
1383 args.push(mac_address.clone());
1384 }
1385
1386 if let Some(ref log_driver) = self.log_driver {
1388 args.push("--log-driver".to_string());
1389 args.push(log_driver.clone());
1390 }
1391 if let Some(ref volume_driver) = self.volume_driver {
1392 args.push("--volume-driver".to_string());
1393 args.push(volume_driver.clone());
1394 }
1395
1396 if let Some(ref userns) = self.userns {
1398 args.push("--userns".to_string());
1399 args.push(userns.clone());
1400 }
1401 if let Some(ref uts) = self.uts {
1402 args.push("--uts".to_string());
1403 args.push(uts.clone());
1404 }
1405 if let Some(ref pid) = self.pid {
1406 args.push("--pid".to_string());
1407 args.push(pid.clone());
1408 }
1409 if let Some(ref ipc) = self.ipc {
1410 args.push("--ipc".to_string());
1411 args.push(ipc.clone());
1412 }
1413 if let Some(ref cgroupns) = self.cgroupns {
1414 args.push("--cgroupns".to_string());
1415 args.push(cgroupns.clone());
1416 }
1417 if let Some(ref cgroup_parent) = self.cgroup_parent {
1418 args.push("--cgroup-parent".to_string());
1419 args.push(cgroup_parent.clone());
1420 }
1421
1422 if let Some(ref kernel_memory) = self.kernel_memory {
1424 args.push("--kernel-memory".to_string());
1425 args.push(kernel_memory.clone());
1426 }
1427 if let Some(memory_swappiness) = self.memory_swappiness {
1428 args.push("--memory-swappiness".to_string());
1429 args.push(memory_swappiness.to_string());
1430 }
1431 if let Some(oom_score_adj) = self.oom_score_adj {
1432 args.push("--oom-score-adj".to_string());
1433 args.push(oom_score_adj.to_string());
1434 }
1435 if let Some(pids_limit) = self.pids_limit {
1436 args.push("--pids-limit".to_string());
1437 args.push(pids_limit.to_string());
1438 }
1439 if let Some(ref shm_size) = self.shm_size {
1440 args.push("--shm-size".to_string());
1441 args.push(shm_size.clone());
1442 }
1443
1444 if let Some(ref stop_signal) = self.stop_signal {
1446 args.push("--stop-signal".to_string());
1447 args.push(stop_signal.clone());
1448 }
1449 if let Some(stop_timeout) = self.stop_timeout {
1450 args.push("--stop-timeout".to_string());
1451 args.push(stop_timeout.to_string());
1452 }
1453 if let Some(ref detach_keys) = self.detach_keys {
1454 args.push("--detach-keys".to_string());
1455 args.push(detach_keys.clone());
1456 }
1457
1458 if !self.sig_proxy {
1460 args.push("--sig-proxy=false".to_string());
1461 }
1462 if self.read_only {
1463 args.push("--read-only".to_string());
1464 }
1465 if self.init {
1466 args.push("--init".to_string());
1467 }
1468 if self.oom_kill_disable {
1469 args.push("--oom-kill-disable".to_string());
1470 }
1471 if self.no_healthcheck {
1472 args.push("--no-healthcheck".to_string());
1473 }
1474 if !self.disable_content_trust {
1475 args.push("--disable-content-trust=false".to_string());
1476 }
1477 if self.publish_all {
1478 args.push("--publish-all".to_string());
1479 }
1480 if self.quiet {
1481 args.push("--quiet".to_string());
1482 }
1483
1484 for dns in &self.dns {
1487 args.push("--dns".to_string());
1488 args.push(dns.clone());
1489 }
1490 for dns_option in &self.dns_option {
1491 args.push("--dns-option".to_string());
1492 args.push(dns_option.clone());
1493 }
1494 for dns_search in &self.dns_search {
1495 args.push("--dns-search".to_string());
1496 args.push(dns_search.clone());
1497 }
1498 for add_host in &self.add_host {
1499 args.push("--add-host".to_string());
1500 args.push(add_host.clone());
1501 }
1502
1503 for cap_add in &self.cap_add {
1505 args.push("--cap-add".to_string());
1506 args.push(cap_add.clone());
1507 }
1508 for cap_drop in &self.cap_drop {
1509 args.push("--cap-drop".to_string());
1510 args.push(cap_drop.clone());
1511 }
1512 for security_opt in &self.security_opt {
1513 args.push("--security-opt".to_string());
1514 args.push(security_opt.clone());
1515 }
1516
1517 for device in &self.device {
1519 args.push("--device".to_string());
1520 args.push(device.clone());
1521 }
1522 for tmpfs in &self.tmpfs {
1523 args.push("--tmpfs".to_string());
1524 args.push(tmpfs.clone());
1525 }
1526 for expose in &self.expose {
1527 args.push("--expose".to_string());
1528 args.push(expose.clone());
1529 }
1530
1531 for env_file in &self.env_file {
1533 args.push("--env-file".to_string());
1534 args.push(env_file.to_string_lossy().to_string());
1535 }
1536 for label in &self.label {
1537 args.push("--label".to_string());
1538 args.push(label.clone());
1539 }
1540 for label_file in &self.label_file {
1541 args.push("--label-file".to_string());
1542 args.push(label_file.to_string_lossy().to_string());
1543 }
1544
1545 for network_alias in &self.network_alias {
1547 args.push("--network-alias".to_string());
1548 args.push(network_alias.clone());
1549 }
1550 for group_add in &self.group_add {
1551 args.push("--group-add".to_string());
1552 args.push(group_add.clone());
1553 }
1554 for attach in &self.attach {
1555 args.push("--attach".to_string());
1556 args.push(attach.clone());
1557 }
1558 for log_opt in &self.log_opt {
1559 args.push("--log-opt".to_string());
1560 args.push(log_opt.clone());
1561 }
1562 for storage_opt in &self.storage_opt {
1563 args.push("--storage-opt".to_string());
1564 args.push(storage_opt.clone());
1565 }
1566 for ulimit in &self.ulimit {
1567 args.push("--ulimit".to_string());
1568 args.push(ulimit.clone());
1569 }
1570 for volumes_from in &self.volumes_from {
1571 args.push("--volumes-from".to_string());
1572 args.push(volumes_from.clone());
1573 }
1574 for link in &self.link {
1575 args.push("--link".to_string());
1576 args.push(link.clone());
1577 }
1578 for link_local_ip in &self.link_local_ip {
1579 args.push("--link-local-ip".to_string());
1580 args.push(link_local_ip.clone());
1581 }
1582
1583 if let Some(ref health_cmd) = self.health_cmd {
1586 args.push("--health-cmd".to_string());
1587 args.push(health_cmd.clone());
1588 }
1589 if let Some(ref health_interval) = self.health_interval {
1590 args.push("--health-interval".to_string());
1591 args.push(health_interval.clone());
1592 }
1593 if let Some(health_retries) = self.health_retries {
1594 args.push("--health-retries".to_string());
1595 args.push(health_retries.to_string());
1596 }
1597 if let Some(ref health_timeout) = self.health_timeout {
1598 args.push("--health-timeout".to_string());
1599 args.push(health_timeout.clone());
1600 }
1601 if let Some(ref health_start_period) = self.health_start_period {
1602 args.push("--health-start-period".to_string());
1603 args.push(health_start_period.clone());
1604 }
1605 if let Some(ref health_start_interval) = self.health_start_interval {
1606 args.push("--health-start-interval".to_string());
1607 args.push(health_start_interval.clone());
1608 }
1609
1610 for mount in &self.mount {
1612 args.push("--mount".to_string());
1613 args.push(mount.clone());
1614 }
1615 for network in &self.network {
1616 args.push("--network".to_string());
1617 args.push(network.clone());
1618 }
1619 if let Some(ref gpus) = self.gpus {
1620 args.push("--gpus".to_string());
1621 args.push(gpus.clone());
1622 }
1623
1624 for annotation in &self.annotation {
1626 args.push("--annotation".to_string());
1627 args.push(annotation.clone());
1628 }
1629 for sysctl in &self.sysctl {
1630 args.push("--sysctl".to_string());
1631 args.push(sysctl.clone());
1632 }
1633
1634 if let Some(blkio_weight) = self.blkio_weight {
1637 args.push("--blkio-weight".to_string());
1638 args.push(blkio_weight.to_string());
1639 }
1640 for blkio_weight_device in &self.blkio_weight_device {
1641 args.push("--blkio-weight-device".to_string());
1642 args.push(blkio_weight_device.clone());
1643 }
1644 for device_read_bps in &self.device_read_bps {
1645 args.push("--device-read-bps".to_string());
1646 args.push(device_read_bps.clone());
1647 }
1648 for device_write_bps in &self.device_write_bps {
1649 args.push("--device-write-bps".to_string());
1650 args.push(device_write_bps.clone());
1651 }
1652 for device_read_iops in &self.device_read_iops {
1653 args.push("--device-read-iops".to_string());
1654 args.push(device_read_iops.clone());
1655 }
1656 for device_write_iops in &self.device_write_iops {
1657 args.push("--device-write-iops".to_string());
1658 args.push(device_write_iops.clone());
1659 }
1660
1661 if let Some(cpu_rt_period) = self.cpu_rt_period {
1663 args.push("--cpu-rt-period".to_string());
1664 args.push(cpu_rt_period.to_string());
1665 }
1666 if let Some(cpu_rt_runtime) = self.cpu_rt_runtime {
1667 args.push("--cpu-rt-runtime".to_string());
1668 args.push(cpu_rt_runtime.to_string());
1669 }
1670
1671 if let Some(ref ip) = self.ip {
1673 args.push("--ip".to_string());
1674 args.push(ip.clone());
1675 }
1676 if let Some(ref ip6) = self.ip6 {
1677 args.push("--ip6".to_string());
1678 args.push(ip6.clone());
1679 }
1680
1681 for device_cgroup_rule in &self.device_cgroup_rule {
1683 args.push("--device-cgroup-rule".to_string());
1684 args.push(device_cgroup_rule.clone());
1685 }
1686
1687 args.push(self.image.clone());
1689
1690 if let Some(ref command) = self.command {
1692 args.extend(command.clone());
1693 }
1694
1695 args
1696 }
1697
1698 async fn execute(&self) -> Result<Self::Output> {
1699 let args = self.build_args();
1700 let output = self
1701 .executor
1702 .execute_command(self.command_name(), args)
1703 .await?;
1704
1705 let container_id = output.stdout.trim().to_string();
1707 if container_id.is_empty() {
1708 return Err(Error::parse_error(
1709 "No container ID returned from docker run",
1710 ));
1711 }
1712
1713 Ok(ContainerId(container_id))
1714 }
1715
1716 fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
1717 self.executor.add_arg(arg);
1718 self
1719 }
1720
1721 fn args<I, S>(&mut self, args: I) -> &mut Self
1722 where
1723 I: IntoIterator<Item = S>,
1724 S: AsRef<OsStr>,
1725 {
1726 self.executor.add_args(args);
1727 self
1728 }
1729
1730 fn flag(&mut self, flag: &str) -> &mut Self {
1731 self.executor.add_flag(flag);
1732 self
1733 }
1734
1735 fn option(&mut self, key: &str, value: &str) -> &mut Self {
1736 self.executor.add_option(key, value);
1737 self
1738 }
1739}
1740
1741#[cfg(test)]
1742mod tests {
1743 use super::*;
1744
1745 #[test]
1746 fn test_run_command_builder() {
1747 let cmd = RunCommand::new("nginx:latest")
1748 .name("test-nginx")
1749 .detach()
1750 .env("ENV_VAR", "value")
1751 .port(8080, 80)
1752 .volume("data", "/var/data")
1753 .workdir("/app")
1754 .remove();
1755
1756 let args = cmd.build_args();
1757
1758 assert!(args.contains(&"--detach".to_string()));
1759 assert!(args.contains(&"--name".to_string()));
1760 assert!(args.contains(&"test-nginx".to_string()));
1761 assert!(args.contains(&"--env".to_string()));
1762 assert!(args.contains(&"ENV_VAR=value".to_string()));
1763 assert!(args.contains(&"--publish".to_string()));
1764 assert!(args.contains(&"8080:80".to_string()));
1765 assert!(args.contains(&"--volume".to_string()));
1766 assert!(args.contains(&"data:/var/data".to_string()));
1767 assert!(args.contains(&"--workdir".to_string()));
1768 assert!(args.contains(&"/app".to_string()));
1769 assert!(args.contains(&"--rm".to_string()));
1770 assert!(args.contains(&"nginx:latest".to_string()));
1771 }
1772
1773 #[test]
1774 fn test_run_command_with_cmd() {
1775 let cmd =
1776 RunCommand::new("alpine:latest").cmd(vec!["echo".to_string(), "hello".to_string()]);
1777
1778 let args = cmd.build_args();
1779 assert!(args.contains(&"alpine:latest".to_string()));
1780 assert!(args.contains(&"echo".to_string()));
1781 assert!(args.contains(&"hello".to_string()));
1782 }
1783
1784 #[test]
1785 fn test_run_command_extensibility() {
1786 let mut cmd = RunCommand::new("test:latest");
1787 cmd.flag("privileged")
1788 .option("memory", "1g")
1789 .arg("--custom-option");
1790
1791 }
1794
1795 #[test]
1796 fn test_volume_mount_display() {
1797 let volume = VolumeMount {
1798 source: "data".to_string(),
1799 target: "/var/data".to_string(),
1800 mount_type: MountType::Volume,
1801 readonly: false,
1802 };
1803 assert_eq!(volume.to_string(), "data:/var/data");
1804
1805 let readonly_volume = VolumeMount {
1806 source: "/host/path".to_string(),
1807 target: "/container/path".to_string(),
1808 mount_type: MountType::Bind,
1809 readonly: true,
1810 };
1811 assert_eq!(readonly_volume.to_string(), "/host/path:/container/path:ro");
1812 }
1813
1814 #[test]
1815 fn test_run_command_resource_limits() {
1816 let cmd = RunCommand::new("alpine:latest")
1817 .memory("1g")
1818 .cpus("2.0")
1819 .cpu_shares(1024)
1820 .cpu_period(100_000)
1821 .cpu_quota(50_000)
1822 .cpuset_cpus("0-3")
1823 .cpuset_mems("0,1")
1824 .memory_swap("2g")
1825 .memory_reservation("500m");
1826
1827 let args = cmd.build_args();
1828
1829 assert!(args.contains(&"--memory".to_string()));
1830 assert!(args.contains(&"1g".to_string()));
1831 assert!(args.contains(&"--cpus".to_string()));
1832 assert!(args.contains(&"2.0".to_string()));
1833 assert!(args.contains(&"--cpu-shares".to_string()));
1834 assert!(args.contains(&"1024".to_string()));
1835 assert!(args.contains(&"--cpu-period".to_string()));
1836 assert!(args.contains(&"100000".to_string()));
1837 assert!(args.contains(&"--cpu-quota".to_string()));
1838 assert!(args.contains(&"50000".to_string()));
1839 assert!(args.contains(&"--cpuset-cpus".to_string()));
1840 assert!(args.contains(&"0-3".to_string()));
1841 assert!(args.contains(&"--cpuset-mems".to_string()));
1842 assert!(args.contains(&"0,1".to_string()));
1843 assert!(args.contains(&"--memory-swap".to_string()));
1844 assert!(args.contains(&"2g".to_string()));
1845 assert!(args.contains(&"--memory-reservation".to_string()));
1846 assert!(args.contains(&"500m".to_string()));
1847 }
1848
1849 #[test]
1850 fn test_run_command_security_and_user() {
1851 let cmd = RunCommand::new("alpine:latest")
1852 .user("1000:1000")
1853 .privileged()
1854 .hostname("test-host");
1855
1856 let args = cmd.build_args();
1857
1858 assert!(args.contains(&"--user".to_string()));
1859 assert!(args.contains(&"1000:1000".to_string()));
1860 assert!(args.contains(&"--privileged".to_string()));
1861 assert!(args.contains(&"--hostname".to_string()));
1862 assert!(args.contains(&"test-host".to_string()));
1863 }
1864
1865 #[test]
1866 fn test_run_command_lifecycle_management() {
1867 let cmd = RunCommand::new("alpine:latest").restart("always");
1868
1869 let args = cmd.build_args();
1870
1871 assert!(args.contains(&"--restart".to_string()));
1872 assert!(args.contains(&"always".to_string()));
1873 }
1874
1875 #[test]
1876 fn test_run_command_system_integration() {
1877 let cmd = RunCommand::new("alpine:latest")
1878 .platform("linux/amd64")
1879 .runtime("runc")
1880 .isolation("default")
1881 .pull("always")
1882 .cidfile("/tmp/container.cid")
1883 .domainname("example.com")
1884 .mac_address("92:d0:c6:0a:29:33");
1885
1886 let args = cmd.build_args();
1887
1888 assert!(args.contains(&"--platform".to_string()));
1889 assert!(args.contains(&"linux/amd64".to_string()));
1890 assert!(args.contains(&"--runtime".to_string()));
1891 assert!(args.contains(&"runc".to_string()));
1892 assert!(args.contains(&"--isolation".to_string()));
1893 assert!(args.contains(&"default".to_string()));
1894 assert!(args.contains(&"--pull".to_string()));
1895 assert!(args.contains(&"always".to_string()));
1896 assert!(args.contains(&"--cidfile".to_string()));
1897 assert!(args.contains(&"/tmp/container.cid".to_string()));
1898 assert!(args.contains(&"--domainname".to_string()));
1899 assert!(args.contains(&"example.com".to_string()));
1900 assert!(args.contains(&"--mac-address".to_string()));
1901 assert!(args.contains(&"92:d0:c6:0a:29:33".to_string()));
1902 }
1903
1904 #[test]
1905 fn test_run_command_logging_and_drivers() {
1906 let cmd = RunCommand::new("alpine:latest")
1907 .log_driver("json-file")
1908 .volume_driver("local");
1909
1910 let args = cmd.build_args();
1911
1912 assert!(args.contains(&"--log-driver".to_string()));
1913 assert!(args.contains(&"json-file".to_string()));
1914 assert!(args.contains(&"--volume-driver".to_string()));
1915 assert!(args.contains(&"local".to_string()));
1916 }
1917
1918 #[test]
1919 fn test_run_command_namespaces() {
1920 let cmd = RunCommand::new("alpine:latest")
1921 .userns("host")
1922 .uts("host")
1923 .pid("host")
1924 .ipc("host")
1925 .cgroupns("private")
1926 .cgroup_parent("/docker");
1927
1928 let args = cmd.build_args();
1929
1930 assert!(args.contains(&"--userns".to_string()));
1931 assert!(args.contains(&"host".to_string()));
1932 assert!(args.contains(&"--uts".to_string()));
1933 assert!(args.contains(&"--pid".to_string()));
1934 assert!(args.contains(&"--ipc".to_string()));
1935 assert!(args.contains(&"--cgroupns".to_string()));
1936 assert!(args.contains(&"private".to_string()));
1937 assert!(args.contains(&"--cgroup-parent".to_string()));
1938 assert!(args.contains(&"/docker".to_string()));
1939 }
1940
1941 #[test]
1942 fn test_run_command_advanced_memory_performance() {
1943 let cmd = RunCommand::new("alpine:latest")
1944 .kernel_memory("100m")
1945 .memory_swappiness(60)
1946 .oom_score_adj(-500)
1947 .pids_limit(100)
1948 .shm_size("64m");
1949
1950 let args = cmd.build_args();
1951
1952 assert!(args.contains(&"--kernel-memory".to_string()));
1953 assert!(args.contains(&"100m".to_string()));
1954 assert!(args.contains(&"--memory-swappiness".to_string()));
1955 assert!(args.contains(&"60".to_string()));
1956 assert!(args.contains(&"--oom-score-adj".to_string()));
1957 assert!(args.contains(&"-500".to_string()));
1958 assert!(args.contains(&"--pids-limit".to_string()));
1959 assert!(args.contains(&"100".to_string()));
1960 assert!(args.contains(&"--shm-size".to_string()));
1961 assert!(args.contains(&"64m".to_string()));
1962 }
1963
1964 #[test]
1965 fn test_run_command_process_control() {
1966 let cmd = RunCommand::new("alpine:latest")
1967 .stop_signal("SIGTERM")
1968 .stop_timeout(10)
1969 .detach_keys("ctrl-p,ctrl-q");
1970
1971 let args = cmd.build_args();
1972
1973 assert!(args.contains(&"--stop-signal".to_string()));
1974 assert!(args.contains(&"SIGTERM".to_string()));
1975 assert!(args.contains(&"--stop-timeout".to_string()));
1976 assert!(args.contains(&"10".to_string()));
1977 assert!(args.contains(&"--detach-keys".to_string()));
1978 assert!(args.contains(&"ctrl-p,ctrl-q".to_string()));
1979 }
1980
1981 #[test]
1982 fn test_run_command_simple_flags() {
1983 let cmd = RunCommand::new("alpine:latest")
1984 .no_sig_proxy()
1985 .read_only()
1986 .init()
1987 .oom_kill_disable()
1988 .no_healthcheck()
1989 .enable_content_trust()
1990 .publish_all()
1991 .quiet();
1992
1993 let args = cmd.build_args();
1994
1995 assert!(args.contains(&"--sig-proxy=false".to_string()));
1996 assert!(args.contains(&"--read-only".to_string()));
1997 assert!(args.contains(&"--init".to_string()));
1998 assert!(args.contains(&"--oom-kill-disable".to_string()));
1999 assert!(args.contains(&"--no-healthcheck".to_string()));
2000 assert!(args.contains(&"--disable-content-trust=false".to_string()));
2001 assert!(args.contains(&"--publish-all".to_string()));
2002 assert!(args.contains(&"--quiet".to_string()));
2003 }
2004
2005 #[test]
2006 fn test_run_command_comprehensive_builder() {
2007 let cmd = RunCommand::new("nginx:latest")
2008 .name("production-nginx")
2009 .detach()
2010 .memory("2g")
2011 .cpus("4.0")
2012 .user("nginx:nginx")
2013 .privileged()
2014 .restart("unless-stopped")
2015 .hostname("web-server")
2016 .platform("linux/amd64")
2017 .env("NGINX_PORT", "8080")
2018 .port(80, 8080)
2019 .volume("nginx-data", "/var/lib/nginx")
2020 .workdir("/usr/share/nginx/html")
2021 .read_only()
2022 .init()
2023 .remove();
2024
2025 let args = cmd.build_args();
2026
2027 assert!(args.contains(&"--name".to_string()));
2029 assert!(args.contains(&"production-nginx".to_string()));
2030 assert!(args.contains(&"--detach".to_string()));
2031 assert!(args.contains(&"--memory".to_string()));
2032 assert!(args.contains(&"2g".to_string()));
2033 assert!(args.contains(&"--cpus".to_string()));
2034 assert!(args.contains(&"4.0".to_string()));
2035 assert!(args.contains(&"--user".to_string()));
2036 assert!(args.contains(&"nginx:nginx".to_string()));
2037 assert!(args.contains(&"--privileged".to_string()));
2038 assert!(args.contains(&"--restart".to_string()));
2039 assert!(args.contains(&"unless-stopped".to_string()));
2040 assert!(args.contains(&"--hostname".to_string()));
2041 assert!(args.contains(&"web-server".to_string()));
2042 assert!(args.contains(&"--platform".to_string()));
2043 assert!(args.contains(&"linux/amd64".to_string()));
2044 assert!(args.contains(&"--read-only".to_string()));
2045 assert!(args.contains(&"--init".to_string()));
2046 assert!(args.contains(&"--rm".to_string()));
2047 assert!(args.contains(&"nginx:latest".to_string()));
2048
2049 let image_pos = args.iter().position(|x| x == "nginx:latest").unwrap();
2051 assert!(image_pos > 10); }
2053
2054 #[test]
2055 fn test_run_command_default_flag_values() {
2056 let cmd = RunCommand::new("alpine:latest");
2057 let args = cmd.build_args();
2058
2059 assert!(!args.contains(&"--sig-proxy=false".to_string()));
2061 assert!(!args.contains(&"--disable-content-trust=false".to_string()));
2062 assert!(!args.contains(&"--read-only".to_string()));
2063 assert!(!args.contains(&"--privileged".to_string()));
2064 assert!(!args.contains(&"--init".to_string()));
2065 }
2066
2067 #[test]
2068 fn test_container_id() {
2069 let id = ContainerId("abcdef123456789".to_string());
2070 assert_eq!(id.as_str(), "abcdef123456789");
2071 assert_eq!(id.short(), "abcdef123456");
2072 assert_eq!(id.to_string(), "abcdef123456789");
2073
2074 let short_id = ContainerId("abc".to_string());
2075 assert_eq!(short_id.short(), "abc");
2076 }
2077
2078 #[test]
2079 fn test_it_convenience_method() {
2080 let cmd = RunCommand::new("alpine:latest").it();
2081 let args = cmd.build_args();
2082 assert!(args.contains(&"--interactive".to_string()));
2083 assert!(args.contains(&"--tty".to_string()));
2084 }
2085
2086 #[test]
2087 fn test_run_command_dns_network_options() {
2088 let cmd = RunCommand::new("alpine:latest")
2089 .dns("8.8.8.8")
2090 .dns("8.8.4.4")
2091 .dns_servers(vec!["1.1.1.1".to_string(), "1.0.0.1".to_string()])
2092 .dns_option("ndots:2")
2093 .dns_option("timeout:1")
2094 .dns_search("example.com")
2095 .dns_search("test.local")
2096 .add_host("api.example.com:127.0.0.1")
2097 .add_host("db.example.com:192.168.1.100");
2098
2099 let args = cmd.build_args();
2100
2101 assert!(args.contains(&"--dns".to_string()));
2103 assert!(args.contains(&"8.8.8.8".to_string()));
2104 assert!(args.contains(&"8.8.4.4".to_string()));
2105 assert!(args.contains(&"1.1.1.1".to_string()));
2106 assert!(args.contains(&"1.0.0.1".to_string()));
2107
2108 assert!(args.contains(&"--dns-option".to_string()));
2110 assert!(args.contains(&"ndots:2".to_string()));
2111 assert!(args.contains(&"timeout:1".to_string()));
2112
2113 assert!(args.contains(&"--dns-search".to_string()));
2115 assert!(args.contains(&"example.com".to_string()));
2116 assert!(args.contains(&"test.local".to_string()));
2117
2118 assert!(args.contains(&"--add-host".to_string()));
2120 assert!(args.contains(&"api.example.com:127.0.0.1".to_string()));
2121 assert!(args.contains(&"db.example.com:192.168.1.100".to_string()));
2122 }
2123
2124 #[test]
2125 fn test_run_command_security_capabilities() {
2126 let cmd = RunCommand::new("alpine:latest")
2127 .cap_add("NET_ADMIN")
2128 .cap_add("SYS_TIME")
2129 .cap_drop("CHOWN")
2130 .cap_drop("DAC_OVERRIDE")
2131 .security_opt("no-new-privileges:true")
2132 .security_opt("seccomp=unconfined");
2133
2134 let args = cmd.build_args();
2135
2136 assert!(args.contains(&"--cap-add".to_string()));
2138 assert!(args.contains(&"NET_ADMIN".to_string()));
2139 assert!(args.contains(&"SYS_TIME".to_string()));
2140
2141 assert!(args.contains(&"--cap-drop".to_string()));
2143 assert!(args.contains(&"CHOWN".to_string()));
2144 assert!(args.contains(&"DAC_OVERRIDE".to_string()));
2145
2146 assert!(args.contains(&"--security-opt".to_string()));
2148 assert!(args.contains(&"no-new-privileges:true".to_string()));
2149 assert!(args.contains(&"seccomp=unconfined".to_string()));
2150 }
2151
2152 #[test]
2153 fn test_run_command_device_filesystem() {
2154 let cmd = RunCommand::new("alpine:latest")
2155 .device("/dev/sda:/dev/xvda:rwm")
2156 .device("/dev/zero")
2157 .tmpfs("/tmp:rw,size=100m")
2158 .tmpfs("/var/tmp:ro")
2159 .expose("80")
2160 .expose("443")
2161 .expose("8080/tcp");
2162
2163 let args = cmd.build_args();
2164
2165 assert!(args.contains(&"--device".to_string()));
2167 assert!(args.contains(&"/dev/sda:/dev/xvda:rwm".to_string()));
2168 assert!(args.contains(&"/dev/zero".to_string()));
2169
2170 assert!(args.contains(&"--tmpfs".to_string()));
2172 assert!(args.contains(&"/tmp:rw,size=100m".to_string()));
2173 assert!(args.contains(&"/var/tmp:ro".to_string()));
2174
2175 assert!(args.contains(&"--expose".to_string()));
2177 assert!(args.contains(&"80".to_string()));
2178 assert!(args.contains(&"443".to_string()));
2179 assert!(args.contains(&"8080/tcp".to_string()));
2180 }
2181
2182 #[test]
2183 fn test_run_command_environment_labels() {
2184 use std::path::PathBuf;
2185
2186 let cmd = RunCommand::new("alpine:latest")
2187 .env_file(PathBuf::from("/etc/environment"))
2188 .env_file(PathBuf::from("./app.env"))
2189 .label("version=1.0.0")
2190 .label("maintainer=team@example.com")
2191 .label("app=myapp")
2192 .label_file(PathBuf::from("/etc/labels"))
2193 .label_file(PathBuf::from("./metadata.labels"));
2194
2195 let args = cmd.build_args();
2196
2197 assert!(args.contains(&"--env-file".to_string()));
2199 assert!(args.contains(&"/etc/environment".to_string()));
2200 assert!(args.contains(&"./app.env".to_string()));
2201
2202 assert!(args.contains(&"--label".to_string()));
2204 assert!(args.contains(&"version=1.0.0".to_string()));
2205 assert!(args.contains(&"maintainer=team@example.com".to_string()));
2206 assert!(args.contains(&"app=myapp".to_string()));
2207
2208 assert!(args.contains(&"--label-file".to_string()));
2210 assert!(args.contains(&"/etc/labels".to_string()));
2211 assert!(args.contains(&"./metadata.labels".to_string()));
2212 }
2213
2214 #[test]
2215 fn test_run_command_all_high_impact_options() {
2216 use std::path::PathBuf;
2217
2218 let cmd = RunCommand::new("nginx:latest")
2219 .name("production-nginx")
2220 .dns("8.8.8.8")
2222 .dns_option("ndots:2")
2223 .dns_search("example.com")
2224 .add_host("api.example.com:127.0.0.1")
2225 .cap_add("NET_ADMIN")
2227 .cap_drop("CHOWN")
2228 .security_opt("no-new-privileges:true")
2229 .device("/dev/null")
2231 .tmpfs("/tmp:rw,size=100m")
2232 .expose("80")
2233 .env_file(PathBuf::from(".env"))
2235 .label("version=1.0.0")
2236 .label_file(PathBuf::from("labels"));
2237
2238 let args = cmd.build_args();
2239
2240 assert!(args.contains(&"--dns".to_string()));
2242 assert!(args.contains(&"--dns-option".to_string()));
2243 assert!(args.contains(&"--dns-search".to_string()));
2244 assert!(args.contains(&"--add-host".to_string()));
2245 assert!(args.contains(&"--cap-add".to_string()));
2246 assert!(args.contains(&"--cap-drop".to_string()));
2247 assert!(args.contains(&"--security-opt".to_string()));
2248 assert!(args.contains(&"--device".to_string()));
2249 assert!(args.contains(&"--tmpfs".to_string()));
2250 assert!(args.contains(&"--expose".to_string()));
2251 assert!(args.contains(&"--env-file".to_string()));
2252 assert!(args.contains(&"--label".to_string()));
2253 assert!(args.contains(&"--label-file".to_string()));
2254
2255 let image_pos = args.iter().position(|x| x == "nginx:latest").unwrap();
2257 assert!(image_pos > 0); assert!(image_pos < args.len() - 1 || args.len() == image_pos + 1); }
2260
2261 #[test]
2262 fn test_run_command_empty_lists_not_added() {
2263 let cmd = RunCommand::new("alpine:latest");
2264 let args = cmd.build_args();
2265
2266 assert!(!args.contains(&"--dns".to_string()));
2268 assert!(!args.contains(&"--dns-option".to_string()));
2269 assert!(!args.contains(&"--dns-search".to_string()));
2270 assert!(!args.contains(&"--add-host".to_string()));
2271 assert!(!args.contains(&"--cap-add".to_string()));
2272 assert!(!args.contains(&"--cap-drop".to_string()));
2273 assert!(!args.contains(&"--security-opt".to_string()));
2274 assert!(!args.contains(&"--device".to_string()));
2275 assert!(!args.contains(&"--tmpfs".to_string()));
2276 assert!(!args.contains(&"--expose".to_string()));
2277 assert!(!args.contains(&"--env-file".to_string()));
2278 assert!(!args.contains(&"--label".to_string()));
2279 assert!(!args.contains(&"--label-file".to_string()));
2280
2281 assert!(!args.contains(&"--network-alias".to_string()));
2283 assert!(!args.contains(&"--group-add".to_string()));
2284 assert!(!args.contains(&"--attach".to_string()));
2285 assert!(!args.contains(&"--log-opt".to_string()));
2286 assert!(!args.contains(&"--storage-opt".to_string()));
2287 assert!(!args.contains(&"--ulimit".to_string()));
2288 assert!(!args.contains(&"--volumes-from".to_string()));
2289 assert!(!args.contains(&"--link".to_string()));
2290 assert!(!args.contains(&"--link-local-ip".to_string()));
2291
2292 assert!(args.contains(&"alpine:latest".to_string()));
2294 }
2295
2296 #[test]
2297 fn test_run_command_additional_list_options() {
2298 let cmd = RunCommand::new("alpine:latest")
2299 .network_alias("web")
2300 .network_alias("frontend")
2301 .group_add("staff")
2302 .group_add("docker")
2303 .attach("stdout")
2304 .attach("stderr")
2305 .log_opt("max-size=10m")
2306 .log_opt("max-file=3")
2307 .storage_opt("size=20G")
2308 .ulimit("nofile=1024:65536")
2309 .ulimit("nproc=1024")
2310 .volumes_from("data-container")
2311 .volumes_from("config-container:ro")
2312 .link("db:database")
2313 .link("cache:redis")
2314 .link_local_ip("169.254.1.1")
2315 .link_local_ip("fe80::1");
2316
2317 let args = cmd.build_args();
2318
2319 assert!(args.contains(&"--network-alias".to_string()));
2321 assert!(args.contains(&"web".to_string()));
2322 assert!(args.contains(&"frontend".to_string()));
2323
2324 assert!(args.contains(&"--group-add".to_string()));
2326 assert!(args.contains(&"staff".to_string()));
2327 assert!(args.contains(&"docker".to_string()));
2328
2329 assert!(args.contains(&"--attach".to_string()));
2331 assert!(args.contains(&"stdout".to_string()));
2332 assert!(args.contains(&"stderr".to_string()));
2333
2334 assert!(args.contains(&"--log-opt".to_string()));
2336 assert!(args.contains(&"max-size=10m".to_string()));
2337 assert!(args.contains(&"max-file=3".to_string()));
2338
2339 assert!(args.contains(&"--storage-opt".to_string()));
2341 assert!(args.contains(&"size=20G".to_string()));
2342
2343 assert!(args.contains(&"--ulimit".to_string()));
2345 assert!(args.contains(&"nofile=1024:65536".to_string()));
2346 assert!(args.contains(&"nproc=1024".to_string()));
2347
2348 assert!(args.contains(&"--volumes-from".to_string()));
2350 assert!(args.contains(&"data-container".to_string()));
2351 assert!(args.contains(&"config-container:ro".to_string()));
2352
2353 assert!(args.contains(&"--link".to_string()));
2355 assert!(args.contains(&"db:database".to_string()));
2356 assert!(args.contains(&"cache:redis".to_string()));
2357
2358 assert!(args.contains(&"--link-local-ip".to_string()));
2360 assert!(args.contains(&"169.254.1.1".to_string()));
2361 assert!(args.contains(&"fe80::1".to_string()));
2362 }
2363
2364 #[test]
2365 fn test_run_command_additional_list_individual_options() {
2366 let network_cmd = RunCommand::new("alpine:latest").network_alias("api");
2368 let network_args = network_cmd.build_args();
2369 assert!(network_args.contains(&"--network-alias".to_string()));
2370 assert!(network_args.contains(&"api".to_string()));
2371
2372 let group_cmd = RunCommand::new("alpine:latest").group_add("wheel");
2373 let group_args = group_cmd.build_args();
2374 assert!(group_args.contains(&"--group-add".to_string()));
2375 assert!(group_args.contains(&"wheel".to_string()));
2376
2377 let attach_cmd = RunCommand::new("alpine:latest").attach("stdin");
2378 let attach_args = attach_cmd.build_args();
2379 assert!(attach_args.contains(&"--attach".to_string()));
2380 assert!(attach_args.contains(&"stdin".to_string()));
2381
2382 let log_cmd = RunCommand::new("alpine:latest").log_opt("compress=true");
2383 let log_args = log_cmd.build_args();
2384 assert!(log_args.contains(&"--log-opt".to_string()));
2385 assert!(log_args.contains(&"compress=true".to_string()));
2386
2387 let storage_cmd =
2388 RunCommand::new("alpine:latest").storage_opt("dm.thinpooldev=/dev/mapper/thin-pool");
2389 let storage_args = storage_cmd.build_args();
2390 assert!(storage_args.contains(&"--storage-opt".to_string()));
2391 assert!(storage_args.contains(&"dm.thinpooldev=/dev/mapper/thin-pool".to_string()));
2392
2393 let ulimit_cmd = RunCommand::new("alpine:latest").ulimit("memlock=-1:-1");
2394 let ulimit_args = ulimit_cmd.build_args();
2395 assert!(ulimit_args.contains(&"--ulimit".to_string()));
2396 assert!(ulimit_args.contains(&"memlock=-1:-1".to_string()));
2397
2398 let volumes_cmd = RunCommand::new("alpine:latest").volumes_from("shared-data");
2399 let volumes_args = volumes_cmd.build_args();
2400 assert!(volumes_args.contains(&"--volumes-from".to_string()));
2401 assert!(volumes_args.contains(&"shared-data".to_string()));
2402
2403 let link_cmd = RunCommand::new("alpine:latest").link("mysql:db");
2404 let link_args = link_cmd.build_args();
2405 assert!(link_args.contains(&"--link".to_string()));
2406 assert!(link_args.contains(&"mysql:db".to_string()));
2407
2408 let ip_cmd = RunCommand::new("alpine:latest").link_local_ip("169.254.100.1");
2409 let ip_args = ip_cmd.build_args();
2410 assert!(ip_args.contains(&"--link-local-ip".to_string()));
2411 assert!(ip_args.contains(&"169.254.100.1".to_string()));
2412 }
2413
2414 #[test]
2415 fn test_run_command_health_check_options() {
2416 let cmd = RunCommand::new("nginx:latest")
2417 .health_cmd("curl -f http://localhost/ || exit 1")
2418 .health_interval("30s")
2419 .health_retries(3)
2420 .health_timeout("5s")
2421 .health_start_period("60s")
2422 .health_start_interval("5s");
2423
2424 let args = cmd.build_args();
2425
2426 assert!(args.contains(&"--health-cmd".to_string()));
2428 assert!(args.contains(&"curl -f http://localhost/ || exit 1".to_string()));
2429 assert!(args.contains(&"--health-interval".to_string()));
2430 assert!(args.contains(&"30s".to_string()));
2431 assert!(args.contains(&"--health-retries".to_string()));
2432 assert!(args.contains(&"3".to_string()));
2433 assert!(args.contains(&"--health-timeout".to_string()));
2434 assert!(args.contains(&"5s".to_string()));
2435 assert!(args.contains(&"--health-start-period".to_string()));
2436 assert!(args.contains(&"60s".to_string()));
2437 assert!(args.contains(&"--health-start-interval".to_string()));
2438 }
2440
2441 #[test]
2442 fn test_run_command_advanced_mount_network_options() {
2443 let cmd = RunCommand::new("alpine:latest")
2444 .mount("type=bind,source=/host/path,target=/container/path")
2445 .mount("type=volume,source=data-vol,target=/data")
2446 .network("frontend")
2447 .network("backend")
2448 .gpus("all")
2449 .annotation("io.kubernetes.cri-o.Devices", "/dev/fuse")
2450 .annotation("io.kubernetes.cri-o.ShmSize", "64m")
2451 .sysctl("net.core.somaxconn", "1024")
2452 .sysctl("kernel.shm_rmid_forced", "1");
2453
2454 let args = cmd.build_args();
2455
2456 assert!(args.contains(&"--mount".to_string()));
2458 assert!(args.contains(&"type=bind,source=/host/path,target=/container/path".to_string()));
2459 assert!(args.contains(&"type=volume,source=data-vol,target=/data".to_string()));
2460
2461 assert!(args.contains(&"--network".to_string()));
2463 assert!(args.contains(&"frontend".to_string()));
2464 assert!(args.contains(&"backend".to_string()));
2465
2466 assert!(args.contains(&"--gpus".to_string()));
2468 assert!(args.contains(&"all".to_string()));
2469
2470 assert!(args.contains(&"--annotation".to_string()));
2472 assert!(args.contains(&"io.kubernetes.cri-o.Devices=/dev/fuse".to_string()));
2473 assert!(args.contains(&"io.kubernetes.cri-o.ShmSize=64m".to_string()));
2474
2475 assert!(args.contains(&"--sysctl".to_string()));
2477 assert!(args.contains(&"net.core.somaxconn=1024".to_string()));
2478 assert!(args.contains(&"kernel.shm_rmid_forced=1".to_string()));
2479 }
2480
2481 #[test]
2482 fn test_run_command_health_advanced_individual_options() {
2483 let health_cmd = RunCommand::new("alpine:latest").health_cmd("ping -c 1 localhost");
2485 let health_args = health_cmd.build_args();
2486 assert!(health_args.contains(&"--health-cmd".to_string()));
2487 assert!(health_args.contains(&"ping -c 1 localhost".to_string()));
2488
2489 let health_interval = RunCommand::new("alpine:latest").health_interval("10s");
2490 let interval_args = health_interval.build_args();
2491 assert!(interval_args.contains(&"--health-interval".to_string()));
2492 assert!(interval_args.contains(&"10s".to_string()));
2493
2494 let health_retries = RunCommand::new("alpine:latest").health_retries(5);
2495 let retries_args = health_retries.build_args();
2496 assert!(retries_args.contains(&"--health-retries".to_string()));
2497 assert!(retries_args.contains(&"5".to_string()));
2498
2499 let mount_cmd = RunCommand::new("alpine:latest").mount("type=tmpfs,destination=/app");
2500 let mount_args = mount_cmd.build_args();
2501 assert!(mount_args.contains(&"--mount".to_string()));
2502 assert!(mount_args.contains(&"type=tmpfs,destination=/app".to_string()));
2503
2504 let network_cmd = RunCommand::new("alpine:latest").network("my-network");
2505 let network_args = network_cmd.build_args();
2506 assert!(network_args.contains(&"--network".to_string()));
2507 assert!(network_args.contains(&"my-network".to_string()));
2508
2509 let gpu_cmd = RunCommand::new("alpine:latest").gpus("device=0");
2510 let gpu_args = gpu_cmd.build_args();
2511 assert!(gpu_args.contains(&"--gpus".to_string()));
2512 assert!(gpu_args.contains(&"device=0".to_string()));
2513
2514 let annotation_cmd = RunCommand::new("alpine:latest").annotation("key", "value");
2515 let annotation_args = annotation_cmd.build_args();
2516 assert!(annotation_args.contains(&"--annotation".to_string()));
2517 assert!(annotation_args.contains(&"key=value".to_string()));
2518
2519 let sysctl_cmd = RunCommand::new("alpine:latest").sysctl("net.ipv4.ip_forward", "1");
2520 let sysctl_args = sysctl_cmd.build_args();
2521 assert!(sysctl_args.contains(&"--sysctl".to_string()));
2522 assert!(sysctl_args.contains(&"net.ipv4.ip_forward=1".to_string()));
2523 }
2524
2525 #[test]
2526 fn test_run_command_comprehensive_health_advanced_integration() {
2527 let cmd = RunCommand::new("web-app:latest")
2528 .name("production-web-app")
2529 .health_cmd("curl -f http://localhost:8080/health || exit 1")
2531 .health_interval("30s")
2532 .health_retries(3)
2533 .health_timeout("10s")
2534 .health_start_period("120s")
2535 .mount("type=bind,source=/var/log/app,target=/app/logs")
2537 .mount("type=volume,source=app-data,target=/app/data")
2538 .network("frontend")
2539 .network("backend")
2540 .gpus("device=0,1")
2542 .annotation(
2544 "io.kubernetes.container.apparmor.security.beta.kubernetes.io/app",
2545 "runtime/default",
2546 )
2547 .annotation(
2548 "io.kubernetes.container.seccomp.security.alpha.kubernetes.io/app",
2549 "runtime/default",
2550 )
2551 .sysctl("net.core.somaxconn", "65535")
2553 .sysctl("net.ipv4.tcp_keepalive_time", "600")
2554 .port(8080, 8080)
2556 .env("NODE_ENV", "production")
2557 .memory("2g")
2558 .cpus("2.0")
2559 .restart("unless-stopped")
2560 .detach();
2561
2562 let args = cmd.build_args();
2563
2564 assert!(args.contains(&"--health-cmd".to_string()));
2566 assert!(args.contains(&"--health-interval".to_string()));
2567 assert!(args.contains(&"--health-retries".to_string()));
2568 assert!(args.contains(&"--health-timeout".to_string()));
2569 assert!(args.contains(&"--health-start-period".to_string()));
2570 assert!(args.contains(&"--mount".to_string()));
2571 assert!(args.contains(&"--network".to_string()));
2572 assert!(args.contains(&"--gpus".to_string()));
2573 assert!(args.contains(&"--annotation".to_string()));
2574 assert!(args.contains(&"--sysctl".to_string()));
2575
2576 let image_pos = args.iter().position(|x| x == "web-app:latest").unwrap();
2578 assert!(image_pos > 0);
2579 }
2580
2581 #[test]
2582 fn test_run_command_block_io_controls() {
2583 let cmd = RunCommand::new("alpine:latest")
2584 .blkio_weight(500)
2585 .blkio_weight_device("/dev/sda:300")
2586 .blkio_weight_device("/dev/sdb:700")
2587 .device_read_bps("/dev/sda:50mb")
2588 .device_write_bps("/dev/sda:30mb")
2589 .device_read_iops("/dev/sda:1000")
2590 .device_write_iops("/dev/sda:800");
2591
2592 let args = cmd.build_args();
2593
2594 assert!(args.contains(&"--blkio-weight".to_string()));
2596 assert!(args.contains(&"500".to_string()));
2597
2598 assert!(args.contains(&"--blkio-weight-device".to_string()));
2600 assert!(args.contains(&"/dev/sda:300".to_string()));
2601 assert!(args.contains(&"/dev/sdb:700".to_string()));
2602
2603 assert!(args.contains(&"--device-read-bps".to_string()));
2605 assert!(args.contains(&"/dev/sda:50mb".to_string()));
2606 assert!(args.contains(&"--device-write-bps".to_string()));
2607 assert!(args.contains(&"/dev/sda:30mb".to_string()));
2608
2609 assert!(args.contains(&"--device-read-iops".to_string()));
2611 assert!(args.contains(&"/dev/sda:1000".to_string()));
2612 assert!(args.contains(&"--device-write-iops".to_string()));
2613 assert!(args.contains(&"/dev/sda:800".to_string()));
2614 }
2615
2616 #[test]
2617 fn test_run_command_realtime_cpu_networking() {
2618 let cmd = RunCommand::new("alpine:latest")
2619 .cpu_rt_period(1_000_000)
2620 .cpu_rt_runtime(950_000)
2621 .ip("172.30.100.104")
2622 .ip6("2001:db8::33")
2623 .device_cgroup_rule("c 1:3 mr")
2624 .device_cgroup_rule("a 7:* rmw");
2625
2626 let args = cmd.build_args();
2627
2628 assert!(args.contains(&"--cpu-rt-period".to_string()));
2630 assert!(args.contains(&"1000000".to_string()));
2631 assert!(args.contains(&"--cpu-rt-runtime".to_string()));
2632 assert!(args.contains(&"950000".to_string()));
2633
2634 assert!(args.contains(&"--ip".to_string()));
2636 assert!(args.contains(&"172.30.100.104".to_string()));
2637 assert!(args.contains(&"--ip6".to_string()));
2638 assert!(args.contains(&"2001:db8::33".to_string()));
2639
2640 assert!(args.contains(&"--device-cgroup-rule".to_string()));
2642 assert!(args.contains(&"c 1:3 mr".to_string()));
2643 assert!(args.contains(&"a 7:* rmw".to_string()));
2644 }
2645
2646 #[test]
2647 fn test_run_command_advanced_system_individual_options() {
2648 let blkio_cmd = RunCommand::new("alpine:latest").blkio_weight(100);
2650 let blkio_args = blkio_cmd.build_args();
2651 assert!(blkio_args.contains(&"--blkio-weight".to_string()));
2652 assert!(blkio_args.contains(&"100".to_string()));
2653
2654 let weight_device_cmd =
2655 RunCommand::new("alpine:latest").blkio_weight_device("/dev/sda:500");
2656 let weight_device_args = weight_device_cmd.build_args();
2657 assert!(weight_device_args.contains(&"--blkio-weight-device".to_string()));
2658 assert!(weight_device_args.contains(&"/dev/sda:500".to_string()));
2659
2660 let read_bps_cmd = RunCommand::new("alpine:latest").device_read_bps("/dev/sda:1mb");
2661 let read_bps_args = read_bps_cmd.build_args();
2662 assert!(read_bps_args.contains(&"--device-read-bps".to_string()));
2663 assert!(read_bps_args.contains(&"/dev/sda:1mb".to_string()));
2664
2665 let write_bps_cmd = RunCommand::new("alpine:latest").device_write_bps("/dev/sda:1mb");
2666 let write_bps_args = write_bps_cmd.build_args();
2667 assert!(write_bps_args.contains(&"--device-write-bps".to_string()));
2668 assert!(write_bps_args.contains(&"/dev/sda:1mb".to_string()));
2669
2670 let read_iops_cmd = RunCommand::new("alpine:latest").device_read_iops("/dev/sda:100");
2671 let read_iops_args = read_iops_cmd.build_args();
2672 assert!(read_iops_args.contains(&"--device-read-iops".to_string()));
2673 assert!(read_iops_args.contains(&"/dev/sda:100".to_string()));
2674
2675 let write_iops_cmd = RunCommand::new("alpine:latest").device_write_iops("/dev/sda:100");
2676 let write_iops_args = write_iops_cmd.build_args();
2677 assert!(write_iops_args.contains(&"--device-write-iops".to_string()));
2678 assert!(write_iops_args.contains(&"/dev/sda:100".to_string()));
2679
2680 let rt_period_cmd = RunCommand::new("alpine:latest").cpu_rt_period(100_000);
2681 let rt_period_args = rt_period_cmd.build_args();
2682 assert!(rt_period_args.contains(&"--cpu-rt-period".to_string()));
2683 assert!(rt_period_args.contains(&"100000".to_string()));
2684
2685 let rt_runtime_cmd = RunCommand::new("alpine:latest").cpu_rt_runtime(95_000);
2686 let rt_runtime_args = rt_runtime_cmd.build_args();
2687 assert!(rt_runtime_args.contains(&"--cpu-rt-runtime".to_string()));
2688 assert!(rt_runtime_args.contains(&"95000".to_string()));
2689
2690 let ip_cmd = RunCommand::new("alpine:latest").ip("192.168.1.100");
2691 let ip_args = ip_cmd.build_args();
2692 assert!(ip_args.contains(&"--ip".to_string()));
2693 assert!(ip_args.contains(&"192.168.1.100".to_string()));
2694
2695 let ipv6_cmd = RunCommand::new("alpine:latest").ip6("fe80::1");
2696 let ipv6_args = ipv6_cmd.build_args();
2697 assert!(ipv6_args.contains(&"--ip6".to_string()));
2698 assert!(ipv6_args.contains(&"fe80::1".to_string()));
2699
2700 let cgroup_rule_cmd = RunCommand::new("alpine:latest").device_cgroup_rule("c 1:1 rwm");
2701 let cgroup_rule_args = cgroup_rule_cmd.build_args();
2702 assert!(cgroup_rule_args.contains(&"--device-cgroup-rule".to_string()));
2703 assert!(cgroup_rule_args.contains(&"c 1:1 rwm".to_string()));
2704 }
2705
2706 #[test]
2707 #[allow(clippy::too_many_lines)]
2708 fn test_run_command_complete_100_percent_coverage() {
2709 use std::path::PathBuf;
2710
2711 let cmd = RunCommand::new("enterprise-app:latest")
2713 .name("production-enterprise")
2714 .detach()
2716 .interactive()
2717 .tty()
2718 .remove()
2719 .env("NODE_ENV", "production")
2721 .port(8080, 8080)
2722 .volume("/data", "/app/data")
2723 .workdir("/app")
2724 .entrypoint("/app/start.sh")
2725 .memory("4g")
2727 .cpus("2.0")
2728 .cpu_shares(1024)
2729 .cpu_period(100_000)
2730 .cpu_quota(50000)
2731 .cpuset_cpus("0-1")
2732 .cpuset_mems("0")
2733 .memory_swap("8g")
2734 .memory_reservation("2g")
2735 .user("app:app")
2737 .privileged()
2738 .hostname("enterprise-app")
2739 .restart("unless-stopped")
2741 .platform("linux/amd64")
2743 .runtime("runc")
2744 .isolation("default")
2745 .pull("always")
2746 .cidfile("/tmp/container.cid")
2747 .domainname("enterprise.local")
2748 .mac_address("02:42:ac:11:00:02")
2749 .log_driver("json-file")
2751 .volume_driver("local")
2752 .userns("host")
2754 .uts("host")
2755 .pid("host")
2756 .ipc("host")
2757 .cgroupns("host")
2758 .cgroup_parent("/docker")
2759 .kernel_memory("1g")
2761 .memory_swappiness(10)
2762 .oom_score_adj(-500)
2763 .pids_limit(1000)
2764 .shm_size("64m")
2765 .stop_signal("SIGTERM")
2767 .stop_timeout(30)
2768 .detach_keys("ctrl-p,ctrl-q")
2769 .no_sig_proxy()
2771 .read_only()
2772 .init()
2773 .oom_kill_disable()
2774 .no_healthcheck()
2775 .enable_content_trust()
2776 .publish_all()
2777 .quiet()
2778 .dns("8.8.8.8")
2780 .dns_option("ndots:2")
2781 .dns_search("enterprise.local")
2782 .add_host("api.enterprise.local:10.0.1.100")
2783 .cap_add("NET_ADMIN")
2784 .cap_drop("ALL")
2785 .security_opt("no-new-privileges:true")
2786 .device("/dev/null")
2787 .tmpfs("/tmp:size=100m")
2788 .expose("9090")
2789 .env_file(PathBuf::from(".env.production"))
2790 .label("app=enterprise")
2791 .label_file(PathBuf::from("labels.txt"))
2792 .network_alias("enterprise-primary")
2794 .group_add("staff")
2795 .attach("stdout")
2796 .log_opt("max-size=10m")
2797 .storage_opt("size=100G")
2798 .ulimit("nofile=65536:65536")
2799 .volumes_from("data-container")
2800 .link("db:database")
2801 .link_local_ip("169.254.1.1")
2802 .health_cmd("curl -f http://localhost:8080/health || exit 1")
2804 .health_interval("30s")
2805 .health_retries(3)
2806 .health_timeout("10s")
2807 .health_start_period("60s")
2808 .health_start_interval("5s")
2809 .mount("type=bind,source=/host/config,target=/app/config")
2810 .network("enterprise-net")
2811 .gpus("device=0")
2812 .annotation("io.kubernetes.cri-o.TTY", "true")
2813 .sysctl("net.core.somaxconn", "65535")
2814 .blkio_weight(500)
2816 .blkio_weight_device("/dev/sda:300")
2817 .device_read_bps("/dev/sda:100mb")
2818 .device_write_bps("/dev/sda:50mb")
2819 .device_read_iops("/dev/sda:1000")
2820 .device_write_iops("/dev/sda:500")
2821 .cpu_rt_period(1_000_000)
2822 .cpu_rt_runtime(950_000)
2823 .ip("10.0.1.50")
2824 .ip6("2001:db8::50")
2825 .device_cgroup_rule("c 1:1 rwm");
2826
2827 let args = cmd.build_args();
2828
2829 assert!(args.len() > 150); assert!(args.contains(&"--detach".to_string()));
2834 assert!(args.contains(&"--memory".to_string()));
2835 assert!(args.contains(&"--dns".to_string()));
2836 assert!(args.contains(&"--network-alias".to_string()));
2837 assert!(args.contains(&"--health-cmd".to_string()));
2838 assert!(args.contains(&"--blkio-weight".to_string()));
2839
2840 let image_pos = args
2842 .iter()
2843 .position(|x| x == "enterprise-app:latest")
2844 .unwrap();
2845 assert!(image_pos > 100); assert_eq!(args[args.len() - 1], "enterprise-app:latest");
2847
2848 println!("🎉 COMPLETE! All 96 Docker run options implemented and tested!");
2849 }
2850}