1use super::{CommandExecutor, DockerCommand, EnvironmentBuilder, PortBuilder};
7use crate::error::{Error, Result};
8use crate::stream::{OutputLine, StreamResult, StreamableCommand};
9use async_trait::async_trait;
10use std::path::PathBuf;
11use tokio::process::Command as TokioCommand;
12use tokio::sync::mpsc;
13
14#[derive(Debug, Clone)]
16#[allow(clippy::struct_excessive_bools)]
17pub struct RunCommand {
18 image: String,
20 pub executor: CommandExecutor,
22 name: Option<String>,
24 detach: bool,
26 environment: EnvironmentBuilder,
28 ports: PortBuilder,
30 volumes: Vec<VolumeMount>,
32 workdir: Option<PathBuf>,
34 entrypoint: Option<String>,
36 command: Option<Vec<String>>,
38 interactive: bool,
40 tty: bool,
42 remove: bool,
44
45 memory: Option<String>,
48 cpus: Option<String>,
50 cpu_shares: Option<i64>,
52 cpu_period: Option<i64>,
54 cpu_quota: Option<i64>,
56 cpuset_cpus: Option<String>,
58 cpuset_mems: Option<String>,
60 memory_swap: Option<String>,
62 memory_reservation: Option<String>,
64
65 user: Option<String>,
68 privileged: bool,
70 hostname: Option<String>,
72
73 restart: Option<String>,
76
77 platform: Option<String>,
80 runtime: Option<String>,
82 isolation: Option<String>,
84 pull: Option<String>,
86 cidfile: Option<String>,
88 domainname: Option<String>,
90 mac_address: Option<String>,
92
93 log_driver: Option<String>,
96 volume_driver: Option<String>,
98
99 userns: Option<String>,
102 uts: Option<String>,
104 pid: Option<String>,
106 ipc: Option<String>,
108 cgroupns: Option<String>,
110 cgroup_parent: Option<String>,
112
113 kernel_memory: Option<String>,
116 memory_swappiness: Option<i32>,
118 oom_score_adj: Option<i32>,
120 pids_limit: Option<i64>,
122 shm_size: Option<String>,
124
125 stop_signal: Option<String>,
128 stop_timeout: Option<i32>,
130 detach_keys: Option<String>,
132
133 sig_proxy: bool,
136 read_only: bool,
138 init: bool,
140 oom_kill_disable: bool,
142 no_healthcheck: bool,
144 disable_content_trust: bool,
146 publish_all: bool,
148 quiet: bool,
150
151 dns: Vec<String>,
155 dns_option: Vec<String>,
157 dns_search: Vec<String>,
159 add_host: Vec<String>,
161
162 cap_add: Vec<String>,
165 cap_drop: Vec<String>,
167 security_opt: Vec<String>,
169
170 device: Vec<String>,
173 tmpfs: Vec<String>,
175 expose: Vec<String>,
177
178 env_file: Vec<PathBuf>,
181 label: Vec<String>,
183 label_file: Vec<PathBuf>,
185
186 network_alias: Vec<String>,
189 group_add: Vec<String>,
191 attach: Vec<String>,
193 log_opt: Vec<String>,
195 storage_opt: Vec<String>,
197 ulimit: Vec<String>,
199 volumes_from: Vec<String>,
201 link: Vec<String>,
203 link_local_ip: Vec<String>,
205
206 health_cmd: Option<String>,
210 health_interval: Option<String>,
212 health_retries: Option<i32>,
214 health_timeout: Option<String>,
216 health_start_period: Option<String>,
218 health_start_interval: Option<String>,
220
221 mount: Vec<String>,
224 network: Vec<String>,
226 gpus: Option<String>,
228
229 annotation: Vec<String>,
232 sysctl: Vec<String>,
234
235 blkio_weight: Option<u16>,
239 blkio_weight_device: Vec<String>,
241 device_read_bps: Vec<String>,
243 device_write_bps: Vec<String>,
245 device_read_iops: Vec<String>,
247 device_write_iops: Vec<String>,
249
250 cpu_rt_period: Option<i64>,
253 cpu_rt_runtime: Option<i64>,
255
256 ip: Option<String>,
259 ip6: Option<String>,
261
262 device_cgroup_rule: Vec<String>,
265}
266
267#[derive(Debug, Clone)]
269pub struct VolumeMount {
270 pub source: String,
272 pub target: String,
274 pub mount_type: MountType,
276 pub readonly: bool,
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
282pub enum MountType {
283 Bind,
285 Volume,
287 Tmpfs,
289}
290
291impl std::fmt::Display for VolumeMount {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 let readonly_suffix = if self.readonly { ":ro" } else { "" };
294 write!(f, "{}:{}{}", self.source, self.target, readonly_suffix)
295 }
296}
297
298#[derive(Debug, Clone, PartialEq, Eq)]
300pub struct ContainerId(pub String);
301
302impl ContainerId {
303 #[must_use]
305 pub fn as_str(&self) -> &str {
306 &self.0
307 }
308
309 #[must_use]
311 pub fn short(&self) -> &str {
312 if self.0.len() >= 12 {
313 &self.0[..12]
314 } else {
315 &self.0
316 }
317 }
318}
319
320impl std::fmt::Display for ContainerId {
321 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322 write!(f, "{}", self.0)
323 }
324}
325
326impl RunCommand {
327 #[allow(clippy::too_many_lines)]
329 pub fn new(image: impl Into<String>) -> Self {
330 Self {
331 image: image.into(),
332 executor: CommandExecutor::new(),
333 name: None,
334 detach: false,
335 environment: EnvironmentBuilder::new(),
336 ports: PortBuilder::new(),
337 volumes: Vec::new(),
338 workdir: None,
339 entrypoint: None,
340 command: None,
341 interactive: false,
342 tty: false,
343 remove: false,
344
345 memory: None,
347 cpus: None,
348 cpu_shares: None,
349 cpu_period: None,
350 cpu_quota: None,
351 cpuset_cpus: None,
352 cpuset_mems: None,
353 memory_swap: None,
354 memory_reservation: None,
355
356 user: None,
358 privileged: false,
359 hostname: None,
360
361 restart: None,
363
364 platform: None,
366 runtime: None,
367 isolation: None,
368 pull: None,
369 cidfile: None,
370 domainname: None,
371 mac_address: None,
372
373 log_driver: None,
375 volume_driver: None,
376
377 userns: None,
379 uts: None,
380 pid: None,
381 ipc: None,
382 cgroupns: None,
383 cgroup_parent: None,
384
385 kernel_memory: None,
387 memory_swappiness: None,
388 oom_score_adj: None,
389 pids_limit: None,
390 shm_size: None,
391
392 stop_signal: None,
394 stop_timeout: None,
395 detach_keys: None,
396
397 sig_proxy: true, read_only: false,
400 init: false,
401 oom_kill_disable: false,
402 no_healthcheck: false,
403 disable_content_trust: true, publish_all: false,
405 quiet: false,
406
407 dns: Vec::new(),
410 dns_option: Vec::new(),
411 dns_search: Vec::new(),
412 add_host: Vec::new(),
413
414 cap_add: Vec::new(),
416 cap_drop: Vec::new(),
417 security_opt: Vec::new(),
418
419 device: Vec::new(),
421 tmpfs: Vec::new(),
422 expose: Vec::new(),
423
424 env_file: Vec::new(),
426 label: Vec::new(),
427 label_file: Vec::new(),
428
429 network_alias: Vec::new(),
431 group_add: Vec::new(),
432 attach: Vec::new(),
433 log_opt: Vec::new(),
434 storage_opt: Vec::new(),
435 ulimit: Vec::new(),
436 volumes_from: Vec::new(),
437 link: Vec::new(),
438 link_local_ip: Vec::new(),
439
440 health_cmd: None,
442 health_interval: None,
443 health_retries: None,
444 health_timeout: None,
445 health_start_period: None,
446 health_start_interval: None,
447 mount: Vec::new(),
448 network: Vec::new(),
449 gpus: None,
450 annotation: Vec::new(),
451 sysctl: Vec::new(),
452
453 blkio_weight: None,
455 blkio_weight_device: Vec::new(),
456 device_read_bps: Vec::new(),
457 device_write_bps: Vec::new(),
458 device_read_iops: Vec::new(),
459 device_write_iops: Vec::new(),
460 cpu_rt_period: None,
461 cpu_rt_runtime: None,
462 ip: None,
463 ip6: None,
464 device_cgroup_rule: Vec::new(),
465 }
466 }
467
468 #[must_use]
470 pub fn name(mut self, name: impl Into<String>) -> Self {
471 self.name = Some(name.into());
472 self
473 }
474
475 #[must_use]
477 pub fn detach(mut self) -> Self {
478 self.detach = true;
479 self
480 }
481
482 #[must_use]
484 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
485 self.environment = self.environment.var(key, value);
486 self
487 }
488
489 #[must_use]
491 pub fn envs(mut self, vars: std::collections::HashMap<String, String>) -> Self {
492 self.environment = self.environment.vars(vars);
493 self
494 }
495
496 #[must_use]
498 pub fn port(mut self, host_port: u16, container_port: u16) -> Self {
499 self.ports = self.ports.port(host_port, container_port);
500 self
501 }
502
503 #[must_use]
505 pub fn dynamic_port(mut self, container_port: u16) -> Self {
506 self.ports = self.ports.dynamic_port(container_port);
507 self
508 }
509
510 #[must_use]
512 pub fn volume(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
513 self.volumes.push(VolumeMount {
514 source: source.into(),
515 target: target.into(),
516 mount_type: MountType::Volume,
517 readonly: false,
518 });
519 self
520 }
521
522 #[must_use]
524 pub fn bind(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
525 self.volumes.push(VolumeMount {
526 source: source.into(),
527 target: target.into(),
528 mount_type: MountType::Bind,
529 readonly: false,
530 });
531 self
532 }
533
534 #[must_use]
536 pub fn volume_ro(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
537 self.volumes.push(VolumeMount {
538 source: source.into(),
539 target: target.into(),
540 mount_type: MountType::Volume,
541 readonly: true,
542 });
543 self
544 }
545
546 #[must_use]
548 pub fn workdir(mut self, workdir: impl Into<PathBuf>) -> Self {
549 self.workdir = Some(workdir.into());
550 self
551 }
552
553 #[must_use]
555 pub fn entrypoint(mut self, entrypoint: impl Into<String>) -> Self {
556 self.entrypoint = Some(entrypoint.into());
557 self
558 }
559
560 #[must_use]
562 pub fn cmd(mut self, command: Vec<String>) -> Self {
563 self.command = Some(command);
564 self
565 }
566
567 #[must_use]
569 pub fn interactive(mut self) -> Self {
570 self.interactive = true;
571 self
572 }
573
574 #[must_use]
576 pub fn tty(mut self) -> Self {
577 self.tty = true;
578 self
579 }
580
581 #[must_use]
583 pub fn remove(mut self) -> Self {
584 self.remove = true;
585 self
586 }
587
588 #[must_use]
590 pub fn it(self) -> Self {
591 self.interactive().tty()
592 }
593
594 #[must_use]
597 pub fn memory(mut self, memory: impl Into<String>) -> Self {
598 self.memory = Some(memory.into());
599 self
600 }
601
602 #[must_use]
604 pub fn cpus(mut self, cpus: impl Into<String>) -> Self {
605 self.cpus = Some(cpus.into());
606 self
607 }
608
609 #[must_use]
611 pub fn cpu_shares(mut self, shares: i64) -> Self {
612 self.cpu_shares = Some(shares);
613 self
614 }
615
616 #[must_use]
618 pub fn cpu_period(mut self, period: i64) -> Self {
619 self.cpu_period = Some(period);
620 self
621 }
622
623 #[must_use]
625 pub fn cpu_quota(mut self, quota: i64) -> Self {
626 self.cpu_quota = Some(quota);
627 self
628 }
629
630 #[must_use]
632 pub fn cpuset_cpus(mut self, cpus: impl Into<String>) -> Self {
633 self.cpuset_cpus = Some(cpus.into());
634 self
635 }
636
637 #[must_use]
639 pub fn cpuset_mems(mut self, mems: impl Into<String>) -> Self {
640 self.cpuset_mems = Some(mems.into());
641 self
642 }
643
644 #[must_use]
646 pub fn memory_swap(mut self, swap: impl Into<String>) -> Self {
647 self.memory_swap = Some(swap.into());
648 self
649 }
650
651 #[must_use]
653 pub fn memory_reservation(mut self, reservation: impl Into<String>) -> Self {
654 self.memory_reservation = Some(reservation.into());
655 self
656 }
657
658 #[must_use]
661 pub fn user(mut self, user: impl Into<String>) -> Self {
662 self.user = Some(user.into());
663 self
664 }
665
666 #[must_use]
668 pub fn privileged(mut self) -> Self {
669 self.privileged = true;
670 self
671 }
672
673 #[must_use]
675 pub fn hostname(mut self, hostname: impl Into<String>) -> Self {
676 self.hostname = Some(hostname.into());
677 self
678 }
679
680 #[must_use]
683 pub fn restart(mut self, restart: impl Into<String>) -> Self {
684 self.restart = Some(restart.into());
685 self
686 }
687
688 #[must_use]
691 pub fn platform(mut self, platform: impl Into<String>) -> Self {
692 self.platform = Some(platform.into());
693 self
694 }
695
696 #[must_use]
698 pub fn runtime(mut self, runtime: impl Into<String>) -> Self {
699 self.runtime = Some(runtime.into());
700 self
701 }
702
703 #[must_use]
705 pub fn isolation(mut self, isolation: impl Into<String>) -> Self {
706 self.isolation = Some(isolation.into());
707 self
708 }
709
710 #[must_use]
712 pub fn pull(mut self, pull: impl Into<String>) -> Self {
713 self.pull = Some(pull.into());
714 self
715 }
716
717 #[must_use]
719 pub fn cidfile(mut self, cidfile: impl Into<String>) -> Self {
720 self.cidfile = Some(cidfile.into());
721 self
722 }
723
724 #[must_use]
726 pub fn domainname(mut self, domainname: impl Into<String>) -> Self {
727 self.domainname = Some(domainname.into());
728 self
729 }
730
731 #[must_use]
733 pub fn mac_address(mut self, mac: impl Into<String>) -> Self {
734 self.mac_address = Some(mac.into());
735 self
736 }
737
738 #[must_use]
741 pub fn log_driver(mut self, driver: impl Into<String>) -> Self {
742 self.log_driver = Some(driver.into());
743 self
744 }
745
746 #[must_use]
748 pub fn volume_driver(mut self, driver: impl Into<String>) -> Self {
749 self.volume_driver = Some(driver.into());
750 self
751 }
752
753 #[must_use]
756 pub fn userns(mut self, userns: impl Into<String>) -> Self {
757 self.userns = Some(userns.into());
758 self
759 }
760
761 #[must_use]
763 pub fn uts(mut self, uts: impl Into<String>) -> Self {
764 self.uts = Some(uts.into());
765 self
766 }
767
768 #[must_use]
770 pub fn pid(mut self, pid: impl Into<String>) -> Self {
771 self.pid = Some(pid.into());
772 self
773 }
774
775 #[must_use]
777 pub fn ipc(mut self, ipc: impl Into<String>) -> Self {
778 self.ipc = Some(ipc.into());
779 self
780 }
781
782 #[must_use]
784 pub fn cgroupns(mut self, cgroupns: impl Into<String>) -> Self {
785 self.cgroupns = Some(cgroupns.into());
786 self
787 }
788
789 #[must_use]
791 pub fn cgroup_parent(mut self, parent: impl Into<String>) -> Self {
792 self.cgroup_parent = Some(parent.into());
793 self
794 }
795
796 #[must_use]
799 pub fn kernel_memory(mut self, memory: impl Into<String>) -> Self {
800 self.kernel_memory = Some(memory.into());
801 self
802 }
803
804 #[must_use]
806 pub fn memory_swappiness(mut self, swappiness: i32) -> Self {
807 self.memory_swappiness = Some(swappiness);
808 self
809 }
810
811 #[must_use]
813 pub fn oom_score_adj(mut self, score: i32) -> Self {
814 self.oom_score_adj = Some(score);
815 self
816 }
817
818 #[must_use]
820 pub fn pids_limit(mut self, limit: i64) -> Self {
821 self.pids_limit = Some(limit);
822 self
823 }
824
825 #[must_use]
827 pub fn shm_size(mut self, size: impl Into<String>) -> Self {
828 self.shm_size = Some(size.into());
829 self
830 }
831
832 #[must_use]
835 pub fn stop_signal(mut self, signal: impl Into<String>) -> Self {
836 self.stop_signal = Some(signal.into());
837 self
838 }
839
840 #[must_use]
842 pub fn stop_timeout(mut self, timeout: i32) -> Self {
843 self.stop_timeout = Some(timeout);
844 self
845 }
846
847 #[must_use]
849 pub fn detach_keys(mut self, keys: impl Into<String>) -> Self {
850 self.detach_keys = Some(keys.into());
851 self
852 }
853
854 #[must_use]
857 pub fn no_sig_proxy(mut self) -> Self {
858 self.sig_proxy = false;
859 self
860 }
861
862 #[must_use]
864 pub fn read_only(mut self) -> Self {
865 self.read_only = true;
866 self
867 }
868
869 #[must_use]
871 pub fn init(mut self) -> Self {
872 self.init = true;
873 self
874 }
875
876 #[must_use]
878 pub fn oom_kill_disable(mut self) -> Self {
879 self.oom_kill_disable = true;
880 self
881 }
882
883 #[must_use]
885 pub fn no_healthcheck(mut self) -> Self {
886 self.no_healthcheck = true;
887 self
888 }
889
890 #[must_use]
892 pub fn enable_content_trust(mut self) -> Self {
893 self.disable_content_trust = false;
894 self
895 }
896
897 #[must_use]
899 pub fn publish_all(mut self) -> Self {
900 self.publish_all = true;
901 self
902 }
903
904 #[must_use]
906 pub fn quiet(mut self) -> Self {
907 self.quiet = true;
908 self
909 }
910
911 #[must_use]
916 pub fn dns(mut self, dns: impl Into<String>) -> Self {
917 self.dns.push(dns.into());
918 self
919 }
920
921 #[must_use]
923 pub fn dns_servers(mut self, servers: Vec<String>) -> Self {
924 self.dns.extend(servers);
925 self
926 }
927
928 #[must_use]
930 pub fn dns_option(mut self, option: impl Into<String>) -> Self {
931 self.dns_option.push(option.into());
932 self
933 }
934
935 #[must_use]
937 pub fn dns_search(mut self, domain: impl Into<String>) -> Self {
938 self.dns_search.push(domain.into());
939 self
940 }
941
942 #[must_use]
944 pub fn add_host(mut self, mapping: impl Into<String>) -> Self {
945 self.add_host.push(mapping.into());
946 self
947 }
948
949 #[must_use]
952 pub fn cap_add(mut self, capability: impl Into<String>) -> Self {
953 self.cap_add.push(capability.into());
954 self
955 }
956
957 #[must_use]
959 pub fn cap_drop(mut self, capability: impl Into<String>) -> Self {
960 self.cap_drop.push(capability.into());
961 self
962 }
963
964 #[must_use]
966 pub fn security_opt(mut self, option: impl Into<String>) -> Self {
967 self.security_opt.push(option.into());
968 self
969 }
970
971 #[must_use]
974 pub fn device(mut self, device: impl Into<String>) -> Self {
975 self.device.push(device.into());
976 self
977 }
978
979 #[must_use]
981 pub fn tmpfs(mut self, path: impl Into<String>) -> Self {
982 self.tmpfs.push(path.into());
983 self
984 }
985
986 #[must_use]
988 pub fn expose(mut self, port: impl Into<String>) -> Self {
989 self.expose.push(port.into());
990 self
991 }
992
993 #[must_use]
996 pub fn env_file(mut self, file: impl Into<PathBuf>) -> Self {
997 self.env_file.push(file.into());
998 self
999 }
1000
1001 #[must_use]
1003 pub fn label(mut self, label: impl Into<String>) -> Self {
1004 self.label.push(label.into());
1005 self
1006 }
1007
1008 #[must_use]
1010 pub fn label_file(mut self, file: impl Into<PathBuf>) -> Self {
1011 self.label_file.push(file.into());
1012 self
1013 }
1014
1015 #[must_use]
1019 pub fn network_alias(mut self, alias: impl Into<String>) -> Self {
1020 self.network_alias.push(alias.into());
1021 self
1022 }
1023
1024 #[must_use]
1026 pub fn group_add(mut self, group: impl Into<String>) -> Self {
1027 self.group_add.push(group.into());
1028 self
1029 }
1030
1031 #[must_use]
1033 pub fn attach(mut self, stream: impl Into<String>) -> Self {
1034 self.attach.push(stream.into());
1035 self
1036 }
1037
1038 #[must_use]
1040 pub fn log_opt(mut self, option: impl Into<String>) -> Self {
1041 self.log_opt.push(option.into());
1042 self
1043 }
1044
1045 #[must_use]
1047 pub fn storage_opt(mut self, option: impl Into<String>) -> Self {
1048 self.storage_opt.push(option.into());
1049 self
1050 }
1051
1052 #[must_use]
1054 pub fn ulimit(mut self, limit: impl Into<String>) -> Self {
1055 self.ulimit.push(limit.into());
1056 self
1057 }
1058
1059 #[must_use]
1061 pub fn volumes_from(mut self, container: impl Into<String>) -> Self {
1062 self.volumes_from.push(container.into());
1063 self
1064 }
1065
1066 #[must_use]
1068 pub fn link(mut self, link: impl Into<String>) -> Self {
1069 self.link.push(link.into());
1070 self
1071 }
1072
1073 #[must_use]
1075 pub fn link_local_ip(mut self, ip: impl Into<String>) -> Self {
1076 self.link_local_ip.push(ip.into());
1077 self
1078 }
1079
1080 #[must_use]
1085 pub fn health_cmd(mut self, cmd: impl Into<String>) -> Self {
1086 self.health_cmd = Some(cmd.into());
1087 self
1088 }
1089
1090 #[must_use]
1092 pub fn health_interval(mut self, interval: impl Into<String>) -> Self {
1093 self.health_interval = Some(interval.into());
1094 self
1095 }
1096
1097 #[must_use]
1099 pub fn health_retries(mut self, retries: i32) -> Self {
1100 self.health_retries = Some(retries);
1101 self
1102 }
1103
1104 #[must_use]
1106 pub fn health_timeout(mut self, timeout: impl Into<String>) -> Self {
1107 self.health_timeout = Some(timeout.into());
1108 self
1109 }
1110
1111 #[must_use]
1113 pub fn health_start_period(mut self, period: impl Into<String>) -> Self {
1114 self.health_start_period = Some(period.into());
1115 self
1116 }
1117
1118 #[must_use]
1120 pub fn health_start_interval(mut self, interval: impl Into<String>) -> Self {
1121 self.health_start_interval = Some(interval.into());
1122 self
1123 }
1124
1125 #[must_use]
1128 pub fn mount(mut self, mount: impl Into<String>) -> Self {
1129 self.mount.push(mount.into());
1130 self
1131 }
1132
1133 #[must_use]
1135 pub fn network(mut self, network: impl Into<String>) -> Self {
1136 self.network.push(network.into());
1137 self
1138 }
1139
1140 #[must_use]
1142 pub fn gpus(mut self, gpus: impl Into<String>) -> Self {
1143 self.gpus = Some(gpus.into());
1144 self
1145 }
1146
1147 #[must_use]
1149 pub fn annotation(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1150 self.annotation
1151 .push(format!("{}={}", key.into(), value.into()));
1152 self
1153 }
1154
1155 #[must_use]
1157 pub fn sysctl(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1158 self.sysctl.push(format!("{}={}", key.into(), value.into()));
1159 self
1160 }
1161
1162 #[must_use]
1167 pub fn blkio_weight(mut self, weight: u16) -> Self {
1168 self.blkio_weight = Some(weight);
1169 self
1170 }
1171
1172 #[must_use]
1174 pub fn blkio_weight_device(mut self, device_weight: impl Into<String>) -> Self {
1175 self.blkio_weight_device.push(device_weight.into());
1176 self
1177 }
1178
1179 #[must_use]
1181 pub fn device_read_bps(mut self, device_rate: impl Into<String>) -> Self {
1182 self.device_read_bps.push(device_rate.into());
1183 self
1184 }
1185
1186 #[must_use]
1188 pub fn device_write_bps(mut self, device_rate: impl Into<String>) -> Self {
1189 self.device_write_bps.push(device_rate.into());
1190 self
1191 }
1192
1193 #[must_use]
1195 pub fn device_read_iops(mut self, device_rate: impl Into<String>) -> Self {
1196 self.device_read_iops.push(device_rate.into());
1197 self
1198 }
1199
1200 #[must_use]
1202 pub fn device_write_iops(mut self, device_rate: impl Into<String>) -> Self {
1203 self.device_write_iops.push(device_rate.into());
1204 self
1205 }
1206
1207 #[must_use]
1210 pub fn cpu_rt_period(mut self, period: i64) -> Self {
1211 self.cpu_rt_period = Some(period);
1212 self
1213 }
1214
1215 #[must_use]
1217 pub fn cpu_rt_runtime(mut self, runtime: i64) -> Self {
1218 self.cpu_rt_runtime = Some(runtime);
1219 self
1220 }
1221
1222 #[must_use]
1225 pub fn ip(mut self, ip: impl Into<String>) -> Self {
1226 self.ip = Some(ip.into());
1227 self
1228 }
1229
1230 #[must_use]
1232 pub fn ip6(mut self, ip6: impl Into<String>) -> Self {
1233 self.ip6 = Some(ip6.into());
1234 self
1235 }
1236
1237 #[must_use]
1239 pub fn device_cgroup_rule(mut self, rule: impl Into<String>) -> Self {
1240 self.device_cgroup_rule.push(rule.into());
1241 self
1242 }
1243}
1244
1245#[async_trait]
1246impl DockerCommand for RunCommand {
1247 type Output = ContainerId;
1248
1249 fn get_executor(&self) -> &CommandExecutor {
1250 &self.executor
1251 }
1252
1253 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
1254 &mut self.executor
1255 }
1256
1257 #[allow(clippy::too_many_lines)]
1258 fn build_command_args(&self) -> Vec<String> {
1259 let mut args = vec!["run".to_string()];
1260
1261 if self.detach {
1263 args.push("--detach".to_string());
1264 }
1265 if self.interactive {
1266 args.push("--interactive".to_string());
1267 }
1268 if self.tty {
1269 args.push("--tty".to_string());
1270 }
1271 if self.remove {
1272 args.push("--rm".to_string());
1273 }
1274
1275 if let Some(ref name) = self.name {
1277 args.push("--name".to_string());
1278 args.push(name.clone());
1279 }
1280
1281 if let Some(ref workdir) = self.workdir {
1283 args.push("--workdir".to_string());
1284 args.push(workdir.to_string_lossy().to_string());
1285 }
1286
1287 if let Some(ref entrypoint) = self.entrypoint {
1289 args.push("--entrypoint".to_string());
1290 args.push(entrypoint.clone());
1291 }
1292
1293 args.extend(self.environment.build_args());
1295
1296 args.extend(self.ports.build_args());
1298
1299 for volume in &self.volumes {
1301 args.push("--volume".to_string());
1302 args.push(volume.to_string());
1303 }
1304
1305 if let Some(ref memory) = self.memory {
1307 args.push("--memory".to_string());
1308 args.push(memory.clone());
1309 }
1310 if let Some(ref cpus) = self.cpus {
1311 args.push("--cpus".to_string());
1312 args.push(cpus.clone());
1313 }
1314 if let Some(cpu_shares) = self.cpu_shares {
1315 args.push("--cpu-shares".to_string());
1316 args.push(cpu_shares.to_string());
1317 }
1318 if let Some(cpu_period) = self.cpu_period {
1319 args.push("--cpu-period".to_string());
1320 args.push(cpu_period.to_string());
1321 }
1322 if let Some(cpu_quota) = self.cpu_quota {
1323 args.push("--cpu-quota".to_string());
1324 args.push(cpu_quota.to_string());
1325 }
1326 if let Some(ref cpuset_cpus) = self.cpuset_cpus {
1327 args.push("--cpuset-cpus".to_string());
1328 args.push(cpuset_cpus.clone());
1329 }
1330 if let Some(ref cpuset_mems) = self.cpuset_mems {
1331 args.push("--cpuset-mems".to_string());
1332 args.push(cpuset_mems.clone());
1333 }
1334 if let Some(ref memory_swap) = self.memory_swap {
1335 args.push("--memory-swap".to_string());
1336 args.push(memory_swap.clone());
1337 }
1338 if let Some(ref memory_reservation) = self.memory_reservation {
1339 args.push("--memory-reservation".to_string());
1340 args.push(memory_reservation.clone());
1341 }
1342
1343 if let Some(ref user) = self.user {
1345 args.push("--user".to_string());
1346 args.push(user.clone());
1347 }
1348 if self.privileged {
1349 args.push("--privileged".to_string());
1350 }
1351 if let Some(ref hostname) = self.hostname {
1352 args.push("--hostname".to_string());
1353 args.push(hostname.clone());
1354 }
1355
1356 if let Some(ref restart) = self.restart {
1358 args.push("--restart".to_string());
1359 args.push(restart.clone());
1360 }
1361
1362 if let Some(ref platform) = self.platform {
1364 args.push("--platform".to_string());
1365 args.push(platform.clone());
1366 }
1367 if let Some(ref runtime) = self.runtime {
1368 args.push("--runtime".to_string());
1369 args.push(runtime.clone());
1370 }
1371 if let Some(ref isolation) = self.isolation {
1372 args.push("--isolation".to_string());
1373 args.push(isolation.clone());
1374 }
1375 if let Some(ref pull) = self.pull {
1376 args.push("--pull".to_string());
1377 args.push(pull.clone());
1378 }
1379 if let Some(ref cidfile) = self.cidfile {
1380 args.push("--cidfile".to_string());
1381 args.push(cidfile.clone());
1382 }
1383 if let Some(ref domainname) = self.domainname {
1384 args.push("--domainname".to_string());
1385 args.push(domainname.clone());
1386 }
1387 if let Some(ref mac_address) = self.mac_address {
1388 args.push("--mac-address".to_string());
1389 args.push(mac_address.clone());
1390 }
1391
1392 if let Some(ref log_driver) = self.log_driver {
1394 args.push("--log-driver".to_string());
1395 args.push(log_driver.clone());
1396 }
1397 if let Some(ref volume_driver) = self.volume_driver {
1398 args.push("--volume-driver".to_string());
1399 args.push(volume_driver.clone());
1400 }
1401
1402 if let Some(ref userns) = self.userns {
1404 args.push("--userns".to_string());
1405 args.push(userns.clone());
1406 }
1407 if let Some(ref uts) = self.uts {
1408 args.push("--uts".to_string());
1409 args.push(uts.clone());
1410 }
1411 if let Some(ref pid) = self.pid {
1412 args.push("--pid".to_string());
1413 args.push(pid.clone());
1414 }
1415 if let Some(ref ipc) = self.ipc {
1416 args.push("--ipc".to_string());
1417 args.push(ipc.clone());
1418 }
1419 if let Some(ref cgroupns) = self.cgroupns {
1420 args.push("--cgroupns".to_string());
1421 args.push(cgroupns.clone());
1422 }
1423 if let Some(ref cgroup_parent) = self.cgroup_parent {
1424 args.push("--cgroup-parent".to_string());
1425 args.push(cgroup_parent.clone());
1426 }
1427
1428 if let Some(ref kernel_memory) = self.kernel_memory {
1430 args.push("--kernel-memory".to_string());
1431 args.push(kernel_memory.clone());
1432 }
1433 if let Some(memory_swappiness) = self.memory_swappiness {
1434 args.push("--memory-swappiness".to_string());
1435 args.push(memory_swappiness.to_string());
1436 }
1437 if let Some(oom_score_adj) = self.oom_score_adj {
1438 args.push("--oom-score-adj".to_string());
1439 args.push(oom_score_adj.to_string());
1440 }
1441 if let Some(pids_limit) = self.pids_limit {
1442 args.push("--pids-limit".to_string());
1443 args.push(pids_limit.to_string());
1444 }
1445 if let Some(ref shm_size) = self.shm_size {
1446 args.push("--shm-size".to_string());
1447 args.push(shm_size.clone());
1448 }
1449
1450 if let Some(ref stop_signal) = self.stop_signal {
1452 args.push("--stop-signal".to_string());
1453 args.push(stop_signal.clone());
1454 }
1455 if let Some(stop_timeout) = self.stop_timeout {
1456 args.push("--stop-timeout".to_string());
1457 args.push(stop_timeout.to_string());
1458 }
1459 if let Some(ref detach_keys) = self.detach_keys {
1460 args.push("--detach-keys".to_string());
1461 args.push(detach_keys.clone());
1462 }
1463
1464 if !self.sig_proxy {
1466 args.push("--sig-proxy=false".to_string());
1467 }
1468 if self.read_only {
1469 args.push("--read-only".to_string());
1470 }
1471 if self.init {
1472 args.push("--init".to_string());
1473 }
1474 if self.oom_kill_disable {
1475 args.push("--oom-kill-disable".to_string());
1476 }
1477 if self.no_healthcheck {
1478 args.push("--no-healthcheck".to_string());
1479 }
1480 if !self.disable_content_trust {
1481 args.push("--disable-content-trust=false".to_string());
1482 }
1483 if self.publish_all {
1484 args.push("--publish-all".to_string());
1485 }
1486 if self.quiet {
1487 args.push("--quiet".to_string());
1488 }
1489
1490 for dns in &self.dns {
1493 args.push("--dns".to_string());
1494 args.push(dns.clone());
1495 }
1496 for dns_option in &self.dns_option {
1497 args.push("--dns-option".to_string());
1498 args.push(dns_option.clone());
1499 }
1500 for dns_search in &self.dns_search {
1501 args.push("--dns-search".to_string());
1502 args.push(dns_search.clone());
1503 }
1504 for add_host in &self.add_host {
1505 args.push("--add-host".to_string());
1506 args.push(add_host.clone());
1507 }
1508
1509 for cap_add in &self.cap_add {
1511 args.push("--cap-add".to_string());
1512 args.push(cap_add.clone());
1513 }
1514 for cap_drop in &self.cap_drop {
1515 args.push("--cap-drop".to_string());
1516 args.push(cap_drop.clone());
1517 }
1518 for security_opt in &self.security_opt {
1519 args.push("--security-opt".to_string());
1520 args.push(security_opt.clone());
1521 }
1522
1523 for device in &self.device {
1525 args.push("--device".to_string());
1526 args.push(device.clone());
1527 }
1528 for tmpfs in &self.tmpfs {
1529 args.push("--tmpfs".to_string());
1530 args.push(tmpfs.clone());
1531 }
1532 for expose in &self.expose {
1533 args.push("--expose".to_string());
1534 args.push(expose.clone());
1535 }
1536
1537 for env_file in &self.env_file {
1539 args.push("--env-file".to_string());
1540 args.push(env_file.to_string_lossy().to_string());
1541 }
1542 for label in &self.label {
1543 args.push("--label".to_string());
1544 args.push(label.clone());
1545 }
1546 for label_file in &self.label_file {
1547 args.push("--label-file".to_string());
1548 args.push(label_file.to_string_lossy().to_string());
1549 }
1550
1551 for network_alias in &self.network_alias {
1553 args.push("--network-alias".to_string());
1554 args.push(network_alias.clone());
1555 }
1556 for group_add in &self.group_add {
1557 args.push("--group-add".to_string());
1558 args.push(group_add.clone());
1559 }
1560 for attach in &self.attach {
1561 args.push("--attach".to_string());
1562 args.push(attach.clone());
1563 }
1564 for log_opt in &self.log_opt {
1565 args.push("--log-opt".to_string());
1566 args.push(log_opt.clone());
1567 }
1568 for storage_opt in &self.storage_opt {
1569 args.push("--storage-opt".to_string());
1570 args.push(storage_opt.clone());
1571 }
1572 for ulimit in &self.ulimit {
1573 args.push("--ulimit".to_string());
1574 args.push(ulimit.clone());
1575 }
1576 for volumes_from in &self.volumes_from {
1577 args.push("--volumes-from".to_string());
1578 args.push(volumes_from.clone());
1579 }
1580 for link in &self.link {
1581 args.push("--link".to_string());
1582 args.push(link.clone());
1583 }
1584 for link_local_ip in &self.link_local_ip {
1585 args.push("--link-local-ip".to_string());
1586 args.push(link_local_ip.clone());
1587 }
1588
1589 if let Some(ref health_cmd) = self.health_cmd {
1592 args.push("--health-cmd".to_string());
1593 args.push(health_cmd.clone());
1594 }
1595 if let Some(ref health_interval) = self.health_interval {
1596 args.push("--health-interval".to_string());
1597 args.push(health_interval.clone());
1598 }
1599 if let Some(health_retries) = self.health_retries {
1600 args.push("--health-retries".to_string());
1601 args.push(health_retries.to_string());
1602 }
1603 if let Some(ref health_timeout) = self.health_timeout {
1604 args.push("--health-timeout".to_string());
1605 args.push(health_timeout.clone());
1606 }
1607 if let Some(ref health_start_period) = self.health_start_period {
1608 args.push("--health-start-period".to_string());
1609 args.push(health_start_period.clone());
1610 }
1611 if let Some(ref health_start_interval) = self.health_start_interval {
1612 args.push("--health-start-interval".to_string());
1613 args.push(health_start_interval.clone());
1614 }
1615
1616 for mount in &self.mount {
1618 args.push("--mount".to_string());
1619 args.push(mount.clone());
1620 }
1621 for network in &self.network {
1622 args.push("--network".to_string());
1623 args.push(network.clone());
1624 }
1625 if let Some(ref gpus) = self.gpus {
1626 args.push("--gpus".to_string());
1627 args.push(gpus.clone());
1628 }
1629
1630 for annotation in &self.annotation {
1632 args.push("--annotation".to_string());
1633 args.push(annotation.clone());
1634 }
1635 for sysctl in &self.sysctl {
1636 args.push("--sysctl".to_string());
1637 args.push(sysctl.clone());
1638 }
1639
1640 if let Some(blkio_weight) = self.blkio_weight {
1643 args.push("--blkio-weight".to_string());
1644 args.push(blkio_weight.to_string());
1645 }
1646 for blkio_weight_device in &self.blkio_weight_device {
1647 args.push("--blkio-weight-device".to_string());
1648 args.push(blkio_weight_device.clone());
1649 }
1650 for device_read_bps in &self.device_read_bps {
1651 args.push("--device-read-bps".to_string());
1652 args.push(device_read_bps.clone());
1653 }
1654 for device_write_bps in &self.device_write_bps {
1655 args.push("--device-write-bps".to_string());
1656 args.push(device_write_bps.clone());
1657 }
1658 for device_read_iops in &self.device_read_iops {
1659 args.push("--device-read-iops".to_string());
1660 args.push(device_read_iops.clone());
1661 }
1662 for device_write_iops in &self.device_write_iops {
1663 args.push("--device-write-iops".to_string());
1664 args.push(device_write_iops.clone());
1665 }
1666
1667 if let Some(cpu_rt_period) = self.cpu_rt_period {
1669 args.push("--cpu-rt-period".to_string());
1670 args.push(cpu_rt_period.to_string());
1671 }
1672 if let Some(cpu_rt_runtime) = self.cpu_rt_runtime {
1673 args.push("--cpu-rt-runtime".to_string());
1674 args.push(cpu_rt_runtime.to_string());
1675 }
1676
1677 if let Some(ref ip) = self.ip {
1679 args.push("--ip".to_string());
1680 args.push(ip.clone());
1681 }
1682 if let Some(ref ip6) = self.ip6 {
1683 args.push("--ip6".to_string());
1684 args.push(ip6.clone());
1685 }
1686
1687 for device_cgroup_rule in &self.device_cgroup_rule {
1689 args.push("--device-cgroup-rule".to_string());
1690 args.push(device_cgroup_rule.clone());
1691 }
1692
1693 args.push(self.image.clone());
1695
1696 if let Some(ref command) = self.command {
1698 args.extend(command.clone());
1699 }
1700
1701 args.extend(self.executor.raw_args.clone());
1703
1704 args
1705 }
1706
1707 async fn execute(&self) -> Result<Self::Output> {
1708 let args = self.build_command_args();
1709 let output = self.execute_command(args).await?;
1710
1711 let container_id = output.stdout.trim().to_string();
1713 if container_id.is_empty() {
1714 return Err(Error::parse_error(
1715 "No container ID returned from docker run",
1716 ));
1717 }
1718
1719 Ok(ContainerId(container_id))
1720 }
1721}
1722
1723#[async_trait]
1725impl StreamableCommand for RunCommand {
1726 async fn stream<F>(&self, handler: F) -> Result<StreamResult>
1727 where
1728 F: FnMut(OutputLine) + Send + 'static,
1729 {
1730 if self.detach {
1732 return Err(Error::custom(
1733 "Cannot stream output for detached containers",
1734 ));
1735 }
1736
1737 let mut cmd = TokioCommand::new("docker");
1738 cmd.arg("run");
1739
1740 for arg in self.build_command_args() {
1741 cmd.arg(arg);
1742 }
1743
1744 crate::stream::stream_command(cmd, handler).await
1745 }
1746
1747 async fn stream_channel(&self) -> Result<(mpsc::Receiver<OutputLine>, StreamResult)> {
1748 if self.detach {
1750 return Err(Error::custom(
1751 "Cannot stream output for detached containers",
1752 ));
1753 }
1754
1755 let mut cmd = TokioCommand::new("docker");
1756 cmd.arg("run");
1757
1758 for arg in self.build_command_args() {
1759 cmd.arg(arg);
1760 }
1761
1762 crate::stream::stream_command_channel(cmd).await
1763 }
1764}
1765
1766impl RunCommand {
1767 pub async fn stream<F>(&self, handler: F) -> Result<StreamResult>
1790 where
1791 F: FnMut(OutputLine) + Send + 'static,
1792 {
1793 <Self as StreamableCommand>::stream(self, handler).await
1794 }
1795}
1796
1797#[cfg(test)]
1798mod tests {
1799 use super::*;
1800
1801 #[test]
1802 fn test_run_command_builder() {
1803 let cmd = RunCommand::new("nginx:latest")
1804 .name("test-nginx")
1805 .detach()
1806 .env("ENV_VAR", "value")
1807 .port(8080, 80)
1808 .volume("data", "/var/data")
1809 .workdir("/app")
1810 .remove();
1811
1812 let args = cmd.build_command_args();
1813
1814 assert!(args.contains(&"--detach".to_string()));
1815 assert!(args.contains(&"--name".to_string()));
1816 assert!(args.contains(&"test-nginx".to_string()));
1817 assert!(args.contains(&"--env".to_string()));
1818 assert!(args.contains(&"ENV_VAR=value".to_string()));
1819 assert!(args.contains(&"--publish".to_string()));
1820 assert!(args.contains(&"8080:80".to_string()));
1821 assert!(args.contains(&"--volume".to_string()));
1822 assert!(args.contains(&"data:/var/data".to_string()));
1823 assert!(args.contains(&"--workdir".to_string()));
1824 assert!(args.contains(&"/app".to_string()));
1825 assert!(args.contains(&"--rm".to_string()));
1826 assert!(args.contains(&"nginx:latest".to_string()));
1827 }
1828
1829 #[test]
1830 fn test_run_command_with_cmd() {
1831 let cmd =
1832 RunCommand::new("alpine:latest").cmd(vec!["echo".to_string(), "hello".to_string()]);
1833
1834 let args = cmd.build_command_args();
1835 assert!(args.contains(&"alpine:latest".to_string()));
1836 assert!(args.contains(&"echo".to_string()));
1837 assert!(args.contains(&"hello".to_string()));
1838 }
1839
1840 #[test]
1841 fn test_run_command_extensibility() {
1842 let mut cmd = RunCommand::new("test:latest");
1843 cmd.flag("privileged")
1844 .option("memory", "1g")
1845 .arg("--custom-option");
1846
1847 }
1850
1851 #[test]
1852 fn test_volume_mount_display() {
1853 let volume = VolumeMount {
1854 source: "data".to_string(),
1855 target: "/var/data".to_string(),
1856 mount_type: MountType::Volume,
1857 readonly: false,
1858 };
1859 assert_eq!(volume.to_string(), "data:/var/data");
1860
1861 let readonly_volume = VolumeMount {
1862 source: "/host/path".to_string(),
1863 target: "/container/path".to_string(),
1864 mount_type: MountType::Bind,
1865 readonly: true,
1866 };
1867 assert_eq!(readonly_volume.to_string(), "/host/path:/container/path:ro");
1868 }
1869
1870 #[test]
1871 fn test_run_command_resource_limits() {
1872 let cmd = RunCommand::new("alpine:latest")
1873 .memory("1g")
1874 .cpus("2.0")
1875 .cpu_shares(1024)
1876 .cpu_period(100_000)
1877 .cpu_quota(50_000)
1878 .cpuset_cpus("0-3")
1879 .cpuset_mems("0,1")
1880 .memory_swap("2g")
1881 .memory_reservation("500m");
1882
1883 let args = cmd.build_command_args();
1884
1885 assert!(args.contains(&"--memory".to_string()));
1886 assert!(args.contains(&"1g".to_string()));
1887 assert!(args.contains(&"--cpus".to_string()));
1888 assert!(args.contains(&"2.0".to_string()));
1889 assert!(args.contains(&"--cpu-shares".to_string()));
1890 assert!(args.contains(&"1024".to_string()));
1891 assert!(args.contains(&"--cpu-period".to_string()));
1892 assert!(args.contains(&"100000".to_string()));
1893 assert!(args.contains(&"--cpu-quota".to_string()));
1894 assert!(args.contains(&"50000".to_string()));
1895 assert!(args.contains(&"--cpuset-cpus".to_string()));
1896 assert!(args.contains(&"0-3".to_string()));
1897 assert!(args.contains(&"--cpuset-mems".to_string()));
1898 assert!(args.contains(&"0,1".to_string()));
1899 assert!(args.contains(&"--memory-swap".to_string()));
1900 assert!(args.contains(&"2g".to_string()));
1901 assert!(args.contains(&"--memory-reservation".to_string()));
1902 assert!(args.contains(&"500m".to_string()));
1903 }
1904
1905 #[test]
1906 fn test_run_command_security_and_user() {
1907 let cmd = RunCommand::new("alpine:latest")
1908 .user("1000:1000")
1909 .privileged()
1910 .hostname("test-host");
1911
1912 let args = cmd.build_command_args();
1913
1914 assert!(args.contains(&"--user".to_string()));
1915 assert!(args.contains(&"1000:1000".to_string()));
1916 assert!(args.contains(&"--privileged".to_string()));
1917 assert!(args.contains(&"--hostname".to_string()));
1918 assert!(args.contains(&"test-host".to_string()));
1919 }
1920
1921 #[test]
1922 fn test_run_command_lifecycle_management() {
1923 let cmd = RunCommand::new("alpine:latest").restart("always");
1924
1925 let args = cmd.build_command_args();
1926
1927 assert!(args.contains(&"--restart".to_string()));
1928 assert!(args.contains(&"always".to_string()));
1929 }
1930
1931 #[test]
1932 fn test_run_command_system_integration() {
1933 let cmd = RunCommand::new("alpine:latest")
1934 .platform("linux/amd64")
1935 .runtime("runc")
1936 .isolation("default")
1937 .pull("always")
1938 .cidfile("/tmp/container.cid")
1939 .domainname("example.com")
1940 .mac_address("92:d0:c6:0a:29:33");
1941
1942 let args = cmd.build_command_args();
1943
1944 assert!(args.contains(&"--platform".to_string()));
1945 assert!(args.contains(&"linux/amd64".to_string()));
1946 assert!(args.contains(&"--runtime".to_string()));
1947 assert!(args.contains(&"runc".to_string()));
1948 assert!(args.contains(&"--isolation".to_string()));
1949 assert!(args.contains(&"default".to_string()));
1950 assert!(args.contains(&"--pull".to_string()));
1951 assert!(args.contains(&"always".to_string()));
1952 assert!(args.contains(&"--cidfile".to_string()));
1953 assert!(args.contains(&"/tmp/container.cid".to_string()));
1954 assert!(args.contains(&"--domainname".to_string()));
1955 assert!(args.contains(&"example.com".to_string()));
1956 assert!(args.contains(&"--mac-address".to_string()));
1957 assert!(args.contains(&"92:d0:c6:0a:29:33".to_string()));
1958 }
1959
1960 #[test]
1961 fn test_run_command_logging_and_drivers() {
1962 let cmd = RunCommand::new("alpine:latest")
1963 .log_driver("json-file")
1964 .volume_driver("local");
1965
1966 let args = cmd.build_command_args();
1967
1968 assert!(args.contains(&"--log-driver".to_string()));
1969 assert!(args.contains(&"json-file".to_string()));
1970 assert!(args.contains(&"--volume-driver".to_string()));
1971 assert!(args.contains(&"local".to_string()));
1972 }
1973
1974 #[test]
1975 fn test_run_command_namespaces() {
1976 let cmd = RunCommand::new("alpine:latest")
1977 .userns("host")
1978 .uts("host")
1979 .pid("host")
1980 .ipc("host")
1981 .cgroupns("private")
1982 .cgroup_parent("/docker");
1983
1984 let args = cmd.build_command_args();
1985
1986 assert!(args.contains(&"--userns".to_string()));
1987 assert!(args.contains(&"host".to_string()));
1988 assert!(args.contains(&"--uts".to_string()));
1989 assert!(args.contains(&"--pid".to_string()));
1990 assert!(args.contains(&"--ipc".to_string()));
1991 assert!(args.contains(&"--cgroupns".to_string()));
1992 assert!(args.contains(&"private".to_string()));
1993 assert!(args.contains(&"--cgroup-parent".to_string()));
1994 assert!(args.contains(&"/docker".to_string()));
1995 }
1996
1997 #[test]
1998 fn test_run_command_advanced_memory_performance() {
1999 let cmd = RunCommand::new("alpine:latest")
2000 .kernel_memory("100m")
2001 .memory_swappiness(60)
2002 .oom_score_adj(-500)
2003 .pids_limit(100)
2004 .shm_size("64m");
2005
2006 let args = cmd.build_command_args();
2007
2008 assert!(args.contains(&"--kernel-memory".to_string()));
2009 assert!(args.contains(&"100m".to_string()));
2010 assert!(args.contains(&"--memory-swappiness".to_string()));
2011 assert!(args.contains(&"60".to_string()));
2012 assert!(args.contains(&"--oom-score-adj".to_string()));
2013 assert!(args.contains(&"-500".to_string()));
2014 assert!(args.contains(&"--pids-limit".to_string()));
2015 assert!(args.contains(&"100".to_string()));
2016 assert!(args.contains(&"--shm-size".to_string()));
2017 assert!(args.contains(&"64m".to_string()));
2018 }
2019
2020 #[test]
2021 fn test_run_command_process_control() {
2022 let cmd = RunCommand::new("alpine:latest")
2023 .stop_signal("SIGTERM")
2024 .stop_timeout(10)
2025 .detach_keys("ctrl-p,ctrl-q");
2026
2027 let args = cmd.build_command_args();
2028
2029 assert!(args.contains(&"--stop-signal".to_string()));
2030 assert!(args.contains(&"SIGTERM".to_string()));
2031 assert!(args.contains(&"--stop-timeout".to_string()));
2032 assert!(args.contains(&"10".to_string()));
2033 assert!(args.contains(&"--detach-keys".to_string()));
2034 assert!(args.contains(&"ctrl-p,ctrl-q".to_string()));
2035 }
2036
2037 #[test]
2038 fn test_run_command_simple_flags() {
2039 let cmd = RunCommand::new("alpine:latest")
2040 .no_sig_proxy()
2041 .read_only()
2042 .init()
2043 .oom_kill_disable()
2044 .no_healthcheck()
2045 .enable_content_trust()
2046 .publish_all()
2047 .quiet();
2048
2049 let args = cmd.build_command_args();
2050
2051 assert!(args.contains(&"--sig-proxy=false".to_string()));
2052 assert!(args.contains(&"--read-only".to_string()));
2053 assert!(args.contains(&"--init".to_string()));
2054 assert!(args.contains(&"--oom-kill-disable".to_string()));
2055 assert!(args.contains(&"--no-healthcheck".to_string()));
2056 assert!(args.contains(&"--disable-content-trust=false".to_string()));
2057 assert!(args.contains(&"--publish-all".to_string()));
2058 assert!(args.contains(&"--quiet".to_string()));
2059 }
2060
2061 #[test]
2062 fn test_run_command_comprehensive_builder() {
2063 let cmd = RunCommand::new("nginx:latest")
2064 .name("production-nginx")
2065 .detach()
2066 .memory("2g")
2067 .cpus("4.0")
2068 .user("nginx:nginx")
2069 .privileged()
2070 .restart("unless-stopped")
2071 .hostname("web-server")
2072 .platform("linux/amd64")
2073 .env("NGINX_PORT", "8080")
2074 .port(80, 8080)
2075 .volume("nginx-data", "/var/lib/nginx")
2076 .workdir("/usr/share/nginx/html")
2077 .read_only()
2078 .init()
2079 .remove();
2080
2081 let args = cmd.build_command_args();
2082
2083 assert!(args.contains(&"--name".to_string()));
2085 assert!(args.contains(&"production-nginx".to_string()));
2086 assert!(args.contains(&"--detach".to_string()));
2087 assert!(args.contains(&"--memory".to_string()));
2088 assert!(args.contains(&"2g".to_string()));
2089 assert!(args.contains(&"--cpus".to_string()));
2090 assert!(args.contains(&"4.0".to_string()));
2091 assert!(args.contains(&"--user".to_string()));
2092 assert!(args.contains(&"nginx:nginx".to_string()));
2093 assert!(args.contains(&"--privileged".to_string()));
2094 assert!(args.contains(&"--restart".to_string()));
2095 assert!(args.contains(&"unless-stopped".to_string()));
2096 assert!(args.contains(&"--hostname".to_string()));
2097 assert!(args.contains(&"web-server".to_string()));
2098 assert!(args.contains(&"--platform".to_string()));
2099 assert!(args.contains(&"linux/amd64".to_string()));
2100 assert!(args.contains(&"--read-only".to_string()));
2101 assert!(args.contains(&"--init".to_string()));
2102 assert!(args.contains(&"--rm".to_string()));
2103 assert!(args.contains(&"nginx:latest".to_string()));
2104
2105 let image_pos = args.iter().position(|x| x == "nginx:latest").unwrap();
2107 assert!(image_pos > 10); }
2109
2110 #[test]
2111 fn test_run_command_default_flag_values() {
2112 let cmd = RunCommand::new("alpine:latest");
2113 let args = cmd.build_command_args();
2114
2115 assert!(!args.contains(&"--sig-proxy=false".to_string()));
2117 assert!(!args.contains(&"--disable-content-trust=false".to_string()));
2118 assert!(!args.contains(&"--read-only".to_string()));
2119 assert!(!args.contains(&"--privileged".to_string()));
2120 assert!(!args.contains(&"--init".to_string()));
2121 }
2122
2123 #[test]
2124 fn test_container_id() {
2125 let id = ContainerId("abcdef123456789".to_string());
2126 assert_eq!(id.as_str(), "abcdef123456789");
2127 assert_eq!(id.short(), "abcdef123456");
2128 assert_eq!(id.to_string(), "abcdef123456789");
2129
2130 let short_id = ContainerId("abc".to_string());
2131 assert_eq!(short_id.short(), "abc");
2132 }
2133
2134 #[test]
2135 fn test_it_convenience_method() {
2136 let cmd = RunCommand::new("alpine:latest").it();
2137 let args = cmd.build_command_args();
2138 assert!(args.contains(&"--interactive".to_string()));
2139 assert!(args.contains(&"--tty".to_string()));
2140 }
2141
2142 #[test]
2143 fn test_run_command_dns_network_options() {
2144 let cmd = RunCommand::new("alpine:latest")
2145 .dns("8.8.8.8")
2146 .dns("8.8.4.4")
2147 .dns_servers(vec!["1.1.1.1".to_string(), "1.0.0.1".to_string()])
2148 .dns_option("ndots:2")
2149 .dns_option("timeout:1")
2150 .dns_search("example.com")
2151 .dns_search("test.local")
2152 .add_host("api.example.com:127.0.0.1")
2153 .add_host("db.example.com:192.168.1.100");
2154
2155 let args = cmd.build_command_args();
2156
2157 assert!(args.contains(&"--dns".to_string()));
2159 assert!(args.contains(&"8.8.8.8".to_string()));
2160 assert!(args.contains(&"8.8.4.4".to_string()));
2161 assert!(args.contains(&"1.1.1.1".to_string()));
2162 assert!(args.contains(&"1.0.0.1".to_string()));
2163
2164 assert!(args.contains(&"--dns-option".to_string()));
2166 assert!(args.contains(&"ndots:2".to_string()));
2167 assert!(args.contains(&"timeout:1".to_string()));
2168
2169 assert!(args.contains(&"--dns-search".to_string()));
2171 assert!(args.contains(&"example.com".to_string()));
2172 assert!(args.contains(&"test.local".to_string()));
2173
2174 assert!(args.contains(&"--add-host".to_string()));
2176 assert!(args.contains(&"api.example.com:127.0.0.1".to_string()));
2177 assert!(args.contains(&"db.example.com:192.168.1.100".to_string()));
2178 }
2179
2180 #[test]
2181 fn test_run_command_security_capabilities() {
2182 let cmd = RunCommand::new("alpine:latest")
2183 .cap_add("NET_ADMIN")
2184 .cap_add("SYS_TIME")
2185 .cap_drop("CHOWN")
2186 .cap_drop("DAC_OVERRIDE")
2187 .security_opt("no-new-privileges:true")
2188 .security_opt("seccomp=unconfined");
2189
2190 let args = cmd.build_command_args();
2191
2192 assert!(args.contains(&"--cap-add".to_string()));
2194 assert!(args.contains(&"NET_ADMIN".to_string()));
2195 assert!(args.contains(&"SYS_TIME".to_string()));
2196
2197 assert!(args.contains(&"--cap-drop".to_string()));
2199 assert!(args.contains(&"CHOWN".to_string()));
2200 assert!(args.contains(&"DAC_OVERRIDE".to_string()));
2201
2202 assert!(args.contains(&"--security-opt".to_string()));
2204 assert!(args.contains(&"no-new-privileges:true".to_string()));
2205 assert!(args.contains(&"seccomp=unconfined".to_string()));
2206 }
2207
2208 #[test]
2209 fn test_run_command_device_filesystem() {
2210 let cmd = RunCommand::new("alpine:latest")
2211 .device("/dev/sda:/dev/xvda:rwm")
2212 .device("/dev/zero")
2213 .tmpfs("/tmp:rw,size=100m")
2214 .tmpfs("/var/tmp:ro")
2215 .expose("80")
2216 .expose("443")
2217 .expose("8080/tcp");
2218
2219 let args = cmd.build_command_args();
2220
2221 assert!(args.contains(&"--device".to_string()));
2223 assert!(args.contains(&"/dev/sda:/dev/xvda:rwm".to_string()));
2224 assert!(args.contains(&"/dev/zero".to_string()));
2225
2226 assert!(args.contains(&"--tmpfs".to_string()));
2228 assert!(args.contains(&"/tmp:rw,size=100m".to_string()));
2229 assert!(args.contains(&"/var/tmp:ro".to_string()));
2230
2231 assert!(args.contains(&"--expose".to_string()));
2233 assert!(args.contains(&"80".to_string()));
2234 assert!(args.contains(&"443".to_string()));
2235 assert!(args.contains(&"8080/tcp".to_string()));
2236 }
2237
2238 #[test]
2239 fn test_run_command_environment_labels() {
2240 use std::path::PathBuf;
2241
2242 let cmd = RunCommand::new("alpine:latest")
2243 .env_file(PathBuf::from("/etc/environment"))
2244 .env_file(PathBuf::from("./app.env"))
2245 .label("version=1.0.0")
2246 .label("maintainer=team@example.com")
2247 .label("app=myapp")
2248 .label_file(PathBuf::from("/etc/labels"))
2249 .label_file(PathBuf::from("./metadata.labels"));
2250
2251 let args = cmd.build_command_args();
2252
2253 assert!(args.contains(&"--env-file".to_string()));
2255 assert!(args.contains(&"/etc/environment".to_string()));
2256 assert!(args.contains(&"./app.env".to_string()));
2257
2258 assert!(args.contains(&"--label".to_string()));
2260 assert!(args.contains(&"version=1.0.0".to_string()));
2261 assert!(args.contains(&"maintainer=team@example.com".to_string()));
2262 assert!(args.contains(&"app=myapp".to_string()));
2263
2264 assert!(args.contains(&"--label-file".to_string()));
2266 assert!(args.contains(&"/etc/labels".to_string()));
2267 assert!(args.contains(&"./metadata.labels".to_string()));
2268 }
2269
2270 #[test]
2271 fn test_run_command_all_high_impact_options() {
2272 use std::path::PathBuf;
2273
2274 let cmd = RunCommand::new("nginx:latest")
2275 .name("production-nginx")
2276 .dns("8.8.8.8")
2278 .dns_option("ndots:2")
2279 .dns_search("example.com")
2280 .add_host("api.example.com:127.0.0.1")
2281 .cap_add("NET_ADMIN")
2283 .cap_drop("CHOWN")
2284 .security_opt("no-new-privileges:true")
2285 .device("/dev/null")
2287 .tmpfs("/tmp:rw,size=100m")
2288 .expose("80")
2289 .env_file(PathBuf::from(".env"))
2291 .label("version=1.0.0")
2292 .label_file(PathBuf::from("labels"));
2293
2294 let args = cmd.build_command_args();
2295
2296 assert!(args.contains(&"--dns".to_string()));
2298 assert!(args.contains(&"--dns-option".to_string()));
2299 assert!(args.contains(&"--dns-search".to_string()));
2300 assert!(args.contains(&"--add-host".to_string()));
2301 assert!(args.contains(&"--cap-add".to_string()));
2302 assert!(args.contains(&"--cap-drop".to_string()));
2303 assert!(args.contains(&"--security-opt".to_string()));
2304 assert!(args.contains(&"--device".to_string()));
2305 assert!(args.contains(&"--tmpfs".to_string()));
2306 assert!(args.contains(&"--expose".to_string()));
2307 assert!(args.contains(&"--env-file".to_string()));
2308 assert!(args.contains(&"--label".to_string()));
2309 assert!(args.contains(&"--label-file".to_string()));
2310
2311 let image_pos = args.iter().position(|x| x == "nginx:latest").unwrap();
2313 assert!(image_pos > 0); assert!(image_pos < args.len() - 1 || args.len() == image_pos + 1); }
2316
2317 #[test]
2318 fn test_run_command_empty_lists_not_added() {
2319 let cmd = RunCommand::new("alpine:latest");
2320 let args = cmd.build_command_args();
2321
2322 assert!(!args.contains(&"--dns".to_string()));
2324 assert!(!args.contains(&"--dns-option".to_string()));
2325 assert!(!args.contains(&"--dns-search".to_string()));
2326 assert!(!args.contains(&"--add-host".to_string()));
2327 assert!(!args.contains(&"--cap-add".to_string()));
2328 assert!(!args.contains(&"--cap-drop".to_string()));
2329 assert!(!args.contains(&"--security-opt".to_string()));
2330 assert!(!args.contains(&"--device".to_string()));
2331 assert!(!args.contains(&"--tmpfs".to_string()));
2332 assert!(!args.contains(&"--expose".to_string()));
2333 assert!(!args.contains(&"--env-file".to_string()));
2334 assert!(!args.contains(&"--label".to_string()));
2335 assert!(!args.contains(&"--label-file".to_string()));
2336
2337 assert!(!args.contains(&"--network-alias".to_string()));
2339 assert!(!args.contains(&"--group-add".to_string()));
2340 assert!(!args.contains(&"--attach".to_string()));
2341 assert!(!args.contains(&"--log-opt".to_string()));
2342 assert!(!args.contains(&"--storage-opt".to_string()));
2343 assert!(!args.contains(&"--ulimit".to_string()));
2344 assert!(!args.contains(&"--volumes-from".to_string()));
2345 assert!(!args.contains(&"--link".to_string()));
2346 assert!(!args.contains(&"--link-local-ip".to_string()));
2347
2348 assert!(args.contains(&"alpine:latest".to_string()));
2350 }
2351
2352 #[test]
2353 fn test_run_command_additional_list_options() {
2354 let cmd = RunCommand::new("alpine:latest")
2355 .network_alias("web")
2356 .network_alias("frontend")
2357 .group_add("staff")
2358 .group_add("docker")
2359 .attach("stdout")
2360 .attach("stderr")
2361 .log_opt("max-size=10m")
2362 .log_opt("max-file=3")
2363 .storage_opt("size=20G")
2364 .ulimit("nofile=1024:65536")
2365 .ulimit("nproc=1024")
2366 .volumes_from("data-container")
2367 .volumes_from("config-container:ro")
2368 .link("db:database")
2369 .link("cache:redis")
2370 .link_local_ip("169.254.1.1")
2371 .link_local_ip("fe80::1");
2372
2373 let args = cmd.build_command_args();
2374
2375 assert!(args.contains(&"--network-alias".to_string()));
2377 assert!(args.contains(&"web".to_string()));
2378 assert!(args.contains(&"frontend".to_string()));
2379
2380 assert!(args.contains(&"--group-add".to_string()));
2382 assert!(args.contains(&"staff".to_string()));
2383 assert!(args.contains(&"docker".to_string()));
2384
2385 assert!(args.contains(&"--attach".to_string()));
2387 assert!(args.contains(&"stdout".to_string()));
2388 assert!(args.contains(&"stderr".to_string()));
2389
2390 assert!(args.contains(&"--log-opt".to_string()));
2392 assert!(args.contains(&"max-size=10m".to_string()));
2393 assert!(args.contains(&"max-file=3".to_string()));
2394
2395 assert!(args.contains(&"--storage-opt".to_string()));
2397 assert!(args.contains(&"size=20G".to_string()));
2398
2399 assert!(args.contains(&"--ulimit".to_string()));
2401 assert!(args.contains(&"nofile=1024:65536".to_string()));
2402 assert!(args.contains(&"nproc=1024".to_string()));
2403
2404 assert!(args.contains(&"--volumes-from".to_string()));
2406 assert!(args.contains(&"data-container".to_string()));
2407 assert!(args.contains(&"config-container:ro".to_string()));
2408
2409 assert!(args.contains(&"--link".to_string()));
2411 assert!(args.contains(&"db:database".to_string()));
2412 assert!(args.contains(&"cache:redis".to_string()));
2413
2414 assert!(args.contains(&"--link-local-ip".to_string()));
2416 assert!(args.contains(&"169.254.1.1".to_string()));
2417 assert!(args.contains(&"fe80::1".to_string()));
2418 }
2419
2420 #[test]
2421 fn test_run_command_additional_list_individual_options() {
2422 let network_cmd = RunCommand::new("alpine:latest").network_alias("api");
2424 let network_args = network_cmd.build_command_args();
2425 assert!(network_args.contains(&"--network-alias".to_string()));
2426 assert!(network_args.contains(&"api".to_string()));
2427
2428 let group_cmd = RunCommand::new("alpine:latest").group_add("wheel");
2429 let group_args = group_cmd.build_command_args();
2430 assert!(group_args.contains(&"--group-add".to_string()));
2431 assert!(group_args.contains(&"wheel".to_string()));
2432
2433 let attach_cmd = RunCommand::new("alpine:latest").attach("stdin");
2434 let attach_args = attach_cmd.build_command_args();
2435 assert!(attach_args.contains(&"--attach".to_string()));
2436 assert!(attach_args.contains(&"stdin".to_string()));
2437
2438 let log_cmd = RunCommand::new("alpine:latest").log_opt("compress=true");
2439 let log_args = log_cmd.build_command_args();
2440 assert!(log_args.contains(&"--log-opt".to_string()));
2441 assert!(log_args.contains(&"compress=true".to_string()));
2442
2443 let storage_cmd =
2444 RunCommand::new("alpine:latest").storage_opt("dm.thinpooldev=/dev/mapper/thin-pool");
2445 let storage_args = storage_cmd.build_command_args();
2446 assert!(storage_args.contains(&"--storage-opt".to_string()));
2447 assert!(storage_args.contains(&"dm.thinpooldev=/dev/mapper/thin-pool".to_string()));
2448
2449 let ulimit_cmd = RunCommand::new("alpine:latest").ulimit("memlock=-1:-1");
2450 let ulimit_args = ulimit_cmd.build_command_args();
2451 assert!(ulimit_args.contains(&"--ulimit".to_string()));
2452 assert!(ulimit_args.contains(&"memlock=-1:-1".to_string()));
2453
2454 let volumes_cmd = RunCommand::new("alpine:latest").volumes_from("shared-data");
2455 let volumes_args = volumes_cmd.build_command_args();
2456 assert!(volumes_args.contains(&"--volumes-from".to_string()));
2457 assert!(volumes_args.contains(&"shared-data".to_string()));
2458
2459 let link_cmd = RunCommand::new("alpine:latest").link("mysql:db");
2460 let link_args = link_cmd.build_command_args();
2461 assert!(link_args.contains(&"--link".to_string()));
2462 assert!(link_args.contains(&"mysql:db".to_string()));
2463
2464 let ip_cmd = RunCommand::new("alpine:latest").link_local_ip("169.254.100.1");
2465 let ip_args = ip_cmd.build_command_args();
2466 assert!(ip_args.contains(&"--link-local-ip".to_string()));
2467 assert!(ip_args.contains(&"169.254.100.1".to_string()));
2468 }
2469
2470 #[test]
2471 fn test_run_command_health_check_options() {
2472 let cmd = RunCommand::new("nginx:latest")
2473 .health_cmd("curl -f http://localhost/ || exit 1")
2474 .health_interval("30s")
2475 .health_retries(3)
2476 .health_timeout("5s")
2477 .health_start_period("60s")
2478 .health_start_interval("5s");
2479
2480 let args = cmd.build_command_args();
2481
2482 assert!(args.contains(&"--health-cmd".to_string()));
2484 assert!(args.contains(&"curl -f http://localhost/ || exit 1".to_string()));
2485 assert!(args.contains(&"--health-interval".to_string()));
2486 assert!(args.contains(&"30s".to_string()));
2487 assert!(args.contains(&"--health-retries".to_string()));
2488 assert!(args.contains(&"3".to_string()));
2489 assert!(args.contains(&"--health-timeout".to_string()));
2490 assert!(args.contains(&"5s".to_string()));
2491 assert!(args.contains(&"--health-start-period".to_string()));
2492 assert!(args.contains(&"60s".to_string()));
2493 assert!(args.contains(&"--health-start-interval".to_string()));
2494 }
2496
2497 #[test]
2498 fn test_run_command_advanced_mount_network_options() {
2499 let cmd = RunCommand::new("alpine:latest")
2500 .mount("type=bind,source=/host/path,target=/container/path")
2501 .mount("type=volume,source=data-vol,target=/data")
2502 .network("frontend")
2503 .network("backend")
2504 .gpus("all")
2505 .annotation("io.kubernetes.cri-o.Devices", "/dev/fuse")
2506 .annotation("io.kubernetes.cri-o.ShmSize", "64m")
2507 .sysctl("net.core.somaxconn", "1024")
2508 .sysctl("kernel.shm_rmid_forced", "1");
2509
2510 let args = cmd.build_command_args();
2511
2512 assert!(args.contains(&"--mount".to_string()));
2514 assert!(args.contains(&"type=bind,source=/host/path,target=/container/path".to_string()));
2515 assert!(args.contains(&"type=volume,source=data-vol,target=/data".to_string()));
2516
2517 assert!(args.contains(&"--network".to_string()));
2519 assert!(args.contains(&"frontend".to_string()));
2520 assert!(args.contains(&"backend".to_string()));
2521
2522 assert!(args.contains(&"--gpus".to_string()));
2524 assert!(args.contains(&"all".to_string()));
2525
2526 assert!(args.contains(&"--annotation".to_string()));
2528 assert!(args.contains(&"io.kubernetes.cri-o.Devices=/dev/fuse".to_string()));
2529 assert!(args.contains(&"io.kubernetes.cri-o.ShmSize=64m".to_string()));
2530
2531 assert!(args.contains(&"--sysctl".to_string()));
2533 assert!(args.contains(&"net.core.somaxconn=1024".to_string()));
2534 assert!(args.contains(&"kernel.shm_rmid_forced=1".to_string()));
2535 }
2536
2537 #[test]
2538 fn test_run_command_health_advanced_individual_options() {
2539 let health_cmd = RunCommand::new("alpine:latest").health_cmd("ping -c 1 localhost");
2541 let health_args = health_cmd.build_command_args();
2542 assert!(health_args.contains(&"--health-cmd".to_string()));
2543 assert!(health_args.contains(&"ping -c 1 localhost".to_string()));
2544
2545 let health_interval = RunCommand::new("alpine:latest").health_interval("10s");
2546 let interval_args = health_interval.build_command_args();
2547 assert!(interval_args.contains(&"--health-interval".to_string()));
2548 assert!(interval_args.contains(&"10s".to_string()));
2549
2550 let health_retries = RunCommand::new("alpine:latest").health_retries(5);
2551 let retries_args = health_retries.build_command_args();
2552 assert!(retries_args.contains(&"--health-retries".to_string()));
2553 assert!(retries_args.contains(&"5".to_string()));
2554
2555 let mount_cmd = RunCommand::new("alpine:latest").mount("type=tmpfs,destination=/app");
2556 let mount_args = mount_cmd.build_command_args();
2557 assert!(mount_args.contains(&"--mount".to_string()));
2558 assert!(mount_args.contains(&"type=tmpfs,destination=/app".to_string()));
2559
2560 let network_cmd = RunCommand::new("alpine:latest").network("my-network");
2561 let network_args = network_cmd.build_command_args();
2562 assert!(network_args.contains(&"--network".to_string()));
2563 assert!(network_args.contains(&"my-network".to_string()));
2564
2565 let gpu_cmd = RunCommand::new("alpine:latest").gpus("device=0");
2566 let gpu_args = gpu_cmd.build_command_args();
2567 assert!(gpu_args.contains(&"--gpus".to_string()));
2568 assert!(gpu_args.contains(&"device=0".to_string()));
2569
2570 let annotation_cmd = RunCommand::new("alpine:latest").annotation("key", "value");
2571 let annotation_args = annotation_cmd.build_command_args();
2572 assert!(annotation_args.contains(&"--annotation".to_string()));
2573 assert!(annotation_args.contains(&"key=value".to_string()));
2574
2575 let sysctl_cmd = RunCommand::new("alpine:latest").sysctl("net.ipv4.ip_forward", "1");
2576 let sysctl_args = sysctl_cmd.build_command_args();
2577 assert!(sysctl_args.contains(&"--sysctl".to_string()));
2578 assert!(sysctl_args.contains(&"net.ipv4.ip_forward=1".to_string()));
2579 }
2580
2581 #[test]
2582 fn test_run_command_comprehensive_health_advanced_integration() {
2583 let cmd = RunCommand::new("web-app:latest")
2584 .name("production-web-app")
2585 .health_cmd("curl -f http://localhost:8080/health || exit 1")
2587 .health_interval("30s")
2588 .health_retries(3)
2589 .health_timeout("10s")
2590 .health_start_period("120s")
2591 .mount("type=bind,source=/var/log/app,target=/app/logs")
2593 .mount("type=volume,source=app-data,target=/app/data")
2594 .network("frontend")
2595 .network("backend")
2596 .gpus("device=0,1")
2598 .annotation(
2600 "io.kubernetes.container.apparmor.security.beta.kubernetes.io/app",
2601 "runtime/default",
2602 )
2603 .annotation(
2604 "io.kubernetes.container.seccomp.security.alpha.kubernetes.io/app",
2605 "runtime/default",
2606 )
2607 .sysctl("net.core.somaxconn", "65535")
2609 .sysctl("net.ipv4.tcp_keepalive_time", "600")
2610 .port(8080, 8080)
2612 .env("NODE_ENV", "production")
2613 .memory("2g")
2614 .cpus("2.0")
2615 .restart("unless-stopped")
2616 .detach();
2617
2618 let args = cmd.build_command_args();
2619
2620 assert!(args.contains(&"--health-cmd".to_string()));
2622 assert!(args.contains(&"--health-interval".to_string()));
2623 assert!(args.contains(&"--health-retries".to_string()));
2624 assert!(args.contains(&"--health-timeout".to_string()));
2625 assert!(args.contains(&"--health-start-period".to_string()));
2626 assert!(args.contains(&"--mount".to_string()));
2627 assert!(args.contains(&"--network".to_string()));
2628 assert!(args.contains(&"--gpus".to_string()));
2629 assert!(args.contains(&"--annotation".to_string()));
2630 assert!(args.contains(&"--sysctl".to_string()));
2631
2632 let image_pos = args.iter().position(|x| x == "web-app:latest").unwrap();
2634 assert!(image_pos > 0);
2635 }
2636
2637 #[test]
2638 fn test_run_command_block_io_controls() {
2639 let cmd = RunCommand::new("alpine:latest")
2640 .blkio_weight(500)
2641 .blkio_weight_device("/dev/sda:300")
2642 .blkio_weight_device("/dev/sdb:700")
2643 .device_read_bps("/dev/sda:50mb")
2644 .device_write_bps("/dev/sda:30mb")
2645 .device_read_iops("/dev/sda:1000")
2646 .device_write_iops("/dev/sda:800");
2647
2648 let args = cmd.build_command_args();
2649
2650 assert!(args.contains(&"--blkio-weight".to_string()));
2652 assert!(args.contains(&"500".to_string()));
2653
2654 assert!(args.contains(&"--blkio-weight-device".to_string()));
2656 assert!(args.contains(&"/dev/sda:300".to_string()));
2657 assert!(args.contains(&"/dev/sdb:700".to_string()));
2658
2659 assert!(args.contains(&"--device-read-bps".to_string()));
2661 assert!(args.contains(&"/dev/sda:50mb".to_string()));
2662 assert!(args.contains(&"--device-write-bps".to_string()));
2663 assert!(args.contains(&"/dev/sda:30mb".to_string()));
2664
2665 assert!(args.contains(&"--device-read-iops".to_string()));
2667 assert!(args.contains(&"/dev/sda:1000".to_string()));
2668 assert!(args.contains(&"--device-write-iops".to_string()));
2669 assert!(args.contains(&"/dev/sda:800".to_string()));
2670 }
2671
2672 #[test]
2673 fn test_run_command_realtime_cpu_networking() {
2674 let cmd = RunCommand::new("alpine:latest")
2675 .cpu_rt_period(1_000_000)
2676 .cpu_rt_runtime(950_000)
2677 .ip("172.30.100.104")
2678 .ip6("2001:db8::33")
2679 .device_cgroup_rule("c 1:3 mr")
2680 .device_cgroup_rule("a 7:* rmw");
2681
2682 let args = cmd.build_command_args();
2683
2684 assert!(args.contains(&"--cpu-rt-period".to_string()));
2686 assert!(args.contains(&"1000000".to_string()));
2687 assert!(args.contains(&"--cpu-rt-runtime".to_string()));
2688 assert!(args.contains(&"950000".to_string()));
2689
2690 assert!(args.contains(&"--ip".to_string()));
2692 assert!(args.contains(&"172.30.100.104".to_string()));
2693 assert!(args.contains(&"--ip6".to_string()));
2694 assert!(args.contains(&"2001:db8::33".to_string()));
2695
2696 assert!(args.contains(&"--device-cgroup-rule".to_string()));
2698 assert!(args.contains(&"c 1:3 mr".to_string()));
2699 assert!(args.contains(&"a 7:* rmw".to_string()));
2700 }
2701
2702 #[test]
2703 fn test_run_command_advanced_system_individual_options() {
2704 let blkio_cmd = RunCommand::new("alpine:latest").blkio_weight(100);
2706 let blkio_args = blkio_cmd.build_command_args();
2707 assert!(blkio_args.contains(&"--blkio-weight".to_string()));
2708 assert!(blkio_args.contains(&"100".to_string()));
2709
2710 let weight_device_cmd =
2711 RunCommand::new("alpine:latest").blkio_weight_device("/dev/sda:500");
2712 let weight_device_args = weight_device_cmd.build_command_args();
2713 assert!(weight_device_args.contains(&"--blkio-weight-device".to_string()));
2714 assert!(weight_device_args.contains(&"/dev/sda:500".to_string()));
2715
2716 let read_bps_cmd = RunCommand::new("alpine:latest").device_read_bps("/dev/sda:1mb");
2717 let read_bps_args = read_bps_cmd.build_command_args();
2718 assert!(read_bps_args.contains(&"--device-read-bps".to_string()));
2719 assert!(read_bps_args.contains(&"/dev/sda:1mb".to_string()));
2720
2721 let write_bps_cmd = RunCommand::new("alpine:latest").device_write_bps("/dev/sda:1mb");
2722 let write_bps_args = write_bps_cmd.build_command_args();
2723 assert!(write_bps_args.contains(&"--device-write-bps".to_string()));
2724 assert!(write_bps_args.contains(&"/dev/sda:1mb".to_string()));
2725
2726 let read_iops_cmd = RunCommand::new("alpine:latest").device_read_iops("/dev/sda:100");
2727 let read_iops_args = read_iops_cmd.build_command_args();
2728 assert!(read_iops_args.contains(&"--device-read-iops".to_string()));
2729 assert!(read_iops_args.contains(&"/dev/sda:100".to_string()));
2730
2731 let write_iops_cmd = RunCommand::new("alpine:latest").device_write_iops("/dev/sda:100");
2732 let write_iops_args = write_iops_cmd.build_command_args();
2733 assert!(write_iops_args.contains(&"--device-write-iops".to_string()));
2734 assert!(write_iops_args.contains(&"/dev/sda:100".to_string()));
2735
2736 let rt_period_cmd = RunCommand::new("alpine:latest").cpu_rt_period(100_000);
2737 let rt_period_args = rt_period_cmd.build_command_args();
2738 assert!(rt_period_args.contains(&"--cpu-rt-period".to_string()));
2739 assert!(rt_period_args.contains(&"100000".to_string()));
2740
2741 let rt_runtime_cmd = RunCommand::new("alpine:latest").cpu_rt_runtime(95_000);
2742 let rt_runtime_args = rt_runtime_cmd.build_command_args();
2743 assert!(rt_runtime_args.contains(&"--cpu-rt-runtime".to_string()));
2744 assert!(rt_runtime_args.contains(&"95000".to_string()));
2745
2746 let ip_cmd = RunCommand::new("alpine:latest").ip("192.168.1.100");
2747 let ip_args = ip_cmd.build_command_args();
2748 assert!(ip_args.contains(&"--ip".to_string()));
2749 assert!(ip_args.contains(&"192.168.1.100".to_string()));
2750
2751 let ipv6_cmd = RunCommand::new("alpine:latest").ip6("fe80::1");
2752 let ipv6_args = ipv6_cmd.build_command_args();
2753 assert!(ipv6_args.contains(&"--ip6".to_string()));
2754 assert!(ipv6_args.contains(&"fe80::1".to_string()));
2755
2756 let cgroup_rule_cmd = RunCommand::new("alpine:latest").device_cgroup_rule("c 1:1 rwm");
2757 let cgroup_rule_args = cgroup_rule_cmd.build_command_args();
2758 assert!(cgroup_rule_args.contains(&"--device-cgroup-rule".to_string()));
2759 assert!(cgroup_rule_args.contains(&"c 1:1 rwm".to_string()));
2760 }
2761
2762 #[test]
2763 #[allow(clippy::too_many_lines)]
2764 fn test_run_command_complete_100_percent_coverage() {
2765 use std::path::PathBuf;
2766
2767 let cmd = RunCommand::new("enterprise-app:latest")
2769 .name("production-enterprise")
2770 .detach()
2772 .interactive()
2773 .tty()
2774 .remove()
2775 .env("NODE_ENV", "production")
2777 .port(8080, 8080)
2778 .volume("/data", "/app/data")
2779 .workdir("/app")
2780 .entrypoint("/app/start.sh")
2781 .memory("4g")
2783 .cpus("2.0")
2784 .cpu_shares(1024)
2785 .cpu_period(100_000)
2786 .cpu_quota(50000)
2787 .cpuset_cpus("0-1")
2788 .cpuset_mems("0")
2789 .memory_swap("8g")
2790 .memory_reservation("2g")
2791 .user("app:app")
2793 .privileged()
2794 .hostname("enterprise-app")
2795 .restart("unless-stopped")
2797 .platform("linux/amd64")
2799 .runtime("runc")
2800 .isolation("default")
2801 .pull("always")
2802 .cidfile("/tmp/container.cid")
2803 .domainname("enterprise.local")
2804 .mac_address("02:42:ac:11:00:02")
2805 .log_driver("json-file")
2807 .volume_driver("local")
2808 .userns("host")
2810 .uts("host")
2811 .pid("host")
2812 .ipc("host")
2813 .cgroupns("host")
2814 .cgroup_parent("/docker")
2815 .kernel_memory("1g")
2817 .memory_swappiness(10)
2818 .oom_score_adj(-500)
2819 .pids_limit(1000)
2820 .shm_size("64m")
2821 .stop_signal("SIGTERM")
2823 .stop_timeout(30)
2824 .detach_keys("ctrl-p,ctrl-q")
2825 .no_sig_proxy()
2827 .read_only()
2828 .init()
2829 .oom_kill_disable()
2830 .no_healthcheck()
2831 .enable_content_trust()
2832 .publish_all()
2833 .quiet()
2834 .dns("8.8.8.8")
2836 .dns_option("ndots:2")
2837 .dns_search("enterprise.local")
2838 .add_host("api.enterprise.local:10.0.1.100")
2839 .cap_add("NET_ADMIN")
2840 .cap_drop("ALL")
2841 .security_opt("no-new-privileges:true")
2842 .device("/dev/null")
2843 .tmpfs("/tmp:size=100m")
2844 .expose("9090")
2845 .env_file(PathBuf::from(".env.production"))
2846 .label("app=enterprise")
2847 .label_file(PathBuf::from("labels.txt"))
2848 .network_alias("enterprise-primary")
2850 .group_add("staff")
2851 .attach("stdout")
2852 .log_opt("max-size=10m")
2853 .storage_opt("size=100G")
2854 .ulimit("nofile=65536:65536")
2855 .volumes_from("data-container")
2856 .link("db:database")
2857 .link_local_ip("169.254.1.1")
2858 .health_cmd("curl -f http://localhost:8080/health || exit 1")
2860 .health_interval("30s")
2861 .health_retries(3)
2862 .health_timeout("10s")
2863 .health_start_period("60s")
2864 .health_start_interval("5s")
2865 .mount("type=bind,source=/host/config,target=/app/config")
2866 .network("enterprise-net")
2867 .gpus("device=0")
2868 .annotation("io.kubernetes.cri-o.TTY", "true")
2869 .sysctl("net.core.somaxconn", "65535")
2870 .blkio_weight(500)
2872 .blkio_weight_device("/dev/sda:300")
2873 .device_read_bps("/dev/sda:100mb")
2874 .device_write_bps("/dev/sda:50mb")
2875 .device_read_iops("/dev/sda:1000")
2876 .device_write_iops("/dev/sda:500")
2877 .cpu_rt_period(1_000_000)
2878 .cpu_rt_runtime(950_000)
2879 .ip("10.0.1.50")
2880 .ip6("2001:db8::50")
2881 .device_cgroup_rule("c 1:1 rwm");
2882
2883 let args = cmd.build_command_args();
2884
2885 assert!(args.len() > 150); assert!(args.contains(&"--detach".to_string()));
2890 assert!(args.contains(&"--memory".to_string()));
2891 assert!(args.contains(&"--dns".to_string()));
2892 assert!(args.contains(&"--network-alias".to_string()));
2893 assert!(args.contains(&"--health-cmd".to_string()));
2894 assert!(args.contains(&"--blkio-weight".to_string()));
2895
2896 let image_pos = args
2898 .iter()
2899 .position(|x| x == "enterprise-app:latest")
2900 .unwrap();
2901 assert!(image_pos > 100); assert_eq!(args[args.len() - 1], "enterprise-app:latest");
2903
2904 println!("COMPLETE! All 96 Docker run options implemented and tested!");
2905 }
2906}