1use super::{CommandExecutor, DockerCommand, EnvironmentBuilder, PortBuilder};
7use crate::command::port::{PortCommand, PortMapping as PortMappingInfo};
8use crate::error::{Error, Result};
9use crate::stream::{OutputLine, StreamResult, StreamableCommand};
10use async_trait::async_trait;
11use std::path::PathBuf;
12use tokio::process::Command as TokioCommand;
13use tokio::sync::mpsc;
14
15#[derive(Debug, Clone)]
17#[allow(clippy::struct_excessive_bools)]
18pub struct RunCommand {
19 image: String,
21 pub executor: CommandExecutor,
23 name: Option<String>,
25 detach: bool,
27 environment: EnvironmentBuilder,
29 ports: PortBuilder,
31 volumes: Vec<VolumeMount>,
33 workdir: Option<PathBuf>,
35 entrypoint: Option<String>,
37 command: Option<Vec<String>>,
39 interactive: bool,
41 tty: bool,
43 remove: bool,
45
46 memory: Option<String>,
49 cpus: Option<String>,
51 cpu_shares: Option<i64>,
53 cpu_period: Option<i64>,
55 cpu_quota: Option<i64>,
57 cpuset_cpus: Option<String>,
59 cpuset_mems: Option<String>,
61 memory_swap: Option<String>,
63 memory_reservation: Option<String>,
65
66 user: Option<String>,
69 privileged: bool,
71 hostname: Option<String>,
73
74 restart: Option<String>,
77
78 platform: Option<String>,
81 runtime: Option<String>,
83 isolation: Option<String>,
85 pull: Option<String>,
87 cidfile: Option<String>,
89 domainname: Option<String>,
91 mac_address: Option<String>,
93
94 log_driver: Option<String>,
97 volume_driver: Option<String>,
99
100 userns: Option<String>,
103 uts: Option<String>,
105 pid: Option<String>,
107 ipc: Option<String>,
109 cgroupns: Option<String>,
111 cgroup_parent: Option<String>,
113
114 kernel_memory: Option<String>,
117 memory_swappiness: Option<i32>,
119 oom_score_adj: Option<i32>,
121 pids_limit: Option<i64>,
123 shm_size: Option<String>,
125
126 stop_signal: Option<String>,
129 stop_timeout: Option<i32>,
131 detach_keys: Option<String>,
133
134 sig_proxy: bool,
137 read_only: bool,
139 init: bool,
141 oom_kill_disable: bool,
143 no_healthcheck: bool,
145 disable_content_trust: bool,
147 publish_all: bool,
149 quiet: bool,
151
152 dns: Vec<String>,
156 dns_option: Vec<String>,
158 dns_search: Vec<String>,
160 add_host: Vec<String>,
162
163 cap_add: Vec<String>,
166 cap_drop: Vec<String>,
168 security_opt: Vec<String>,
170
171 device: Vec<String>,
174 tmpfs: Vec<String>,
176 expose: Vec<String>,
178
179 env_file: Vec<PathBuf>,
182 label: Vec<String>,
184 label_file: Vec<PathBuf>,
186
187 network_alias: Vec<String>,
190 group_add: Vec<String>,
192 attach: Vec<String>,
194 log_opt: Vec<String>,
196 storage_opt: Vec<String>,
198 ulimit: Vec<String>,
200 volumes_from: Vec<String>,
202 link: Vec<String>,
204 link_local_ip: Vec<String>,
206
207 health_cmd: Option<String>,
211 health_interval: Option<String>,
213 health_retries: Option<i32>,
215 health_timeout: Option<String>,
217 health_start_period: Option<String>,
219 health_start_interval: Option<String>,
221
222 mount: Vec<String>,
225 network: Vec<String>,
227 gpus: Option<String>,
229
230 annotation: Vec<String>,
233 sysctl: Vec<String>,
235
236 blkio_weight: Option<u16>,
240 blkio_weight_device: Vec<String>,
242 device_read_bps: Vec<String>,
244 device_write_bps: Vec<String>,
246 device_read_iops: Vec<String>,
248 device_write_iops: Vec<String>,
250
251 cpu_rt_period: Option<i64>,
254 cpu_rt_runtime: Option<i64>,
256
257 ip: Option<String>,
260 ip6: Option<String>,
262
263 device_cgroup_rule: Vec<String>,
266}
267
268#[derive(Debug, Clone)]
270pub struct VolumeMount {
271 pub source: String,
273 pub target: String,
275 pub mount_type: MountType,
277 pub readonly: bool,
279}
280
281#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub enum MountType {
284 Bind,
286 Volume,
288 Tmpfs,
290}
291
292impl std::fmt::Display for VolumeMount {
293 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294 let readonly_suffix = if self.readonly { ":ro" } else { "" };
295 write!(f, "{}:{}{}", self.source, self.target, readonly_suffix)
296 }
297}
298
299#[derive(Debug, Clone, PartialEq, Eq)]
301pub struct ContainerId(pub String);
302
303impl ContainerId {
304 #[must_use]
306 pub fn as_str(&self) -> &str {
307 &self.0
308 }
309
310 #[must_use]
312 pub fn short(&self) -> &str {
313 if self.0.len() >= 12 {
314 &self.0[..12]
315 } else {
316 &self.0
317 }
318 }
319
320 pub async fn port_mappings(&self) -> Result<Vec<PortMappingInfo>> {
356 let result = PortCommand::new(&self.0).run().await?;
357 Ok(result.port_mappings)
358 }
359
360 pub async fn port_mapping(&self, container_port: u16) -> Result<Option<PortMappingInfo>> {
390 let result = PortCommand::new(&self.0).port(container_port).run().await?;
391 Ok(result.port_mappings.into_iter().next())
392 }
393}
394
395impl std::fmt::Display for ContainerId {
396 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397 write!(f, "{}", self.0)
398 }
399}
400
401impl RunCommand {
402 #[allow(clippy::too_many_lines)]
404 pub fn new(image: impl Into<String>) -> Self {
405 Self {
406 image: image.into(),
407 executor: CommandExecutor::new(),
408 name: None,
409 detach: false,
410 environment: EnvironmentBuilder::new(),
411 ports: PortBuilder::new(),
412 volumes: Vec::new(),
413 workdir: None,
414 entrypoint: None,
415 command: None,
416 interactive: false,
417 tty: false,
418 remove: false,
419
420 memory: None,
422 cpus: None,
423 cpu_shares: None,
424 cpu_period: None,
425 cpu_quota: None,
426 cpuset_cpus: None,
427 cpuset_mems: None,
428 memory_swap: None,
429 memory_reservation: None,
430
431 user: None,
433 privileged: false,
434 hostname: None,
435
436 restart: None,
438
439 platform: None,
441 runtime: None,
442 isolation: None,
443 pull: None,
444 cidfile: None,
445 domainname: None,
446 mac_address: None,
447
448 log_driver: None,
450 volume_driver: None,
451
452 userns: None,
454 uts: None,
455 pid: None,
456 ipc: None,
457 cgroupns: None,
458 cgroup_parent: None,
459
460 kernel_memory: None,
462 memory_swappiness: None,
463 oom_score_adj: None,
464 pids_limit: None,
465 shm_size: None,
466
467 stop_signal: None,
469 stop_timeout: None,
470 detach_keys: None,
471
472 sig_proxy: true, read_only: false,
475 init: false,
476 oom_kill_disable: false,
477 no_healthcheck: false,
478 disable_content_trust: true, publish_all: false,
480 quiet: false,
481
482 dns: Vec::new(),
485 dns_option: Vec::new(),
486 dns_search: Vec::new(),
487 add_host: Vec::new(),
488
489 cap_add: Vec::new(),
491 cap_drop: Vec::new(),
492 security_opt: Vec::new(),
493
494 device: Vec::new(),
496 tmpfs: Vec::new(),
497 expose: Vec::new(),
498
499 env_file: Vec::new(),
501 label: Vec::new(),
502 label_file: Vec::new(),
503
504 network_alias: Vec::new(),
506 group_add: Vec::new(),
507 attach: Vec::new(),
508 log_opt: Vec::new(),
509 storage_opt: Vec::new(),
510 ulimit: Vec::new(),
511 volumes_from: Vec::new(),
512 link: Vec::new(),
513 link_local_ip: Vec::new(),
514
515 health_cmd: None,
517 health_interval: None,
518 health_retries: None,
519 health_timeout: None,
520 health_start_period: None,
521 health_start_interval: None,
522 mount: Vec::new(),
523 network: Vec::new(),
524 gpus: None,
525 annotation: Vec::new(),
526 sysctl: Vec::new(),
527
528 blkio_weight: None,
530 blkio_weight_device: Vec::new(),
531 device_read_bps: Vec::new(),
532 device_write_bps: Vec::new(),
533 device_read_iops: Vec::new(),
534 device_write_iops: Vec::new(),
535 cpu_rt_period: None,
536 cpu_rt_runtime: None,
537 ip: None,
538 ip6: None,
539 device_cgroup_rule: Vec::new(),
540 }
541 }
542
543 #[must_use]
545 pub fn name(mut self, name: impl Into<String>) -> Self {
546 self.name = Some(name.into());
547 self
548 }
549
550 #[must_use]
552 pub fn detach(mut self) -> Self {
553 self.detach = true;
554 self
555 }
556
557 #[must_use]
559 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
560 self.environment = self.environment.var(key, value);
561 self
562 }
563
564 #[must_use]
566 pub fn envs(mut self, vars: std::collections::HashMap<String, String>) -> Self {
567 self.environment = self.environment.vars(vars);
568 self
569 }
570
571 #[must_use]
573 pub fn port(mut self, host_port: u16, container_port: u16) -> Self {
574 self.ports = self.ports.port(host_port, container_port);
575 self
576 }
577
578 #[must_use]
580 pub fn dynamic_port(mut self, container_port: u16) -> Self {
581 self.ports = self.ports.dynamic_port(container_port);
582 self
583 }
584
585 #[must_use]
587 pub fn port_dyn(self, container_port: u16) -> Self {
588 self.dynamic_port(container_port)
589 }
590
591 #[must_use]
593 pub fn volume(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
594 self.volumes.push(VolumeMount {
595 source: source.into(),
596 target: target.into(),
597 mount_type: MountType::Volume,
598 readonly: false,
599 });
600 self
601 }
602
603 #[must_use]
605 pub fn bind(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
606 self.volumes.push(VolumeMount {
607 source: source.into(),
608 target: target.into(),
609 mount_type: MountType::Bind,
610 readonly: false,
611 });
612 self
613 }
614
615 #[must_use]
617 pub fn volume_ro(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
618 self.volumes.push(VolumeMount {
619 source: source.into(),
620 target: target.into(),
621 mount_type: MountType::Volume,
622 readonly: true,
623 });
624 self
625 }
626
627 #[must_use]
629 pub fn workdir(mut self, workdir: impl Into<PathBuf>) -> Self {
630 self.workdir = Some(workdir.into());
631 self
632 }
633
634 #[must_use]
636 pub fn entrypoint(mut self, entrypoint: impl Into<String>) -> Self {
637 self.entrypoint = Some(entrypoint.into());
638 self
639 }
640
641 #[must_use]
643 pub fn cmd(mut self, command: Vec<String>) -> Self {
644 self.command = Some(command);
645 self
646 }
647
648 #[must_use]
650 pub fn interactive(mut self) -> Self {
651 self.interactive = true;
652 self
653 }
654
655 #[must_use]
657 pub fn tty(mut self) -> Self {
658 self.tty = true;
659 self
660 }
661
662 #[must_use]
664 pub fn remove(mut self) -> Self {
665 self.remove = true;
666 self
667 }
668
669 #[must_use]
671 pub fn rm(self) -> Self {
672 self.remove()
673 }
674
675 #[must_use]
677 pub fn it(self) -> Self {
678 self.interactive().tty()
679 }
680
681 #[must_use]
684 pub fn memory(mut self, memory: impl Into<String>) -> Self {
685 self.memory = Some(memory.into());
686 self
687 }
688
689 #[must_use]
691 pub fn cpus(mut self, cpus: impl Into<String>) -> Self {
692 self.cpus = Some(cpus.into());
693 self
694 }
695
696 #[must_use]
698 pub fn cpu_shares(mut self, shares: i64) -> Self {
699 self.cpu_shares = Some(shares);
700 self
701 }
702
703 #[must_use]
705 pub fn cpu_period(mut self, period: i64) -> Self {
706 self.cpu_period = Some(period);
707 self
708 }
709
710 #[must_use]
712 pub fn cpu_quota(mut self, quota: i64) -> Self {
713 self.cpu_quota = Some(quota);
714 self
715 }
716
717 #[must_use]
719 pub fn cpuset_cpus(mut self, cpus: impl Into<String>) -> Self {
720 self.cpuset_cpus = Some(cpus.into());
721 self
722 }
723
724 #[must_use]
726 pub fn cpuset_mems(mut self, mems: impl Into<String>) -> Self {
727 self.cpuset_mems = Some(mems.into());
728 self
729 }
730
731 #[must_use]
733 pub fn memory_swap(mut self, swap: impl Into<String>) -> Self {
734 self.memory_swap = Some(swap.into());
735 self
736 }
737
738 #[must_use]
740 pub fn memory_reservation(mut self, reservation: impl Into<String>) -> Self {
741 self.memory_reservation = Some(reservation.into());
742 self
743 }
744
745 #[must_use]
748 pub fn user(mut self, user: impl Into<String>) -> Self {
749 self.user = Some(user.into());
750 self
751 }
752
753 #[must_use]
755 pub fn privileged(mut self) -> Self {
756 self.privileged = true;
757 self
758 }
759
760 #[must_use]
762 pub fn hostname(mut self, hostname: impl Into<String>) -> Self {
763 self.hostname = Some(hostname.into());
764 self
765 }
766
767 #[must_use]
770 pub fn restart(mut self, restart: impl Into<String>) -> Self {
771 self.restart = Some(restart.into());
772 self
773 }
774
775 #[must_use]
778 pub fn platform(mut self, platform: impl Into<String>) -> Self {
779 self.platform = Some(platform.into());
780 self
781 }
782
783 #[must_use]
785 pub fn runtime(mut self, runtime: impl Into<String>) -> Self {
786 self.runtime = Some(runtime.into());
787 self
788 }
789
790 #[must_use]
792 pub fn isolation(mut self, isolation: impl Into<String>) -> Self {
793 self.isolation = Some(isolation.into());
794 self
795 }
796
797 #[must_use]
799 pub fn pull(mut self, pull: impl Into<String>) -> Self {
800 self.pull = Some(pull.into());
801 self
802 }
803
804 #[must_use]
806 pub fn cidfile(mut self, cidfile: impl Into<String>) -> Self {
807 self.cidfile = Some(cidfile.into());
808 self
809 }
810
811 #[must_use]
813 pub fn domainname(mut self, domainname: impl Into<String>) -> Self {
814 self.domainname = Some(domainname.into());
815 self
816 }
817
818 #[must_use]
820 pub fn mac_address(mut self, mac: impl Into<String>) -> Self {
821 self.mac_address = Some(mac.into());
822 self
823 }
824
825 #[must_use]
828 pub fn log_driver(mut self, driver: impl Into<String>) -> Self {
829 self.log_driver = Some(driver.into());
830 self
831 }
832
833 #[must_use]
835 pub fn volume_driver(mut self, driver: impl Into<String>) -> Self {
836 self.volume_driver = Some(driver.into());
837 self
838 }
839
840 #[must_use]
843 pub fn userns(mut self, userns: impl Into<String>) -> Self {
844 self.userns = Some(userns.into());
845 self
846 }
847
848 #[must_use]
850 pub fn uts(mut self, uts: impl Into<String>) -> Self {
851 self.uts = Some(uts.into());
852 self
853 }
854
855 #[must_use]
857 pub fn pid(mut self, pid: impl Into<String>) -> Self {
858 self.pid = Some(pid.into());
859 self
860 }
861
862 #[must_use]
864 pub fn ipc(mut self, ipc: impl Into<String>) -> Self {
865 self.ipc = Some(ipc.into());
866 self
867 }
868
869 #[must_use]
871 pub fn cgroupns(mut self, cgroupns: impl Into<String>) -> Self {
872 self.cgroupns = Some(cgroupns.into());
873 self
874 }
875
876 #[must_use]
878 pub fn cgroup_parent(mut self, parent: impl Into<String>) -> Self {
879 self.cgroup_parent = Some(parent.into());
880 self
881 }
882
883 #[must_use]
886 pub fn kernel_memory(mut self, memory: impl Into<String>) -> Self {
887 self.kernel_memory = Some(memory.into());
888 self
889 }
890
891 #[must_use]
893 pub fn memory_swappiness(mut self, swappiness: i32) -> Self {
894 self.memory_swappiness = Some(swappiness);
895 self
896 }
897
898 #[must_use]
900 pub fn oom_score_adj(mut self, score: i32) -> Self {
901 self.oom_score_adj = Some(score);
902 self
903 }
904
905 #[must_use]
907 pub fn pids_limit(mut self, limit: i64) -> Self {
908 self.pids_limit = Some(limit);
909 self
910 }
911
912 #[must_use]
914 pub fn shm_size(mut self, size: impl Into<String>) -> Self {
915 self.shm_size = Some(size.into());
916 self
917 }
918
919 #[must_use]
922 pub fn stop_signal(mut self, signal: impl Into<String>) -> Self {
923 self.stop_signal = Some(signal.into());
924 self
925 }
926
927 #[must_use]
929 pub fn stop_timeout(mut self, timeout: i32) -> Self {
930 self.stop_timeout = Some(timeout);
931 self
932 }
933
934 #[must_use]
936 pub fn detach_keys(mut self, keys: impl Into<String>) -> Self {
937 self.detach_keys = Some(keys.into());
938 self
939 }
940
941 #[must_use]
944 pub fn no_sig_proxy(mut self) -> Self {
945 self.sig_proxy = false;
946 self
947 }
948
949 #[must_use]
951 pub fn read_only(mut self) -> Self {
952 self.read_only = true;
953 self
954 }
955
956 #[must_use]
958 pub fn init(mut self) -> Self {
959 self.init = true;
960 self
961 }
962
963 #[must_use]
965 pub fn oom_kill_disable(mut self) -> Self {
966 self.oom_kill_disable = true;
967 self
968 }
969
970 #[must_use]
972 pub fn no_healthcheck(mut self) -> Self {
973 self.no_healthcheck = true;
974 self
975 }
976
977 #[must_use]
979 pub fn enable_content_trust(mut self) -> Self {
980 self.disable_content_trust = false;
981 self
982 }
983
984 #[must_use]
986 pub fn publish_all(mut self) -> Self {
987 self.publish_all = true;
988 self
989 }
990
991 #[must_use]
993 pub fn quiet(mut self) -> Self {
994 self.quiet = true;
995 self
996 }
997
998 #[must_use]
1003 pub fn dns(mut self, dns: impl Into<String>) -> Self {
1004 self.dns.push(dns.into());
1005 self
1006 }
1007
1008 #[must_use]
1010 pub fn dns_servers(mut self, servers: Vec<String>) -> Self {
1011 self.dns.extend(servers);
1012 self
1013 }
1014
1015 #[must_use]
1017 pub fn dns_option(mut self, option: impl Into<String>) -> Self {
1018 self.dns_option.push(option.into());
1019 self
1020 }
1021
1022 #[must_use]
1024 pub fn dns_search(mut self, domain: impl Into<String>) -> Self {
1025 self.dns_search.push(domain.into());
1026 self
1027 }
1028
1029 #[must_use]
1031 pub fn add_host(mut self, mapping: impl Into<String>) -> Self {
1032 self.add_host.push(mapping.into());
1033 self
1034 }
1035
1036 #[must_use]
1039 pub fn cap_add(mut self, capability: impl Into<String>) -> Self {
1040 self.cap_add.push(capability.into());
1041 self
1042 }
1043
1044 #[must_use]
1046 pub fn cap_drop(mut self, capability: impl Into<String>) -> Self {
1047 self.cap_drop.push(capability.into());
1048 self
1049 }
1050
1051 #[must_use]
1053 pub fn security_opt(mut self, option: impl Into<String>) -> Self {
1054 self.security_opt.push(option.into());
1055 self
1056 }
1057
1058 #[must_use]
1061 pub fn device(mut self, device: impl Into<String>) -> Self {
1062 self.device.push(device.into());
1063 self
1064 }
1065
1066 #[must_use]
1068 pub fn tmpfs(mut self, path: impl Into<String>) -> Self {
1069 self.tmpfs.push(path.into());
1070 self
1071 }
1072
1073 #[must_use]
1075 pub fn expose(mut self, port: impl Into<String>) -> Self {
1076 self.expose.push(port.into());
1077 self
1078 }
1079
1080 #[must_use]
1083 pub fn env_file(mut self, file: impl Into<PathBuf>) -> Self {
1084 self.env_file.push(file.into());
1085 self
1086 }
1087
1088 #[must_use]
1090 pub fn label(mut self, label: impl Into<String>) -> Self {
1091 self.label.push(label.into());
1092 self
1093 }
1094
1095 #[must_use]
1097 pub fn label_file(mut self, file: impl Into<PathBuf>) -> Self {
1098 self.label_file.push(file.into());
1099 self
1100 }
1101
1102 #[must_use]
1106 pub fn network_alias(mut self, alias: impl Into<String>) -> Self {
1107 self.network_alias.push(alias.into());
1108 self
1109 }
1110
1111 #[must_use]
1113 pub fn group_add(mut self, group: impl Into<String>) -> Self {
1114 self.group_add.push(group.into());
1115 self
1116 }
1117
1118 #[must_use]
1120 pub fn attach(mut self, stream: impl Into<String>) -> Self {
1121 self.attach.push(stream.into());
1122 self
1123 }
1124
1125 #[must_use]
1127 pub fn log_opt(mut self, option: impl Into<String>) -> Self {
1128 self.log_opt.push(option.into());
1129 self
1130 }
1131
1132 #[must_use]
1134 pub fn storage_opt(mut self, option: impl Into<String>) -> Self {
1135 self.storage_opt.push(option.into());
1136 self
1137 }
1138
1139 #[must_use]
1141 pub fn ulimit(mut self, limit: impl Into<String>) -> Self {
1142 self.ulimit.push(limit.into());
1143 self
1144 }
1145
1146 #[must_use]
1148 pub fn volumes_from(mut self, container: impl Into<String>) -> Self {
1149 self.volumes_from.push(container.into());
1150 self
1151 }
1152
1153 #[must_use]
1155 pub fn link(mut self, link: impl Into<String>) -> Self {
1156 self.link.push(link.into());
1157 self
1158 }
1159
1160 #[must_use]
1162 pub fn link_local_ip(mut self, ip: impl Into<String>) -> Self {
1163 self.link_local_ip.push(ip.into());
1164 self
1165 }
1166
1167 #[must_use]
1172 pub fn health_cmd(mut self, cmd: impl Into<String>) -> Self {
1173 self.health_cmd = Some(cmd.into());
1174 self
1175 }
1176
1177 #[must_use]
1179 pub fn health_interval(mut self, interval: impl Into<String>) -> Self {
1180 self.health_interval = Some(interval.into());
1181 self
1182 }
1183
1184 #[must_use]
1186 pub fn health_retries(mut self, retries: i32) -> Self {
1187 self.health_retries = Some(retries);
1188 self
1189 }
1190
1191 #[must_use]
1193 pub fn health_timeout(mut self, timeout: impl Into<String>) -> Self {
1194 self.health_timeout = Some(timeout.into());
1195 self
1196 }
1197
1198 #[must_use]
1200 pub fn health_start_period(mut self, period: impl Into<String>) -> Self {
1201 self.health_start_period = Some(period.into());
1202 self
1203 }
1204
1205 #[must_use]
1207 pub fn health_start_interval(mut self, interval: impl Into<String>) -> Self {
1208 self.health_start_interval = Some(interval.into());
1209 self
1210 }
1211
1212 #[must_use]
1215 pub fn mount(mut self, mount: impl Into<String>) -> Self {
1216 self.mount.push(mount.into());
1217 self
1218 }
1219
1220 #[must_use]
1222 pub fn network(mut self, network: impl Into<String>) -> Self {
1223 self.network.push(network.into());
1224 self
1225 }
1226
1227 #[must_use]
1229 pub fn gpus(mut self, gpus: impl Into<String>) -> Self {
1230 self.gpus = Some(gpus.into());
1231 self
1232 }
1233
1234 #[must_use]
1236 pub fn annotation(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1237 self.annotation
1238 .push(format!("{}={}", key.into(), value.into()));
1239 self
1240 }
1241
1242 #[must_use]
1244 pub fn sysctl(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1245 self.sysctl.push(format!("{}={}", key.into(), value.into()));
1246 self
1247 }
1248
1249 #[must_use]
1254 pub fn blkio_weight(mut self, weight: u16) -> Self {
1255 self.blkio_weight = Some(weight);
1256 self
1257 }
1258
1259 #[must_use]
1261 pub fn blkio_weight_device(mut self, device_weight: impl Into<String>) -> Self {
1262 self.blkio_weight_device.push(device_weight.into());
1263 self
1264 }
1265
1266 #[must_use]
1268 pub fn device_read_bps(mut self, device_rate: impl Into<String>) -> Self {
1269 self.device_read_bps.push(device_rate.into());
1270 self
1271 }
1272
1273 #[must_use]
1275 pub fn device_write_bps(mut self, device_rate: impl Into<String>) -> Self {
1276 self.device_write_bps.push(device_rate.into());
1277 self
1278 }
1279
1280 #[must_use]
1282 pub fn device_read_iops(mut self, device_rate: impl Into<String>) -> Self {
1283 self.device_read_iops.push(device_rate.into());
1284 self
1285 }
1286
1287 #[must_use]
1289 pub fn device_write_iops(mut self, device_rate: impl Into<String>) -> Self {
1290 self.device_write_iops.push(device_rate.into());
1291 self
1292 }
1293
1294 #[must_use]
1297 pub fn cpu_rt_period(mut self, period: i64) -> Self {
1298 self.cpu_rt_period = Some(period);
1299 self
1300 }
1301
1302 #[must_use]
1304 pub fn cpu_rt_runtime(mut self, runtime: i64) -> Self {
1305 self.cpu_rt_runtime = Some(runtime);
1306 self
1307 }
1308
1309 #[must_use]
1312 pub fn ip(mut self, ip: impl Into<String>) -> Self {
1313 self.ip = Some(ip.into());
1314 self
1315 }
1316
1317 #[must_use]
1319 pub fn ip6(mut self, ip6: impl Into<String>) -> Self {
1320 self.ip6 = Some(ip6.into());
1321 self
1322 }
1323
1324 #[must_use]
1326 pub fn device_cgroup_rule(mut self, rule: impl Into<String>) -> Self {
1327 self.device_cgroup_rule.push(rule.into());
1328 self
1329 }
1330}
1331
1332#[async_trait]
1333impl DockerCommand for RunCommand {
1334 type Output = ContainerId;
1335
1336 fn get_executor(&self) -> &CommandExecutor {
1337 &self.executor
1338 }
1339
1340 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
1341 &mut self.executor
1342 }
1343
1344 #[allow(clippy::too_many_lines)]
1345 fn build_command_args(&self) -> Vec<String> {
1346 let mut args = vec!["run".to_string()];
1347
1348 if self.detach {
1350 args.push("--detach".to_string());
1351 }
1352 if self.interactive {
1353 args.push("--interactive".to_string());
1354 }
1355 if self.tty {
1356 args.push("--tty".to_string());
1357 }
1358 if self.remove {
1359 args.push("--rm".to_string());
1360 }
1361
1362 if let Some(ref name) = self.name {
1364 args.push("--name".to_string());
1365 args.push(name.clone());
1366 }
1367
1368 if let Some(ref workdir) = self.workdir {
1370 args.push("--workdir".to_string());
1371 args.push(workdir.to_string_lossy().to_string());
1372 }
1373
1374 if let Some(ref entrypoint) = self.entrypoint {
1376 args.push("--entrypoint".to_string());
1377 args.push(entrypoint.clone());
1378 }
1379
1380 args.extend(self.environment.build_args());
1382
1383 args.extend(self.ports.build_args());
1385
1386 for volume in &self.volumes {
1388 args.push("--volume".to_string());
1389 args.push(volume.to_string());
1390 }
1391
1392 if let Some(ref memory) = self.memory {
1394 args.push("--memory".to_string());
1395 args.push(memory.clone());
1396 }
1397 if let Some(ref cpus) = self.cpus {
1398 args.push("--cpus".to_string());
1399 args.push(cpus.clone());
1400 }
1401 if let Some(cpu_shares) = self.cpu_shares {
1402 args.push("--cpu-shares".to_string());
1403 args.push(cpu_shares.to_string());
1404 }
1405 if let Some(cpu_period) = self.cpu_period {
1406 args.push("--cpu-period".to_string());
1407 args.push(cpu_period.to_string());
1408 }
1409 if let Some(cpu_quota) = self.cpu_quota {
1410 args.push("--cpu-quota".to_string());
1411 args.push(cpu_quota.to_string());
1412 }
1413 if let Some(ref cpuset_cpus) = self.cpuset_cpus {
1414 args.push("--cpuset-cpus".to_string());
1415 args.push(cpuset_cpus.clone());
1416 }
1417 if let Some(ref cpuset_mems) = self.cpuset_mems {
1418 args.push("--cpuset-mems".to_string());
1419 args.push(cpuset_mems.clone());
1420 }
1421 if let Some(ref memory_swap) = self.memory_swap {
1422 args.push("--memory-swap".to_string());
1423 args.push(memory_swap.clone());
1424 }
1425 if let Some(ref memory_reservation) = self.memory_reservation {
1426 args.push("--memory-reservation".to_string());
1427 args.push(memory_reservation.clone());
1428 }
1429
1430 if let Some(ref user) = self.user {
1432 args.push("--user".to_string());
1433 args.push(user.clone());
1434 }
1435 if self.privileged {
1436 args.push("--privileged".to_string());
1437 }
1438 if let Some(ref hostname) = self.hostname {
1439 args.push("--hostname".to_string());
1440 args.push(hostname.clone());
1441 }
1442
1443 if let Some(ref restart) = self.restart {
1445 args.push("--restart".to_string());
1446 args.push(restart.clone());
1447 }
1448
1449 if let Some(ref platform) = self.platform {
1451 args.push("--platform".to_string());
1452 args.push(platform.clone());
1453 }
1454 if let Some(ref runtime) = self.runtime {
1455 args.push("--runtime".to_string());
1456 args.push(runtime.clone());
1457 }
1458 if let Some(ref isolation) = self.isolation {
1459 args.push("--isolation".to_string());
1460 args.push(isolation.clone());
1461 }
1462 if let Some(ref pull) = self.pull {
1463 args.push("--pull".to_string());
1464 args.push(pull.clone());
1465 }
1466 if let Some(ref cidfile) = self.cidfile {
1467 args.push("--cidfile".to_string());
1468 args.push(cidfile.clone());
1469 }
1470 if let Some(ref domainname) = self.domainname {
1471 args.push("--domainname".to_string());
1472 args.push(domainname.clone());
1473 }
1474 if let Some(ref mac_address) = self.mac_address {
1475 args.push("--mac-address".to_string());
1476 args.push(mac_address.clone());
1477 }
1478
1479 if let Some(ref log_driver) = self.log_driver {
1481 args.push("--log-driver".to_string());
1482 args.push(log_driver.clone());
1483 }
1484 if let Some(ref volume_driver) = self.volume_driver {
1485 args.push("--volume-driver".to_string());
1486 args.push(volume_driver.clone());
1487 }
1488
1489 if let Some(ref userns) = self.userns {
1491 args.push("--userns".to_string());
1492 args.push(userns.clone());
1493 }
1494 if let Some(ref uts) = self.uts {
1495 args.push("--uts".to_string());
1496 args.push(uts.clone());
1497 }
1498 if let Some(ref pid) = self.pid {
1499 args.push("--pid".to_string());
1500 args.push(pid.clone());
1501 }
1502 if let Some(ref ipc) = self.ipc {
1503 args.push("--ipc".to_string());
1504 args.push(ipc.clone());
1505 }
1506 if let Some(ref cgroupns) = self.cgroupns {
1507 args.push("--cgroupns".to_string());
1508 args.push(cgroupns.clone());
1509 }
1510 if let Some(ref cgroup_parent) = self.cgroup_parent {
1511 args.push("--cgroup-parent".to_string());
1512 args.push(cgroup_parent.clone());
1513 }
1514
1515 if let Some(ref kernel_memory) = self.kernel_memory {
1517 args.push("--kernel-memory".to_string());
1518 args.push(kernel_memory.clone());
1519 }
1520 if let Some(memory_swappiness) = self.memory_swappiness {
1521 args.push("--memory-swappiness".to_string());
1522 args.push(memory_swappiness.to_string());
1523 }
1524 if let Some(oom_score_adj) = self.oom_score_adj {
1525 args.push("--oom-score-adj".to_string());
1526 args.push(oom_score_adj.to_string());
1527 }
1528 if let Some(pids_limit) = self.pids_limit {
1529 args.push("--pids-limit".to_string());
1530 args.push(pids_limit.to_string());
1531 }
1532 if let Some(ref shm_size) = self.shm_size {
1533 args.push("--shm-size".to_string());
1534 args.push(shm_size.clone());
1535 }
1536
1537 if let Some(ref stop_signal) = self.stop_signal {
1539 args.push("--stop-signal".to_string());
1540 args.push(stop_signal.clone());
1541 }
1542 if let Some(stop_timeout) = self.stop_timeout {
1543 args.push("--stop-timeout".to_string());
1544 args.push(stop_timeout.to_string());
1545 }
1546 if let Some(ref detach_keys) = self.detach_keys {
1547 args.push("--detach-keys".to_string());
1548 args.push(detach_keys.clone());
1549 }
1550
1551 if !self.sig_proxy {
1553 args.push("--sig-proxy=false".to_string());
1554 }
1555 if self.read_only {
1556 args.push("--read-only".to_string());
1557 }
1558 if self.init {
1559 args.push("--init".to_string());
1560 }
1561 if self.oom_kill_disable {
1562 args.push("--oom-kill-disable".to_string());
1563 }
1564 if self.no_healthcheck {
1565 args.push("--no-healthcheck".to_string());
1566 }
1567 if !self.disable_content_trust {
1568 args.push("--disable-content-trust=false".to_string());
1569 }
1570 if self.publish_all {
1571 args.push("--publish-all".to_string());
1572 }
1573 if self.quiet {
1574 args.push("--quiet".to_string());
1575 }
1576
1577 for dns in &self.dns {
1580 args.push("--dns".to_string());
1581 args.push(dns.clone());
1582 }
1583 for dns_option in &self.dns_option {
1584 args.push("--dns-option".to_string());
1585 args.push(dns_option.clone());
1586 }
1587 for dns_search in &self.dns_search {
1588 args.push("--dns-search".to_string());
1589 args.push(dns_search.clone());
1590 }
1591 for add_host in &self.add_host {
1592 args.push("--add-host".to_string());
1593 args.push(add_host.clone());
1594 }
1595
1596 for cap_add in &self.cap_add {
1598 args.push("--cap-add".to_string());
1599 args.push(cap_add.clone());
1600 }
1601 for cap_drop in &self.cap_drop {
1602 args.push("--cap-drop".to_string());
1603 args.push(cap_drop.clone());
1604 }
1605 for security_opt in &self.security_opt {
1606 args.push("--security-opt".to_string());
1607 args.push(security_opt.clone());
1608 }
1609
1610 for device in &self.device {
1612 args.push("--device".to_string());
1613 args.push(device.clone());
1614 }
1615 for tmpfs in &self.tmpfs {
1616 args.push("--tmpfs".to_string());
1617 args.push(tmpfs.clone());
1618 }
1619 for expose in &self.expose {
1620 args.push("--expose".to_string());
1621 args.push(expose.clone());
1622 }
1623
1624 for env_file in &self.env_file {
1626 args.push("--env-file".to_string());
1627 args.push(env_file.to_string_lossy().to_string());
1628 }
1629 for label in &self.label {
1630 args.push("--label".to_string());
1631 args.push(label.clone());
1632 }
1633 for label_file in &self.label_file {
1634 args.push("--label-file".to_string());
1635 args.push(label_file.to_string_lossy().to_string());
1636 }
1637
1638 for network_alias in &self.network_alias {
1640 args.push("--network-alias".to_string());
1641 args.push(network_alias.clone());
1642 }
1643 for group_add in &self.group_add {
1644 args.push("--group-add".to_string());
1645 args.push(group_add.clone());
1646 }
1647 for attach in &self.attach {
1648 args.push("--attach".to_string());
1649 args.push(attach.clone());
1650 }
1651 for log_opt in &self.log_opt {
1652 args.push("--log-opt".to_string());
1653 args.push(log_opt.clone());
1654 }
1655 for storage_opt in &self.storage_opt {
1656 args.push("--storage-opt".to_string());
1657 args.push(storage_opt.clone());
1658 }
1659 for ulimit in &self.ulimit {
1660 args.push("--ulimit".to_string());
1661 args.push(ulimit.clone());
1662 }
1663 for volumes_from in &self.volumes_from {
1664 args.push("--volumes-from".to_string());
1665 args.push(volumes_from.clone());
1666 }
1667 for link in &self.link {
1668 args.push("--link".to_string());
1669 args.push(link.clone());
1670 }
1671 for link_local_ip in &self.link_local_ip {
1672 args.push("--link-local-ip".to_string());
1673 args.push(link_local_ip.clone());
1674 }
1675
1676 if let Some(ref health_cmd) = self.health_cmd {
1679 args.push("--health-cmd".to_string());
1680 args.push(health_cmd.clone());
1681 }
1682 if let Some(ref health_interval) = self.health_interval {
1683 args.push("--health-interval".to_string());
1684 args.push(health_interval.clone());
1685 }
1686 if let Some(health_retries) = self.health_retries {
1687 args.push("--health-retries".to_string());
1688 args.push(health_retries.to_string());
1689 }
1690 if let Some(ref health_timeout) = self.health_timeout {
1691 args.push("--health-timeout".to_string());
1692 args.push(health_timeout.clone());
1693 }
1694 if let Some(ref health_start_period) = self.health_start_period {
1695 args.push("--health-start-period".to_string());
1696 args.push(health_start_period.clone());
1697 }
1698 if let Some(ref health_start_interval) = self.health_start_interval {
1699 args.push("--health-start-interval".to_string());
1700 args.push(health_start_interval.clone());
1701 }
1702
1703 for mount in &self.mount {
1705 args.push("--mount".to_string());
1706 args.push(mount.clone());
1707 }
1708 for network in &self.network {
1709 args.push("--network".to_string());
1710 args.push(network.clone());
1711 }
1712 if let Some(ref gpus) = self.gpus {
1713 args.push("--gpus".to_string());
1714 args.push(gpus.clone());
1715 }
1716
1717 for annotation in &self.annotation {
1719 args.push("--annotation".to_string());
1720 args.push(annotation.clone());
1721 }
1722 for sysctl in &self.sysctl {
1723 args.push("--sysctl".to_string());
1724 args.push(sysctl.clone());
1725 }
1726
1727 if let Some(blkio_weight) = self.blkio_weight {
1730 args.push("--blkio-weight".to_string());
1731 args.push(blkio_weight.to_string());
1732 }
1733 for blkio_weight_device in &self.blkio_weight_device {
1734 args.push("--blkio-weight-device".to_string());
1735 args.push(blkio_weight_device.clone());
1736 }
1737 for device_read_bps in &self.device_read_bps {
1738 args.push("--device-read-bps".to_string());
1739 args.push(device_read_bps.clone());
1740 }
1741 for device_write_bps in &self.device_write_bps {
1742 args.push("--device-write-bps".to_string());
1743 args.push(device_write_bps.clone());
1744 }
1745 for device_read_iops in &self.device_read_iops {
1746 args.push("--device-read-iops".to_string());
1747 args.push(device_read_iops.clone());
1748 }
1749 for device_write_iops in &self.device_write_iops {
1750 args.push("--device-write-iops".to_string());
1751 args.push(device_write_iops.clone());
1752 }
1753
1754 if let Some(cpu_rt_period) = self.cpu_rt_period {
1756 args.push("--cpu-rt-period".to_string());
1757 args.push(cpu_rt_period.to_string());
1758 }
1759 if let Some(cpu_rt_runtime) = self.cpu_rt_runtime {
1760 args.push("--cpu-rt-runtime".to_string());
1761 args.push(cpu_rt_runtime.to_string());
1762 }
1763
1764 if let Some(ref ip) = self.ip {
1766 args.push("--ip".to_string());
1767 args.push(ip.clone());
1768 }
1769 if let Some(ref ip6) = self.ip6 {
1770 args.push("--ip6".to_string());
1771 args.push(ip6.clone());
1772 }
1773
1774 for device_cgroup_rule in &self.device_cgroup_rule {
1776 args.push("--device-cgroup-rule".to_string());
1777 args.push(device_cgroup_rule.clone());
1778 }
1779
1780 args.push(self.image.clone());
1782
1783 if let Some(ref command) = self.command {
1785 args.extend(command.clone());
1786 }
1787
1788 args.extend(self.executor.raw_args.clone());
1790
1791 args
1792 }
1793
1794 async fn execute(&self) -> Result<Self::Output> {
1795 let args = self.build_command_args();
1796 let output = self.execute_command(args).await?;
1797
1798 let container_id = output.stdout.trim().to_string();
1800 if container_id.is_empty() {
1801 return Err(Error::parse_error(
1802 "No container ID returned from docker run",
1803 ));
1804 }
1805
1806 Ok(ContainerId(container_id))
1807 }
1808}
1809
1810#[async_trait]
1812impl StreamableCommand for RunCommand {
1813 async fn stream<F>(&self, handler: F) -> Result<StreamResult>
1814 where
1815 F: FnMut(OutputLine) + Send + 'static,
1816 {
1817 if self.detach {
1819 return Err(Error::custom(
1820 "Cannot stream output for detached containers",
1821 ));
1822 }
1823
1824 let mut cmd = TokioCommand::new("docker");
1825 cmd.arg("run");
1826
1827 for arg in self.build_command_args() {
1828 cmd.arg(arg);
1829 }
1830
1831 crate::stream::stream_command(cmd, handler).await
1832 }
1833
1834 async fn stream_channel(&self) -> Result<(mpsc::Receiver<OutputLine>, StreamResult)> {
1835 if self.detach {
1837 return Err(Error::custom(
1838 "Cannot stream output for detached containers",
1839 ));
1840 }
1841
1842 let mut cmd = TokioCommand::new("docker");
1843 cmd.arg("run");
1844
1845 for arg in self.build_command_args() {
1846 cmd.arg(arg);
1847 }
1848
1849 crate::stream::stream_command_channel(cmd).await
1850 }
1851}
1852
1853impl RunCommand {
1854 pub async fn stream<F>(&self, handler: F) -> Result<StreamResult>
1877 where
1878 F: FnMut(OutputLine) + Send + 'static,
1879 {
1880 <Self as StreamableCommand>::stream(self, handler).await
1881 }
1882}
1883
1884#[cfg(test)]
1885mod tests {
1886 use super::*;
1887
1888 #[test]
1889 fn test_run_command_builder() {
1890 let cmd = RunCommand::new("nginx:latest")
1891 .name("test-nginx")
1892 .detach()
1893 .env("ENV_VAR", "value")
1894 .port(8080, 80)
1895 .volume("data", "/var/data")
1896 .workdir("/app")
1897 .remove();
1898
1899 let args = cmd.build_command_args();
1900
1901 assert!(args.contains(&"--detach".to_string()));
1902 assert!(args.contains(&"--name".to_string()));
1903 assert!(args.contains(&"test-nginx".to_string()));
1904 assert!(args.contains(&"--env".to_string()));
1905 assert!(args.contains(&"ENV_VAR=value".to_string()));
1906 assert!(args.contains(&"--publish".to_string()));
1907 assert!(args.contains(&"8080:80".to_string()));
1908 assert!(args.contains(&"--volume".to_string()));
1909 assert!(args.contains(&"data:/var/data".to_string()));
1910 assert!(args.contains(&"--workdir".to_string()));
1911 assert!(args.contains(&"/app".to_string()));
1912 assert!(args.contains(&"--rm".to_string()));
1913 assert!(args.contains(&"nginx:latest".to_string()));
1914 }
1915
1916 #[test]
1917 fn test_run_command_with_cmd() {
1918 let cmd =
1919 RunCommand::new("alpine:latest").cmd(vec!["echo".to_string(), "hello".to_string()]);
1920
1921 let args = cmd.build_command_args();
1922 assert!(args.contains(&"alpine:latest".to_string()));
1923 assert!(args.contains(&"echo".to_string()));
1924 assert!(args.contains(&"hello".to_string()));
1925 }
1926
1927 #[test]
1928 fn test_run_command_extensibility() {
1929 let mut cmd = RunCommand::new("test:latest");
1930 cmd.flag("privileged")
1931 .option("memory", "1g")
1932 .arg("--custom-option");
1933
1934 }
1937
1938 #[test]
1939 fn test_volume_mount_display() {
1940 let volume = VolumeMount {
1941 source: "data".to_string(),
1942 target: "/var/data".to_string(),
1943 mount_type: MountType::Volume,
1944 readonly: false,
1945 };
1946 assert_eq!(volume.to_string(), "data:/var/data");
1947
1948 let readonly_volume = VolumeMount {
1949 source: "/host/path".to_string(),
1950 target: "/container/path".to_string(),
1951 mount_type: MountType::Bind,
1952 readonly: true,
1953 };
1954 assert_eq!(readonly_volume.to_string(), "/host/path:/container/path:ro");
1955 }
1956
1957 #[test]
1958 fn test_run_command_resource_limits() {
1959 let cmd = RunCommand::new("alpine:latest")
1960 .memory("1g")
1961 .cpus("2.0")
1962 .cpu_shares(1024)
1963 .cpu_period(100_000)
1964 .cpu_quota(50_000)
1965 .cpuset_cpus("0-3")
1966 .cpuset_mems("0,1")
1967 .memory_swap("2g")
1968 .memory_reservation("500m");
1969
1970 let args = cmd.build_command_args();
1971
1972 assert!(args.contains(&"--memory".to_string()));
1973 assert!(args.contains(&"1g".to_string()));
1974 assert!(args.contains(&"--cpus".to_string()));
1975 assert!(args.contains(&"2.0".to_string()));
1976 assert!(args.contains(&"--cpu-shares".to_string()));
1977 assert!(args.contains(&"1024".to_string()));
1978 assert!(args.contains(&"--cpu-period".to_string()));
1979 assert!(args.contains(&"100000".to_string()));
1980 assert!(args.contains(&"--cpu-quota".to_string()));
1981 assert!(args.contains(&"50000".to_string()));
1982 assert!(args.contains(&"--cpuset-cpus".to_string()));
1983 assert!(args.contains(&"0-3".to_string()));
1984 assert!(args.contains(&"--cpuset-mems".to_string()));
1985 assert!(args.contains(&"0,1".to_string()));
1986 assert!(args.contains(&"--memory-swap".to_string()));
1987 assert!(args.contains(&"2g".to_string()));
1988 assert!(args.contains(&"--memory-reservation".to_string()));
1989 assert!(args.contains(&"500m".to_string()));
1990 }
1991
1992 #[test]
1993 fn test_run_command_security_and_user() {
1994 let cmd = RunCommand::new("alpine:latest")
1995 .user("1000:1000")
1996 .privileged()
1997 .hostname("test-host");
1998
1999 let args = cmd.build_command_args();
2000
2001 assert!(args.contains(&"--user".to_string()));
2002 assert!(args.contains(&"1000:1000".to_string()));
2003 assert!(args.contains(&"--privileged".to_string()));
2004 assert!(args.contains(&"--hostname".to_string()));
2005 assert!(args.contains(&"test-host".to_string()));
2006 }
2007
2008 #[test]
2009 fn test_run_command_lifecycle_management() {
2010 let cmd = RunCommand::new("alpine:latest").restart("always");
2011
2012 let args = cmd.build_command_args();
2013
2014 assert!(args.contains(&"--restart".to_string()));
2015 assert!(args.contains(&"always".to_string()));
2016 }
2017
2018 #[test]
2019 fn test_run_command_system_integration() {
2020 let cmd = RunCommand::new("alpine:latest")
2021 .platform("linux/amd64")
2022 .runtime("runc")
2023 .isolation("default")
2024 .pull("always")
2025 .cidfile("/tmp/container.cid")
2026 .domainname("example.com")
2027 .mac_address("92:d0:c6:0a:29:33");
2028
2029 let args = cmd.build_command_args();
2030
2031 assert!(args.contains(&"--platform".to_string()));
2032 assert!(args.contains(&"linux/amd64".to_string()));
2033 assert!(args.contains(&"--runtime".to_string()));
2034 assert!(args.contains(&"runc".to_string()));
2035 assert!(args.contains(&"--isolation".to_string()));
2036 assert!(args.contains(&"default".to_string()));
2037 assert!(args.contains(&"--pull".to_string()));
2038 assert!(args.contains(&"always".to_string()));
2039 assert!(args.contains(&"--cidfile".to_string()));
2040 assert!(args.contains(&"/tmp/container.cid".to_string()));
2041 assert!(args.contains(&"--domainname".to_string()));
2042 assert!(args.contains(&"example.com".to_string()));
2043 assert!(args.contains(&"--mac-address".to_string()));
2044 assert!(args.contains(&"92:d0:c6:0a:29:33".to_string()));
2045 }
2046
2047 #[test]
2048 fn test_run_command_logging_and_drivers() {
2049 let cmd = RunCommand::new("alpine:latest")
2050 .log_driver("json-file")
2051 .volume_driver("local");
2052
2053 let args = cmd.build_command_args();
2054
2055 assert!(args.contains(&"--log-driver".to_string()));
2056 assert!(args.contains(&"json-file".to_string()));
2057 assert!(args.contains(&"--volume-driver".to_string()));
2058 assert!(args.contains(&"local".to_string()));
2059 }
2060
2061 #[test]
2062 fn test_run_command_namespaces() {
2063 let cmd = RunCommand::new("alpine:latest")
2064 .userns("host")
2065 .uts("host")
2066 .pid("host")
2067 .ipc("host")
2068 .cgroupns("private")
2069 .cgroup_parent("/docker");
2070
2071 let args = cmd.build_command_args();
2072
2073 assert!(args.contains(&"--userns".to_string()));
2074 assert!(args.contains(&"host".to_string()));
2075 assert!(args.contains(&"--uts".to_string()));
2076 assert!(args.contains(&"--pid".to_string()));
2077 assert!(args.contains(&"--ipc".to_string()));
2078 assert!(args.contains(&"--cgroupns".to_string()));
2079 assert!(args.contains(&"private".to_string()));
2080 assert!(args.contains(&"--cgroup-parent".to_string()));
2081 assert!(args.contains(&"/docker".to_string()));
2082 }
2083
2084 #[test]
2085 fn test_run_command_advanced_memory_performance() {
2086 let cmd = RunCommand::new("alpine:latest")
2087 .kernel_memory("100m")
2088 .memory_swappiness(60)
2089 .oom_score_adj(-500)
2090 .pids_limit(100)
2091 .shm_size("64m");
2092
2093 let args = cmd.build_command_args();
2094
2095 assert!(args.contains(&"--kernel-memory".to_string()));
2096 assert!(args.contains(&"100m".to_string()));
2097 assert!(args.contains(&"--memory-swappiness".to_string()));
2098 assert!(args.contains(&"60".to_string()));
2099 assert!(args.contains(&"--oom-score-adj".to_string()));
2100 assert!(args.contains(&"-500".to_string()));
2101 assert!(args.contains(&"--pids-limit".to_string()));
2102 assert!(args.contains(&"100".to_string()));
2103 assert!(args.contains(&"--shm-size".to_string()));
2104 assert!(args.contains(&"64m".to_string()));
2105 }
2106
2107 #[test]
2108 fn test_run_command_process_control() {
2109 let cmd = RunCommand::new("alpine:latest")
2110 .stop_signal("SIGTERM")
2111 .stop_timeout(10)
2112 .detach_keys("ctrl-p,ctrl-q");
2113
2114 let args = cmd.build_command_args();
2115
2116 assert!(args.contains(&"--stop-signal".to_string()));
2117 assert!(args.contains(&"SIGTERM".to_string()));
2118 assert!(args.contains(&"--stop-timeout".to_string()));
2119 assert!(args.contains(&"10".to_string()));
2120 assert!(args.contains(&"--detach-keys".to_string()));
2121 assert!(args.contains(&"ctrl-p,ctrl-q".to_string()));
2122 }
2123
2124 #[test]
2125 fn test_run_command_simple_flags() {
2126 let cmd = RunCommand::new("alpine:latest")
2127 .no_sig_proxy()
2128 .read_only()
2129 .init()
2130 .oom_kill_disable()
2131 .no_healthcheck()
2132 .enable_content_trust()
2133 .publish_all()
2134 .quiet();
2135
2136 let args = cmd.build_command_args();
2137
2138 assert!(args.contains(&"--sig-proxy=false".to_string()));
2139 assert!(args.contains(&"--read-only".to_string()));
2140 assert!(args.contains(&"--init".to_string()));
2141 assert!(args.contains(&"--oom-kill-disable".to_string()));
2142 assert!(args.contains(&"--no-healthcheck".to_string()));
2143 assert!(args.contains(&"--disable-content-trust=false".to_string()));
2144 assert!(args.contains(&"--publish-all".to_string()));
2145 assert!(args.contains(&"--quiet".to_string()));
2146 }
2147
2148 #[test]
2149 fn test_run_command_comprehensive_builder() {
2150 let cmd = RunCommand::new("nginx:latest")
2151 .name("production-nginx")
2152 .detach()
2153 .memory("2g")
2154 .cpus("4.0")
2155 .user("nginx:nginx")
2156 .privileged()
2157 .restart("unless-stopped")
2158 .hostname("web-server")
2159 .platform("linux/amd64")
2160 .env("NGINX_PORT", "8080")
2161 .port(80, 8080)
2162 .volume("nginx-data", "/var/lib/nginx")
2163 .workdir("/usr/share/nginx/html")
2164 .read_only()
2165 .init()
2166 .remove();
2167
2168 let args = cmd.build_command_args();
2169
2170 assert!(args.contains(&"--name".to_string()));
2172 assert!(args.contains(&"production-nginx".to_string()));
2173 assert!(args.contains(&"--detach".to_string()));
2174 assert!(args.contains(&"--memory".to_string()));
2175 assert!(args.contains(&"2g".to_string()));
2176 assert!(args.contains(&"--cpus".to_string()));
2177 assert!(args.contains(&"4.0".to_string()));
2178 assert!(args.contains(&"--user".to_string()));
2179 assert!(args.contains(&"nginx:nginx".to_string()));
2180 assert!(args.contains(&"--privileged".to_string()));
2181 assert!(args.contains(&"--restart".to_string()));
2182 assert!(args.contains(&"unless-stopped".to_string()));
2183 assert!(args.contains(&"--hostname".to_string()));
2184 assert!(args.contains(&"web-server".to_string()));
2185 assert!(args.contains(&"--platform".to_string()));
2186 assert!(args.contains(&"linux/amd64".to_string()));
2187 assert!(args.contains(&"--read-only".to_string()));
2188 assert!(args.contains(&"--init".to_string()));
2189 assert!(args.contains(&"--rm".to_string()));
2190 assert!(args.contains(&"nginx:latest".to_string()));
2191
2192 let image_pos = args.iter().position(|x| x == "nginx:latest").unwrap();
2194 assert!(image_pos > 10); }
2196
2197 #[test]
2198 fn test_run_command_default_flag_values() {
2199 let cmd = RunCommand::new("alpine:latest");
2200 let args = cmd.build_command_args();
2201
2202 assert!(!args.contains(&"--sig-proxy=false".to_string()));
2204 assert!(!args.contains(&"--disable-content-trust=false".to_string()));
2205 assert!(!args.contains(&"--read-only".to_string()));
2206 assert!(!args.contains(&"--privileged".to_string()));
2207 assert!(!args.contains(&"--init".to_string()));
2208 }
2209
2210 #[test]
2211 fn test_container_id() {
2212 let id = ContainerId("abcdef123456789".to_string());
2213 assert_eq!(id.as_str(), "abcdef123456789");
2214 assert_eq!(id.short(), "abcdef123456");
2215 assert_eq!(id.to_string(), "abcdef123456789");
2216
2217 let short_id = ContainerId("abc".to_string());
2218 assert_eq!(short_id.short(), "abc");
2219 }
2220
2221 #[test]
2222 fn test_it_convenience_method() {
2223 let cmd = RunCommand::new("alpine:latest").it();
2224 let args = cmd.build_command_args();
2225 assert!(args.contains(&"--interactive".to_string()));
2226 assert!(args.contains(&"--tty".to_string()));
2227 }
2228
2229 #[test]
2230 fn test_run_command_dns_network_options() {
2231 let cmd = RunCommand::new("alpine:latest")
2232 .dns("8.8.8.8")
2233 .dns("8.8.4.4")
2234 .dns_servers(vec!["1.1.1.1".to_string(), "1.0.0.1".to_string()])
2235 .dns_option("ndots:2")
2236 .dns_option("timeout:1")
2237 .dns_search("example.com")
2238 .dns_search("test.local")
2239 .add_host("api.example.com:127.0.0.1")
2240 .add_host("db.example.com:192.168.1.100");
2241
2242 let args = cmd.build_command_args();
2243
2244 assert!(args.contains(&"--dns".to_string()));
2246 assert!(args.contains(&"8.8.8.8".to_string()));
2247 assert!(args.contains(&"8.8.4.4".to_string()));
2248 assert!(args.contains(&"1.1.1.1".to_string()));
2249 assert!(args.contains(&"1.0.0.1".to_string()));
2250
2251 assert!(args.contains(&"--dns-option".to_string()));
2253 assert!(args.contains(&"ndots:2".to_string()));
2254 assert!(args.contains(&"timeout:1".to_string()));
2255
2256 assert!(args.contains(&"--dns-search".to_string()));
2258 assert!(args.contains(&"example.com".to_string()));
2259 assert!(args.contains(&"test.local".to_string()));
2260
2261 assert!(args.contains(&"--add-host".to_string()));
2263 assert!(args.contains(&"api.example.com:127.0.0.1".to_string()));
2264 assert!(args.contains(&"db.example.com:192.168.1.100".to_string()));
2265 }
2266
2267 #[test]
2268 fn test_run_command_security_capabilities() {
2269 let cmd = RunCommand::new("alpine:latest")
2270 .cap_add("NET_ADMIN")
2271 .cap_add("SYS_TIME")
2272 .cap_drop("CHOWN")
2273 .cap_drop("DAC_OVERRIDE")
2274 .security_opt("no-new-privileges:true")
2275 .security_opt("seccomp=unconfined");
2276
2277 let args = cmd.build_command_args();
2278
2279 assert!(args.contains(&"--cap-add".to_string()));
2281 assert!(args.contains(&"NET_ADMIN".to_string()));
2282 assert!(args.contains(&"SYS_TIME".to_string()));
2283
2284 assert!(args.contains(&"--cap-drop".to_string()));
2286 assert!(args.contains(&"CHOWN".to_string()));
2287 assert!(args.contains(&"DAC_OVERRIDE".to_string()));
2288
2289 assert!(args.contains(&"--security-opt".to_string()));
2291 assert!(args.contains(&"no-new-privileges:true".to_string()));
2292 assert!(args.contains(&"seccomp=unconfined".to_string()));
2293 }
2294
2295 #[test]
2296 fn test_run_command_device_filesystem() {
2297 let cmd = RunCommand::new("alpine:latest")
2298 .device("/dev/sda:/dev/xvda:rwm")
2299 .device("/dev/zero")
2300 .tmpfs("/tmp:rw,size=100m")
2301 .tmpfs("/var/tmp:ro")
2302 .expose("80")
2303 .expose("443")
2304 .expose("8080/tcp");
2305
2306 let args = cmd.build_command_args();
2307
2308 assert!(args.contains(&"--device".to_string()));
2310 assert!(args.contains(&"/dev/sda:/dev/xvda:rwm".to_string()));
2311 assert!(args.contains(&"/dev/zero".to_string()));
2312
2313 assert!(args.contains(&"--tmpfs".to_string()));
2315 assert!(args.contains(&"/tmp:rw,size=100m".to_string()));
2316 assert!(args.contains(&"/var/tmp:ro".to_string()));
2317
2318 assert!(args.contains(&"--expose".to_string()));
2320 assert!(args.contains(&"80".to_string()));
2321 assert!(args.contains(&"443".to_string()));
2322 assert!(args.contains(&"8080/tcp".to_string()));
2323 }
2324
2325 #[test]
2326 fn test_run_command_environment_labels() {
2327 use std::path::PathBuf;
2328
2329 let cmd = RunCommand::new("alpine:latest")
2330 .env_file(PathBuf::from("/etc/environment"))
2331 .env_file(PathBuf::from("./app.env"))
2332 .label("version=1.0.0")
2333 .label("maintainer=team@example.com")
2334 .label("app=myapp")
2335 .label_file(PathBuf::from("/etc/labels"))
2336 .label_file(PathBuf::from("./metadata.labels"));
2337
2338 let args = cmd.build_command_args();
2339
2340 assert!(args.contains(&"--env-file".to_string()));
2342 assert!(args.contains(&"/etc/environment".to_string()));
2343 assert!(args.contains(&"./app.env".to_string()));
2344
2345 assert!(args.contains(&"--label".to_string()));
2347 assert!(args.contains(&"version=1.0.0".to_string()));
2348 assert!(args.contains(&"maintainer=team@example.com".to_string()));
2349 assert!(args.contains(&"app=myapp".to_string()));
2350
2351 assert!(args.contains(&"--label-file".to_string()));
2353 assert!(args.contains(&"/etc/labels".to_string()));
2354 assert!(args.contains(&"./metadata.labels".to_string()));
2355 }
2356
2357 #[test]
2358 fn test_run_command_all_high_impact_options() {
2359 use std::path::PathBuf;
2360
2361 let cmd = RunCommand::new("nginx:latest")
2362 .name("production-nginx")
2363 .dns("8.8.8.8")
2365 .dns_option("ndots:2")
2366 .dns_search("example.com")
2367 .add_host("api.example.com:127.0.0.1")
2368 .cap_add("NET_ADMIN")
2370 .cap_drop("CHOWN")
2371 .security_opt("no-new-privileges:true")
2372 .device("/dev/null")
2374 .tmpfs("/tmp:rw,size=100m")
2375 .expose("80")
2376 .env_file(PathBuf::from(".env"))
2378 .label("version=1.0.0")
2379 .label_file(PathBuf::from("labels"));
2380
2381 let args = cmd.build_command_args();
2382
2383 assert!(args.contains(&"--dns".to_string()));
2385 assert!(args.contains(&"--dns-option".to_string()));
2386 assert!(args.contains(&"--dns-search".to_string()));
2387 assert!(args.contains(&"--add-host".to_string()));
2388 assert!(args.contains(&"--cap-add".to_string()));
2389 assert!(args.contains(&"--cap-drop".to_string()));
2390 assert!(args.contains(&"--security-opt".to_string()));
2391 assert!(args.contains(&"--device".to_string()));
2392 assert!(args.contains(&"--tmpfs".to_string()));
2393 assert!(args.contains(&"--expose".to_string()));
2394 assert!(args.contains(&"--env-file".to_string()));
2395 assert!(args.contains(&"--label".to_string()));
2396 assert!(args.contains(&"--label-file".to_string()));
2397
2398 let image_pos = args.iter().position(|x| x == "nginx:latest").unwrap();
2400 assert!(image_pos > 0); assert!(image_pos < args.len() - 1 || args.len() == image_pos + 1); }
2403
2404 #[test]
2405 fn test_run_command_empty_lists_not_added() {
2406 let cmd = RunCommand::new("alpine:latest");
2407 let args = cmd.build_command_args();
2408
2409 assert!(!args.contains(&"--dns".to_string()));
2411 assert!(!args.contains(&"--dns-option".to_string()));
2412 assert!(!args.contains(&"--dns-search".to_string()));
2413 assert!(!args.contains(&"--add-host".to_string()));
2414 assert!(!args.contains(&"--cap-add".to_string()));
2415 assert!(!args.contains(&"--cap-drop".to_string()));
2416 assert!(!args.contains(&"--security-opt".to_string()));
2417 assert!(!args.contains(&"--device".to_string()));
2418 assert!(!args.contains(&"--tmpfs".to_string()));
2419 assert!(!args.contains(&"--expose".to_string()));
2420 assert!(!args.contains(&"--env-file".to_string()));
2421 assert!(!args.contains(&"--label".to_string()));
2422 assert!(!args.contains(&"--label-file".to_string()));
2423
2424 assert!(!args.contains(&"--network-alias".to_string()));
2426 assert!(!args.contains(&"--group-add".to_string()));
2427 assert!(!args.contains(&"--attach".to_string()));
2428 assert!(!args.contains(&"--log-opt".to_string()));
2429 assert!(!args.contains(&"--storage-opt".to_string()));
2430 assert!(!args.contains(&"--ulimit".to_string()));
2431 assert!(!args.contains(&"--volumes-from".to_string()));
2432 assert!(!args.contains(&"--link".to_string()));
2433 assert!(!args.contains(&"--link-local-ip".to_string()));
2434
2435 assert!(args.contains(&"alpine:latest".to_string()));
2437 }
2438
2439 #[test]
2440 fn test_run_command_additional_list_options() {
2441 let cmd = RunCommand::new("alpine:latest")
2442 .network_alias("web")
2443 .network_alias("frontend")
2444 .group_add("staff")
2445 .group_add("docker")
2446 .attach("stdout")
2447 .attach("stderr")
2448 .log_opt("max-size=10m")
2449 .log_opt("max-file=3")
2450 .storage_opt("size=20G")
2451 .ulimit("nofile=1024:65536")
2452 .ulimit("nproc=1024")
2453 .volumes_from("data-container")
2454 .volumes_from("config-container:ro")
2455 .link("db:database")
2456 .link("cache:redis")
2457 .link_local_ip("169.254.1.1")
2458 .link_local_ip("fe80::1");
2459
2460 let args = cmd.build_command_args();
2461
2462 assert!(args.contains(&"--network-alias".to_string()));
2464 assert!(args.contains(&"web".to_string()));
2465 assert!(args.contains(&"frontend".to_string()));
2466
2467 assert!(args.contains(&"--group-add".to_string()));
2469 assert!(args.contains(&"staff".to_string()));
2470 assert!(args.contains(&"docker".to_string()));
2471
2472 assert!(args.contains(&"--attach".to_string()));
2474 assert!(args.contains(&"stdout".to_string()));
2475 assert!(args.contains(&"stderr".to_string()));
2476
2477 assert!(args.contains(&"--log-opt".to_string()));
2479 assert!(args.contains(&"max-size=10m".to_string()));
2480 assert!(args.contains(&"max-file=3".to_string()));
2481
2482 assert!(args.contains(&"--storage-opt".to_string()));
2484 assert!(args.contains(&"size=20G".to_string()));
2485
2486 assert!(args.contains(&"--ulimit".to_string()));
2488 assert!(args.contains(&"nofile=1024:65536".to_string()));
2489 assert!(args.contains(&"nproc=1024".to_string()));
2490
2491 assert!(args.contains(&"--volumes-from".to_string()));
2493 assert!(args.contains(&"data-container".to_string()));
2494 assert!(args.contains(&"config-container:ro".to_string()));
2495
2496 assert!(args.contains(&"--link".to_string()));
2498 assert!(args.contains(&"db:database".to_string()));
2499 assert!(args.contains(&"cache:redis".to_string()));
2500
2501 assert!(args.contains(&"--link-local-ip".to_string()));
2503 assert!(args.contains(&"169.254.1.1".to_string()));
2504 assert!(args.contains(&"fe80::1".to_string()));
2505 }
2506
2507 #[test]
2508 fn test_run_command_additional_list_individual_options() {
2509 let network_cmd = RunCommand::new("alpine:latest").network_alias("api");
2511 let network_args = network_cmd.build_command_args();
2512 assert!(network_args.contains(&"--network-alias".to_string()));
2513 assert!(network_args.contains(&"api".to_string()));
2514
2515 let group_cmd = RunCommand::new("alpine:latest").group_add("wheel");
2516 let group_args = group_cmd.build_command_args();
2517 assert!(group_args.contains(&"--group-add".to_string()));
2518 assert!(group_args.contains(&"wheel".to_string()));
2519
2520 let attach_cmd = RunCommand::new("alpine:latest").attach("stdin");
2521 let attach_args = attach_cmd.build_command_args();
2522 assert!(attach_args.contains(&"--attach".to_string()));
2523 assert!(attach_args.contains(&"stdin".to_string()));
2524
2525 let log_cmd = RunCommand::new("alpine:latest").log_opt("compress=true");
2526 let log_args = log_cmd.build_command_args();
2527 assert!(log_args.contains(&"--log-opt".to_string()));
2528 assert!(log_args.contains(&"compress=true".to_string()));
2529
2530 let storage_cmd =
2531 RunCommand::new("alpine:latest").storage_opt("dm.thinpooldev=/dev/mapper/thin-pool");
2532 let storage_args = storage_cmd.build_command_args();
2533 assert!(storage_args.contains(&"--storage-opt".to_string()));
2534 assert!(storage_args.contains(&"dm.thinpooldev=/dev/mapper/thin-pool".to_string()));
2535
2536 let ulimit_cmd = RunCommand::new("alpine:latest").ulimit("memlock=-1:-1");
2537 let ulimit_args = ulimit_cmd.build_command_args();
2538 assert!(ulimit_args.contains(&"--ulimit".to_string()));
2539 assert!(ulimit_args.contains(&"memlock=-1:-1".to_string()));
2540
2541 let volumes_cmd = RunCommand::new("alpine:latest").volumes_from("shared-data");
2542 let volumes_args = volumes_cmd.build_command_args();
2543 assert!(volumes_args.contains(&"--volumes-from".to_string()));
2544 assert!(volumes_args.contains(&"shared-data".to_string()));
2545
2546 let link_cmd = RunCommand::new("alpine:latest").link("mysql:db");
2547 let link_args = link_cmd.build_command_args();
2548 assert!(link_args.contains(&"--link".to_string()));
2549 assert!(link_args.contains(&"mysql:db".to_string()));
2550
2551 let ip_cmd = RunCommand::new("alpine:latest").link_local_ip("169.254.100.1");
2552 let ip_args = ip_cmd.build_command_args();
2553 assert!(ip_args.contains(&"--link-local-ip".to_string()));
2554 assert!(ip_args.contains(&"169.254.100.1".to_string()));
2555 }
2556
2557 #[test]
2558 fn test_run_command_health_check_options() {
2559 let cmd = RunCommand::new("nginx:latest")
2560 .health_cmd("curl -f http://localhost/ || exit 1")
2561 .health_interval("30s")
2562 .health_retries(3)
2563 .health_timeout("5s")
2564 .health_start_period("60s")
2565 .health_start_interval("5s");
2566
2567 let args = cmd.build_command_args();
2568
2569 assert!(args.contains(&"--health-cmd".to_string()));
2571 assert!(args.contains(&"curl -f http://localhost/ || exit 1".to_string()));
2572 assert!(args.contains(&"--health-interval".to_string()));
2573 assert!(args.contains(&"30s".to_string()));
2574 assert!(args.contains(&"--health-retries".to_string()));
2575 assert!(args.contains(&"3".to_string()));
2576 assert!(args.contains(&"--health-timeout".to_string()));
2577 assert!(args.contains(&"5s".to_string()));
2578 assert!(args.contains(&"--health-start-period".to_string()));
2579 assert!(args.contains(&"60s".to_string()));
2580 assert!(args.contains(&"--health-start-interval".to_string()));
2581 }
2583
2584 #[test]
2585 fn test_run_command_advanced_mount_network_options() {
2586 let cmd = RunCommand::new("alpine:latest")
2587 .mount("type=bind,source=/host/path,target=/container/path")
2588 .mount("type=volume,source=data-vol,target=/data")
2589 .network("frontend")
2590 .network("backend")
2591 .gpus("all")
2592 .annotation("io.kubernetes.cri-o.Devices", "/dev/fuse")
2593 .annotation("io.kubernetes.cri-o.ShmSize", "64m")
2594 .sysctl("net.core.somaxconn", "1024")
2595 .sysctl("kernel.shm_rmid_forced", "1");
2596
2597 let args = cmd.build_command_args();
2598
2599 assert!(args.contains(&"--mount".to_string()));
2601 assert!(args.contains(&"type=bind,source=/host/path,target=/container/path".to_string()));
2602 assert!(args.contains(&"type=volume,source=data-vol,target=/data".to_string()));
2603
2604 assert!(args.contains(&"--network".to_string()));
2606 assert!(args.contains(&"frontend".to_string()));
2607 assert!(args.contains(&"backend".to_string()));
2608
2609 assert!(args.contains(&"--gpus".to_string()));
2611 assert!(args.contains(&"all".to_string()));
2612
2613 assert!(args.contains(&"--annotation".to_string()));
2615 assert!(args.contains(&"io.kubernetes.cri-o.Devices=/dev/fuse".to_string()));
2616 assert!(args.contains(&"io.kubernetes.cri-o.ShmSize=64m".to_string()));
2617
2618 assert!(args.contains(&"--sysctl".to_string()));
2620 assert!(args.contains(&"net.core.somaxconn=1024".to_string()));
2621 assert!(args.contains(&"kernel.shm_rmid_forced=1".to_string()));
2622 }
2623
2624 #[test]
2625 fn test_run_command_health_advanced_individual_options() {
2626 let health_cmd = RunCommand::new("alpine:latest").health_cmd("ping -c 1 localhost");
2628 let health_args = health_cmd.build_command_args();
2629 assert!(health_args.contains(&"--health-cmd".to_string()));
2630 assert!(health_args.contains(&"ping -c 1 localhost".to_string()));
2631
2632 let health_interval = RunCommand::new("alpine:latest").health_interval("10s");
2633 let interval_args = health_interval.build_command_args();
2634 assert!(interval_args.contains(&"--health-interval".to_string()));
2635 assert!(interval_args.contains(&"10s".to_string()));
2636
2637 let health_retries = RunCommand::new("alpine:latest").health_retries(5);
2638 let retries_args = health_retries.build_command_args();
2639 assert!(retries_args.contains(&"--health-retries".to_string()));
2640 assert!(retries_args.contains(&"5".to_string()));
2641
2642 let mount_cmd = RunCommand::new("alpine:latest").mount("type=tmpfs,destination=/app");
2643 let mount_args = mount_cmd.build_command_args();
2644 assert!(mount_args.contains(&"--mount".to_string()));
2645 assert!(mount_args.contains(&"type=tmpfs,destination=/app".to_string()));
2646
2647 let network_cmd = RunCommand::new("alpine:latest").network("my-network");
2648 let network_args = network_cmd.build_command_args();
2649 assert!(network_args.contains(&"--network".to_string()));
2650 assert!(network_args.contains(&"my-network".to_string()));
2651
2652 let gpu_cmd = RunCommand::new("alpine:latest").gpus("device=0");
2653 let gpu_args = gpu_cmd.build_command_args();
2654 assert!(gpu_args.contains(&"--gpus".to_string()));
2655 assert!(gpu_args.contains(&"device=0".to_string()));
2656
2657 let annotation_cmd = RunCommand::new("alpine:latest").annotation("key", "value");
2658 let annotation_args = annotation_cmd.build_command_args();
2659 assert!(annotation_args.contains(&"--annotation".to_string()));
2660 assert!(annotation_args.contains(&"key=value".to_string()));
2661
2662 let sysctl_cmd = RunCommand::new("alpine:latest").sysctl("net.ipv4.ip_forward", "1");
2663 let sysctl_args = sysctl_cmd.build_command_args();
2664 assert!(sysctl_args.contains(&"--sysctl".to_string()));
2665 assert!(sysctl_args.contains(&"net.ipv4.ip_forward=1".to_string()));
2666 }
2667
2668 #[test]
2669 fn test_run_command_comprehensive_health_advanced_integration() {
2670 let cmd = RunCommand::new("web-app:latest")
2671 .name("production-web-app")
2672 .health_cmd("curl -f http://localhost:8080/health || exit 1")
2674 .health_interval("30s")
2675 .health_retries(3)
2676 .health_timeout("10s")
2677 .health_start_period("120s")
2678 .mount("type=bind,source=/var/log/app,target=/app/logs")
2680 .mount("type=volume,source=app-data,target=/app/data")
2681 .network("frontend")
2682 .network("backend")
2683 .gpus("device=0,1")
2685 .annotation(
2687 "io.kubernetes.container.apparmor.security.beta.kubernetes.io/app",
2688 "runtime/default",
2689 )
2690 .annotation(
2691 "io.kubernetes.container.seccomp.security.alpha.kubernetes.io/app",
2692 "runtime/default",
2693 )
2694 .sysctl("net.core.somaxconn", "65535")
2696 .sysctl("net.ipv4.tcp_keepalive_time", "600")
2697 .port(8080, 8080)
2699 .env("NODE_ENV", "production")
2700 .memory("2g")
2701 .cpus("2.0")
2702 .restart("unless-stopped")
2703 .detach();
2704
2705 let args = cmd.build_command_args();
2706
2707 assert!(args.contains(&"--health-cmd".to_string()));
2709 assert!(args.contains(&"--health-interval".to_string()));
2710 assert!(args.contains(&"--health-retries".to_string()));
2711 assert!(args.contains(&"--health-timeout".to_string()));
2712 assert!(args.contains(&"--health-start-period".to_string()));
2713 assert!(args.contains(&"--mount".to_string()));
2714 assert!(args.contains(&"--network".to_string()));
2715 assert!(args.contains(&"--gpus".to_string()));
2716 assert!(args.contains(&"--annotation".to_string()));
2717 assert!(args.contains(&"--sysctl".to_string()));
2718
2719 let image_pos = args.iter().position(|x| x == "web-app:latest").unwrap();
2721 assert!(image_pos > 0);
2722 }
2723
2724 #[test]
2725 fn test_run_command_block_io_controls() {
2726 let cmd = RunCommand::new("alpine:latest")
2727 .blkio_weight(500)
2728 .blkio_weight_device("/dev/sda:300")
2729 .blkio_weight_device("/dev/sdb:700")
2730 .device_read_bps("/dev/sda:50mb")
2731 .device_write_bps("/dev/sda:30mb")
2732 .device_read_iops("/dev/sda:1000")
2733 .device_write_iops("/dev/sda:800");
2734
2735 let args = cmd.build_command_args();
2736
2737 assert!(args.contains(&"--blkio-weight".to_string()));
2739 assert!(args.contains(&"500".to_string()));
2740
2741 assert!(args.contains(&"--blkio-weight-device".to_string()));
2743 assert!(args.contains(&"/dev/sda:300".to_string()));
2744 assert!(args.contains(&"/dev/sdb:700".to_string()));
2745
2746 assert!(args.contains(&"--device-read-bps".to_string()));
2748 assert!(args.contains(&"/dev/sda:50mb".to_string()));
2749 assert!(args.contains(&"--device-write-bps".to_string()));
2750 assert!(args.contains(&"/dev/sda:30mb".to_string()));
2751
2752 assert!(args.contains(&"--device-read-iops".to_string()));
2754 assert!(args.contains(&"/dev/sda:1000".to_string()));
2755 assert!(args.contains(&"--device-write-iops".to_string()));
2756 assert!(args.contains(&"/dev/sda:800".to_string()));
2757 }
2758
2759 #[test]
2760 fn test_run_command_realtime_cpu_networking() {
2761 let cmd = RunCommand::new("alpine:latest")
2762 .cpu_rt_period(1_000_000)
2763 .cpu_rt_runtime(950_000)
2764 .ip("172.30.100.104")
2765 .ip6("2001:db8::33")
2766 .device_cgroup_rule("c 1:3 mr")
2767 .device_cgroup_rule("a 7:* rmw");
2768
2769 let args = cmd.build_command_args();
2770
2771 assert!(args.contains(&"--cpu-rt-period".to_string()));
2773 assert!(args.contains(&"1000000".to_string()));
2774 assert!(args.contains(&"--cpu-rt-runtime".to_string()));
2775 assert!(args.contains(&"950000".to_string()));
2776
2777 assert!(args.contains(&"--ip".to_string()));
2779 assert!(args.contains(&"172.30.100.104".to_string()));
2780 assert!(args.contains(&"--ip6".to_string()));
2781 assert!(args.contains(&"2001:db8::33".to_string()));
2782
2783 assert!(args.contains(&"--device-cgroup-rule".to_string()));
2785 assert!(args.contains(&"c 1:3 mr".to_string()));
2786 assert!(args.contains(&"a 7:* rmw".to_string()));
2787 }
2788
2789 #[test]
2790 fn test_run_command_advanced_system_individual_options() {
2791 let blkio_cmd = RunCommand::new("alpine:latest").blkio_weight(100);
2793 let blkio_args = blkio_cmd.build_command_args();
2794 assert!(blkio_args.contains(&"--blkio-weight".to_string()));
2795 assert!(blkio_args.contains(&"100".to_string()));
2796
2797 let weight_device_cmd =
2798 RunCommand::new("alpine:latest").blkio_weight_device("/dev/sda:500");
2799 let weight_device_args = weight_device_cmd.build_command_args();
2800 assert!(weight_device_args.contains(&"--blkio-weight-device".to_string()));
2801 assert!(weight_device_args.contains(&"/dev/sda:500".to_string()));
2802
2803 let read_bps_cmd = RunCommand::new("alpine:latest").device_read_bps("/dev/sda:1mb");
2804 let read_bps_args = read_bps_cmd.build_command_args();
2805 assert!(read_bps_args.contains(&"--device-read-bps".to_string()));
2806 assert!(read_bps_args.contains(&"/dev/sda:1mb".to_string()));
2807
2808 let write_bps_cmd = RunCommand::new("alpine:latest").device_write_bps("/dev/sda:1mb");
2809 let write_bps_args = write_bps_cmd.build_command_args();
2810 assert!(write_bps_args.contains(&"--device-write-bps".to_string()));
2811 assert!(write_bps_args.contains(&"/dev/sda:1mb".to_string()));
2812
2813 let read_iops_cmd = RunCommand::new("alpine:latest").device_read_iops("/dev/sda:100");
2814 let read_iops_args = read_iops_cmd.build_command_args();
2815 assert!(read_iops_args.contains(&"--device-read-iops".to_string()));
2816 assert!(read_iops_args.contains(&"/dev/sda:100".to_string()));
2817
2818 let write_iops_cmd = RunCommand::new("alpine:latest").device_write_iops("/dev/sda:100");
2819 let write_iops_args = write_iops_cmd.build_command_args();
2820 assert!(write_iops_args.contains(&"--device-write-iops".to_string()));
2821 assert!(write_iops_args.contains(&"/dev/sda:100".to_string()));
2822
2823 let rt_period_cmd = RunCommand::new("alpine:latest").cpu_rt_period(100_000);
2824 let rt_period_args = rt_period_cmd.build_command_args();
2825 assert!(rt_period_args.contains(&"--cpu-rt-period".to_string()));
2826 assert!(rt_period_args.contains(&"100000".to_string()));
2827
2828 let rt_runtime_cmd = RunCommand::new("alpine:latest").cpu_rt_runtime(95_000);
2829 let rt_runtime_args = rt_runtime_cmd.build_command_args();
2830 assert!(rt_runtime_args.contains(&"--cpu-rt-runtime".to_string()));
2831 assert!(rt_runtime_args.contains(&"95000".to_string()));
2832
2833 let ip_cmd = RunCommand::new("alpine:latest").ip("192.168.1.100");
2834 let ip_args = ip_cmd.build_command_args();
2835 assert!(ip_args.contains(&"--ip".to_string()));
2836 assert!(ip_args.contains(&"192.168.1.100".to_string()));
2837
2838 let ipv6_cmd = RunCommand::new("alpine:latest").ip6("fe80::1");
2839 let ipv6_args = ipv6_cmd.build_command_args();
2840 assert!(ipv6_args.contains(&"--ip6".to_string()));
2841 assert!(ipv6_args.contains(&"fe80::1".to_string()));
2842
2843 let cgroup_rule_cmd = RunCommand::new("alpine:latest").device_cgroup_rule("c 1:1 rwm");
2844 let cgroup_rule_args = cgroup_rule_cmd.build_command_args();
2845 assert!(cgroup_rule_args.contains(&"--device-cgroup-rule".to_string()));
2846 assert!(cgroup_rule_args.contains(&"c 1:1 rwm".to_string()));
2847 }
2848
2849 #[test]
2850 #[allow(clippy::too_many_lines)]
2851 fn test_run_command_complete_100_percent_coverage() {
2852 use std::path::PathBuf;
2853
2854 let cmd = RunCommand::new("enterprise-app:latest")
2856 .name("production-enterprise")
2857 .detach()
2859 .interactive()
2860 .tty()
2861 .remove()
2862 .env("NODE_ENV", "production")
2864 .port(8080, 8080)
2865 .volume("/data", "/app/data")
2866 .workdir("/app")
2867 .entrypoint("/app/start.sh")
2868 .memory("4g")
2870 .cpus("2.0")
2871 .cpu_shares(1024)
2872 .cpu_period(100_000)
2873 .cpu_quota(50000)
2874 .cpuset_cpus("0-1")
2875 .cpuset_mems("0")
2876 .memory_swap("8g")
2877 .memory_reservation("2g")
2878 .user("app:app")
2880 .privileged()
2881 .hostname("enterprise-app")
2882 .restart("unless-stopped")
2884 .platform("linux/amd64")
2886 .runtime("runc")
2887 .isolation("default")
2888 .pull("always")
2889 .cidfile("/tmp/container.cid")
2890 .domainname("enterprise.local")
2891 .mac_address("02:42:ac:11:00:02")
2892 .log_driver("json-file")
2894 .volume_driver("local")
2895 .userns("host")
2897 .uts("host")
2898 .pid("host")
2899 .ipc("host")
2900 .cgroupns("host")
2901 .cgroup_parent("/docker")
2902 .kernel_memory("1g")
2904 .memory_swappiness(10)
2905 .oom_score_adj(-500)
2906 .pids_limit(1000)
2907 .shm_size("64m")
2908 .stop_signal("SIGTERM")
2910 .stop_timeout(30)
2911 .detach_keys("ctrl-p,ctrl-q")
2912 .no_sig_proxy()
2914 .read_only()
2915 .init()
2916 .oom_kill_disable()
2917 .no_healthcheck()
2918 .enable_content_trust()
2919 .publish_all()
2920 .quiet()
2921 .dns("8.8.8.8")
2923 .dns_option("ndots:2")
2924 .dns_search("enterprise.local")
2925 .add_host("api.enterprise.local:10.0.1.100")
2926 .cap_add("NET_ADMIN")
2927 .cap_drop("ALL")
2928 .security_opt("no-new-privileges:true")
2929 .device("/dev/null")
2930 .tmpfs("/tmp:size=100m")
2931 .expose("9090")
2932 .env_file(PathBuf::from(".env.production"))
2933 .label("app=enterprise")
2934 .label_file(PathBuf::from("labels.txt"))
2935 .network_alias("enterprise-primary")
2937 .group_add("staff")
2938 .attach("stdout")
2939 .log_opt("max-size=10m")
2940 .storage_opt("size=100G")
2941 .ulimit("nofile=65536:65536")
2942 .volumes_from("data-container")
2943 .link("db:database")
2944 .link_local_ip("169.254.1.1")
2945 .health_cmd("curl -f http://localhost:8080/health || exit 1")
2947 .health_interval("30s")
2948 .health_retries(3)
2949 .health_timeout("10s")
2950 .health_start_period("60s")
2951 .health_start_interval("5s")
2952 .mount("type=bind,source=/host/config,target=/app/config")
2953 .network("enterprise-net")
2954 .gpus("device=0")
2955 .annotation("io.kubernetes.cri-o.TTY", "true")
2956 .sysctl("net.core.somaxconn", "65535")
2957 .blkio_weight(500)
2959 .blkio_weight_device("/dev/sda:300")
2960 .device_read_bps("/dev/sda:100mb")
2961 .device_write_bps("/dev/sda:50mb")
2962 .device_read_iops("/dev/sda:1000")
2963 .device_write_iops("/dev/sda:500")
2964 .cpu_rt_period(1_000_000)
2965 .cpu_rt_runtime(950_000)
2966 .ip("10.0.1.50")
2967 .ip6("2001:db8::50")
2968 .device_cgroup_rule("c 1:1 rwm");
2969
2970 let args = cmd.build_command_args();
2971
2972 assert!(args.len() > 150); assert!(args.contains(&"--detach".to_string()));
2977 assert!(args.contains(&"--memory".to_string()));
2978 assert!(args.contains(&"--dns".to_string()));
2979 assert!(args.contains(&"--network-alias".to_string()));
2980 assert!(args.contains(&"--health-cmd".to_string()));
2981 assert!(args.contains(&"--blkio-weight".to_string()));
2982
2983 let image_pos = args
2985 .iter()
2986 .position(|x| x == "enterprise-app:latest")
2987 .unwrap();
2988 assert!(image_pos > 100); assert_eq!(args[args.len() - 1], "enterprise-app:latest");
2990
2991 println!("COMPLETE! All 96 Docker run options implemented and tested!");
2992 }
2993}