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]
315 pub fn short(&self) -> &str {
316 let end_idx = self
318 .0
319 .char_indices()
320 .nth(12)
321 .map_or(self.0.len(), |(idx, _)| idx);
322 &self.0[..end_idx]
323 }
324
325 pub async fn port_mappings(&self) -> Result<Vec<PortMappingInfo>> {
361 let result = PortCommand::new(&self.0).run().await?;
362 Ok(result.port_mappings)
363 }
364
365 pub async fn port_mapping(&self, container_port: u16) -> Result<Option<PortMappingInfo>> {
395 let result = PortCommand::new(&self.0).port(container_port).run().await?;
396 Ok(result.port_mappings.into_iter().next())
397 }
398}
399
400impl std::fmt::Display for ContainerId {
401 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402 write!(f, "{}", self.0)
403 }
404}
405
406impl RunCommand {
407 #[allow(clippy::too_many_lines)]
409 pub fn new(image: impl Into<String>) -> Self {
410 Self {
411 image: image.into(),
412 executor: CommandExecutor::new(),
413 name: None,
414 detach: false,
415 environment: EnvironmentBuilder::new(),
416 ports: PortBuilder::new(),
417 volumes: Vec::new(),
418 workdir: None,
419 entrypoint: None,
420 command: None,
421 interactive: false,
422 tty: false,
423 remove: false,
424
425 memory: None,
427 cpus: None,
428 cpu_shares: None,
429 cpu_period: None,
430 cpu_quota: None,
431 cpuset_cpus: None,
432 cpuset_mems: None,
433 memory_swap: None,
434 memory_reservation: None,
435
436 user: None,
438 privileged: false,
439 hostname: None,
440
441 restart: None,
443
444 platform: None,
446 runtime: None,
447 isolation: None,
448 pull: None,
449 cidfile: None,
450 domainname: None,
451 mac_address: None,
452
453 log_driver: None,
455 volume_driver: None,
456
457 userns: None,
459 uts: None,
460 pid: None,
461 ipc: None,
462 cgroupns: None,
463 cgroup_parent: None,
464
465 kernel_memory: None,
467 memory_swappiness: None,
468 oom_score_adj: None,
469 pids_limit: None,
470 shm_size: None,
471
472 stop_signal: None,
474 stop_timeout: None,
475 detach_keys: None,
476
477 sig_proxy: true, read_only: false,
480 init: false,
481 oom_kill_disable: false,
482 no_healthcheck: false,
483 disable_content_trust: true, publish_all: false,
485 quiet: false,
486
487 dns: Vec::new(),
490 dns_option: Vec::new(),
491 dns_search: Vec::new(),
492 add_host: Vec::new(),
493
494 cap_add: Vec::new(),
496 cap_drop: Vec::new(),
497 security_opt: Vec::new(),
498
499 device: Vec::new(),
501 tmpfs: Vec::new(),
502 expose: Vec::new(),
503
504 env_file: Vec::new(),
506 label: Vec::new(),
507 label_file: Vec::new(),
508
509 network_alias: Vec::new(),
511 group_add: Vec::new(),
512 attach: Vec::new(),
513 log_opt: Vec::new(),
514 storage_opt: Vec::new(),
515 ulimit: Vec::new(),
516 volumes_from: Vec::new(),
517 link: Vec::new(),
518 link_local_ip: Vec::new(),
519
520 health_cmd: None,
522 health_interval: None,
523 health_retries: None,
524 health_timeout: None,
525 health_start_period: None,
526 health_start_interval: None,
527 mount: Vec::new(),
528 network: Vec::new(),
529 gpus: None,
530 annotation: Vec::new(),
531 sysctl: Vec::new(),
532
533 blkio_weight: None,
535 blkio_weight_device: Vec::new(),
536 device_read_bps: Vec::new(),
537 device_write_bps: Vec::new(),
538 device_read_iops: Vec::new(),
539 device_write_iops: Vec::new(),
540 cpu_rt_period: None,
541 cpu_rt_runtime: None,
542 ip: None,
543 ip6: None,
544 device_cgroup_rule: Vec::new(),
545 }
546 }
547
548 #[must_use]
550 pub fn name(mut self, name: impl Into<String>) -> Self {
551 self.name = Some(name.into());
552 self
553 }
554
555 #[must_use]
557 pub fn detach(mut self) -> Self {
558 self.detach = true;
559 self
560 }
561
562 #[must_use]
564 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
565 self.environment = self.environment.var(key, value);
566 self
567 }
568
569 #[must_use]
571 pub fn envs(mut self, vars: std::collections::HashMap<String, String>) -> Self {
572 self.environment = self.environment.vars(vars);
573 self
574 }
575
576 #[must_use]
578 pub fn port(mut self, host_port: u16, container_port: u16) -> Self {
579 self.ports = self.ports.port(host_port, container_port);
580 self
581 }
582
583 #[must_use]
585 pub fn dynamic_port(mut self, container_port: u16) -> Self {
586 self.ports = self.ports.dynamic_port(container_port);
587 self
588 }
589
590 #[must_use]
592 pub fn port_dyn(self, container_port: u16) -> Self {
593 self.dynamic_port(container_port)
594 }
595
596 #[must_use]
598 pub fn volume(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
599 self.volumes.push(VolumeMount {
600 source: source.into(),
601 target: target.into(),
602 mount_type: MountType::Volume,
603 readonly: false,
604 });
605 self
606 }
607
608 #[must_use]
610 pub fn bind(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
611 self.volumes.push(VolumeMount {
612 source: source.into(),
613 target: target.into(),
614 mount_type: MountType::Bind,
615 readonly: false,
616 });
617 self
618 }
619
620 #[must_use]
622 pub fn volume_ro(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
623 self.volumes.push(VolumeMount {
624 source: source.into(),
625 target: target.into(),
626 mount_type: MountType::Volume,
627 readonly: true,
628 });
629 self
630 }
631
632 #[must_use]
634 pub fn workdir(mut self, workdir: impl Into<PathBuf>) -> Self {
635 self.workdir = Some(workdir.into());
636 self
637 }
638
639 #[must_use]
641 pub fn entrypoint(mut self, entrypoint: impl Into<String>) -> Self {
642 self.entrypoint = Some(entrypoint.into());
643 self
644 }
645
646 #[must_use]
648 pub fn cmd(mut self, command: Vec<String>) -> Self {
649 self.command = Some(command);
650 self
651 }
652
653 #[must_use]
655 pub fn interactive(mut self) -> Self {
656 self.interactive = true;
657 self
658 }
659
660 #[must_use]
662 pub fn tty(mut self) -> Self {
663 self.tty = true;
664 self
665 }
666
667 #[must_use]
669 pub fn remove(mut self) -> Self {
670 self.remove = true;
671 self
672 }
673
674 #[must_use]
676 pub fn rm(self) -> Self {
677 self.remove()
678 }
679
680 #[must_use]
682 pub fn it(self) -> Self {
683 self.interactive().tty()
684 }
685
686 #[must_use]
689 pub fn memory(mut self, memory: impl Into<String>) -> Self {
690 self.memory = Some(memory.into());
691 self
692 }
693
694 #[must_use]
696 pub fn cpus(mut self, cpus: impl Into<String>) -> Self {
697 self.cpus = Some(cpus.into());
698 self
699 }
700
701 #[must_use]
703 pub fn cpu_shares(mut self, shares: i64) -> Self {
704 self.cpu_shares = Some(shares);
705 self
706 }
707
708 #[must_use]
710 pub fn cpu_period(mut self, period: i64) -> Self {
711 self.cpu_period = Some(period);
712 self
713 }
714
715 #[must_use]
717 pub fn cpu_quota(mut self, quota: i64) -> Self {
718 self.cpu_quota = Some(quota);
719 self
720 }
721
722 #[must_use]
724 pub fn cpuset_cpus(mut self, cpus: impl Into<String>) -> Self {
725 self.cpuset_cpus = Some(cpus.into());
726 self
727 }
728
729 #[must_use]
731 pub fn cpuset_mems(mut self, mems: impl Into<String>) -> Self {
732 self.cpuset_mems = Some(mems.into());
733 self
734 }
735
736 #[must_use]
738 pub fn memory_swap(mut self, swap: impl Into<String>) -> Self {
739 self.memory_swap = Some(swap.into());
740 self
741 }
742
743 #[must_use]
745 pub fn memory_reservation(mut self, reservation: impl Into<String>) -> Self {
746 self.memory_reservation = Some(reservation.into());
747 self
748 }
749
750 #[must_use]
753 pub fn user(mut self, user: impl Into<String>) -> Self {
754 self.user = Some(user.into());
755 self
756 }
757
758 #[must_use]
760 pub fn privileged(mut self) -> Self {
761 self.privileged = true;
762 self
763 }
764
765 #[must_use]
767 pub fn hostname(mut self, hostname: impl Into<String>) -> Self {
768 self.hostname = Some(hostname.into());
769 self
770 }
771
772 #[must_use]
775 pub fn restart(mut self, restart: impl Into<String>) -> Self {
776 self.restart = Some(restart.into());
777 self
778 }
779
780 #[must_use]
783 pub fn platform(mut self, platform: impl Into<String>) -> Self {
784 self.platform = Some(platform.into());
785 self
786 }
787
788 #[must_use]
790 pub fn runtime(mut self, runtime: impl Into<String>) -> Self {
791 self.runtime = Some(runtime.into());
792 self
793 }
794
795 #[must_use]
797 pub fn isolation(mut self, isolation: impl Into<String>) -> Self {
798 self.isolation = Some(isolation.into());
799 self
800 }
801
802 #[must_use]
804 pub fn pull(mut self, pull: impl Into<String>) -> Self {
805 self.pull = Some(pull.into());
806 self
807 }
808
809 #[must_use]
811 pub fn cidfile(mut self, cidfile: impl Into<String>) -> Self {
812 self.cidfile = Some(cidfile.into());
813 self
814 }
815
816 #[must_use]
818 pub fn domainname(mut self, domainname: impl Into<String>) -> Self {
819 self.domainname = Some(domainname.into());
820 self
821 }
822
823 #[must_use]
825 pub fn mac_address(mut self, mac: impl Into<String>) -> Self {
826 self.mac_address = Some(mac.into());
827 self
828 }
829
830 #[must_use]
833 pub fn log_driver(mut self, driver: impl Into<String>) -> Self {
834 self.log_driver = Some(driver.into());
835 self
836 }
837
838 #[must_use]
840 pub fn volume_driver(mut self, driver: impl Into<String>) -> Self {
841 self.volume_driver = Some(driver.into());
842 self
843 }
844
845 #[must_use]
848 pub fn userns(mut self, userns: impl Into<String>) -> Self {
849 self.userns = Some(userns.into());
850 self
851 }
852
853 #[must_use]
855 pub fn uts(mut self, uts: impl Into<String>) -> Self {
856 self.uts = Some(uts.into());
857 self
858 }
859
860 #[must_use]
862 pub fn pid(mut self, pid: impl Into<String>) -> Self {
863 self.pid = Some(pid.into());
864 self
865 }
866
867 #[must_use]
869 pub fn ipc(mut self, ipc: impl Into<String>) -> Self {
870 self.ipc = Some(ipc.into());
871 self
872 }
873
874 #[must_use]
876 pub fn cgroupns(mut self, cgroupns: impl Into<String>) -> Self {
877 self.cgroupns = Some(cgroupns.into());
878 self
879 }
880
881 #[must_use]
883 pub fn cgroup_parent(mut self, parent: impl Into<String>) -> Self {
884 self.cgroup_parent = Some(parent.into());
885 self
886 }
887
888 #[must_use]
891 pub fn kernel_memory(mut self, memory: impl Into<String>) -> Self {
892 self.kernel_memory = Some(memory.into());
893 self
894 }
895
896 #[must_use]
898 pub fn memory_swappiness(mut self, swappiness: i32) -> Self {
899 self.memory_swappiness = Some(swappiness);
900 self
901 }
902
903 #[must_use]
905 pub fn oom_score_adj(mut self, score: i32) -> Self {
906 self.oom_score_adj = Some(score);
907 self
908 }
909
910 #[must_use]
912 pub fn pids_limit(mut self, limit: i64) -> Self {
913 self.pids_limit = Some(limit);
914 self
915 }
916
917 #[must_use]
919 pub fn shm_size(mut self, size: impl Into<String>) -> Self {
920 self.shm_size = Some(size.into());
921 self
922 }
923
924 #[must_use]
927 pub fn stop_signal(mut self, signal: impl Into<String>) -> Self {
928 self.stop_signal = Some(signal.into());
929 self
930 }
931
932 #[must_use]
934 pub fn stop_timeout(mut self, timeout: i32) -> Self {
935 self.stop_timeout = Some(timeout);
936 self
937 }
938
939 #[must_use]
941 pub fn detach_keys(mut self, keys: impl Into<String>) -> Self {
942 self.detach_keys = Some(keys.into());
943 self
944 }
945
946 #[must_use]
949 pub fn no_sig_proxy(mut self) -> Self {
950 self.sig_proxy = false;
951 self
952 }
953
954 #[must_use]
956 pub fn read_only(mut self) -> Self {
957 self.read_only = true;
958 self
959 }
960
961 #[must_use]
963 pub fn init(mut self) -> Self {
964 self.init = true;
965 self
966 }
967
968 #[must_use]
970 pub fn oom_kill_disable(mut self) -> Self {
971 self.oom_kill_disable = true;
972 self
973 }
974
975 #[must_use]
977 pub fn no_healthcheck(mut self) -> Self {
978 self.no_healthcheck = true;
979 self
980 }
981
982 #[must_use]
984 pub fn enable_content_trust(mut self) -> Self {
985 self.disable_content_trust = false;
986 self
987 }
988
989 #[must_use]
991 pub fn publish_all(mut self) -> Self {
992 self.publish_all = true;
993 self
994 }
995
996 #[must_use]
998 pub fn quiet(mut self) -> Self {
999 self.quiet = true;
1000 self
1001 }
1002
1003 #[must_use]
1008 pub fn dns(mut self, dns: impl Into<String>) -> Self {
1009 self.dns.push(dns.into());
1010 self
1011 }
1012
1013 #[must_use]
1015 pub fn dns_servers(mut self, servers: Vec<String>) -> Self {
1016 self.dns.extend(servers);
1017 self
1018 }
1019
1020 #[must_use]
1022 pub fn dns_option(mut self, option: impl Into<String>) -> Self {
1023 self.dns_option.push(option.into());
1024 self
1025 }
1026
1027 #[must_use]
1029 pub fn dns_search(mut self, domain: impl Into<String>) -> Self {
1030 self.dns_search.push(domain.into());
1031 self
1032 }
1033
1034 #[must_use]
1036 pub fn add_host(mut self, mapping: impl Into<String>) -> Self {
1037 self.add_host.push(mapping.into());
1038 self
1039 }
1040
1041 #[must_use]
1044 pub fn cap_add(mut self, capability: impl Into<String>) -> Self {
1045 self.cap_add.push(capability.into());
1046 self
1047 }
1048
1049 #[must_use]
1051 pub fn cap_drop(mut self, capability: impl Into<String>) -> Self {
1052 self.cap_drop.push(capability.into());
1053 self
1054 }
1055
1056 #[must_use]
1058 pub fn security_opt(mut self, option: impl Into<String>) -> Self {
1059 self.security_opt.push(option.into());
1060 self
1061 }
1062
1063 #[must_use]
1066 pub fn device(mut self, device: impl Into<String>) -> Self {
1067 self.device.push(device.into());
1068 self
1069 }
1070
1071 #[must_use]
1073 pub fn tmpfs(mut self, path: impl Into<String>) -> Self {
1074 self.tmpfs.push(path.into());
1075 self
1076 }
1077
1078 #[must_use]
1080 pub fn expose(mut self, port: impl Into<String>) -> Self {
1081 self.expose.push(port.into());
1082 self
1083 }
1084
1085 #[must_use]
1088 pub fn env_file(mut self, file: impl Into<PathBuf>) -> Self {
1089 self.env_file.push(file.into());
1090 self
1091 }
1092
1093 #[must_use]
1095 pub fn label(mut self, label: impl Into<String>) -> Self {
1096 self.label.push(label.into());
1097 self
1098 }
1099
1100 #[must_use]
1102 pub fn label_file(mut self, file: impl Into<PathBuf>) -> Self {
1103 self.label_file.push(file.into());
1104 self
1105 }
1106
1107 #[must_use]
1111 pub fn network_alias(mut self, alias: impl Into<String>) -> Self {
1112 self.network_alias.push(alias.into());
1113 self
1114 }
1115
1116 #[must_use]
1118 pub fn group_add(mut self, group: impl Into<String>) -> Self {
1119 self.group_add.push(group.into());
1120 self
1121 }
1122
1123 #[must_use]
1125 pub fn attach(mut self, stream: impl Into<String>) -> Self {
1126 self.attach.push(stream.into());
1127 self
1128 }
1129
1130 #[must_use]
1132 pub fn log_opt(mut self, option: impl Into<String>) -> Self {
1133 self.log_opt.push(option.into());
1134 self
1135 }
1136
1137 #[must_use]
1139 pub fn storage_opt(mut self, option: impl Into<String>) -> Self {
1140 self.storage_opt.push(option.into());
1141 self
1142 }
1143
1144 #[must_use]
1146 pub fn ulimit(mut self, limit: impl Into<String>) -> Self {
1147 self.ulimit.push(limit.into());
1148 self
1149 }
1150
1151 #[must_use]
1153 pub fn volumes_from(mut self, container: impl Into<String>) -> Self {
1154 self.volumes_from.push(container.into());
1155 self
1156 }
1157
1158 #[must_use]
1160 pub fn link(mut self, link: impl Into<String>) -> Self {
1161 self.link.push(link.into());
1162 self
1163 }
1164
1165 #[must_use]
1167 pub fn link_local_ip(mut self, ip: impl Into<String>) -> Self {
1168 self.link_local_ip.push(ip.into());
1169 self
1170 }
1171
1172 #[must_use]
1177 pub fn health_cmd(mut self, cmd: impl Into<String>) -> Self {
1178 self.health_cmd = Some(cmd.into());
1179 self
1180 }
1181
1182 #[must_use]
1184 pub fn health_interval(mut self, interval: impl Into<String>) -> Self {
1185 self.health_interval = Some(interval.into());
1186 self
1187 }
1188
1189 #[must_use]
1191 pub fn health_retries(mut self, retries: i32) -> Self {
1192 self.health_retries = Some(retries);
1193 self
1194 }
1195
1196 #[must_use]
1198 pub fn health_timeout(mut self, timeout: impl Into<String>) -> Self {
1199 self.health_timeout = Some(timeout.into());
1200 self
1201 }
1202
1203 #[must_use]
1205 pub fn health_start_period(mut self, period: impl Into<String>) -> Self {
1206 self.health_start_period = Some(period.into());
1207 self
1208 }
1209
1210 #[must_use]
1212 pub fn health_start_interval(mut self, interval: impl Into<String>) -> Self {
1213 self.health_start_interval = Some(interval.into());
1214 self
1215 }
1216
1217 #[must_use]
1220 pub fn mount(mut self, mount: impl Into<String>) -> Self {
1221 self.mount.push(mount.into());
1222 self
1223 }
1224
1225 #[must_use]
1227 pub fn network(mut self, network: impl Into<String>) -> Self {
1228 self.network.push(network.into());
1229 self
1230 }
1231
1232 #[must_use]
1234 pub fn gpus(mut self, gpus: impl Into<String>) -> Self {
1235 self.gpus = Some(gpus.into());
1236 self
1237 }
1238
1239 #[must_use]
1241 pub fn annotation(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1242 self.annotation
1243 .push(format!("{}={}", key.into(), value.into()));
1244 self
1245 }
1246
1247 #[must_use]
1249 pub fn sysctl(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1250 self.sysctl.push(format!("{}={}", key.into(), value.into()));
1251 self
1252 }
1253
1254 #[must_use]
1259 pub fn blkio_weight(mut self, weight: u16) -> Self {
1260 self.blkio_weight = Some(weight);
1261 self
1262 }
1263
1264 #[must_use]
1266 pub fn blkio_weight_device(mut self, device_weight: impl Into<String>) -> Self {
1267 self.blkio_weight_device.push(device_weight.into());
1268 self
1269 }
1270
1271 #[must_use]
1273 pub fn device_read_bps(mut self, device_rate: impl Into<String>) -> Self {
1274 self.device_read_bps.push(device_rate.into());
1275 self
1276 }
1277
1278 #[must_use]
1280 pub fn device_write_bps(mut self, device_rate: impl Into<String>) -> Self {
1281 self.device_write_bps.push(device_rate.into());
1282 self
1283 }
1284
1285 #[must_use]
1287 pub fn device_read_iops(mut self, device_rate: impl Into<String>) -> Self {
1288 self.device_read_iops.push(device_rate.into());
1289 self
1290 }
1291
1292 #[must_use]
1294 pub fn device_write_iops(mut self, device_rate: impl Into<String>) -> Self {
1295 self.device_write_iops.push(device_rate.into());
1296 self
1297 }
1298
1299 #[must_use]
1302 pub fn cpu_rt_period(mut self, period: i64) -> Self {
1303 self.cpu_rt_period = Some(period);
1304 self
1305 }
1306
1307 #[must_use]
1309 pub fn cpu_rt_runtime(mut self, runtime: i64) -> Self {
1310 self.cpu_rt_runtime = Some(runtime);
1311 self
1312 }
1313
1314 #[must_use]
1317 pub fn ip(mut self, ip: impl Into<String>) -> Self {
1318 self.ip = Some(ip.into());
1319 self
1320 }
1321
1322 #[must_use]
1324 pub fn ip6(mut self, ip6: impl Into<String>) -> Self {
1325 self.ip6 = Some(ip6.into());
1326 self
1327 }
1328
1329 #[must_use]
1331 pub fn device_cgroup_rule(mut self, rule: impl Into<String>) -> Self {
1332 self.device_cgroup_rule.push(rule.into());
1333 self
1334 }
1335}
1336
1337#[async_trait]
1338impl DockerCommand for RunCommand {
1339 type Output = ContainerId;
1340
1341 fn get_executor(&self) -> &CommandExecutor {
1342 &self.executor
1343 }
1344
1345 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
1346 &mut self.executor
1347 }
1348
1349 #[allow(clippy::too_many_lines)]
1350 fn build_command_args(&self) -> Vec<String> {
1351 let mut args = vec!["run".to_string()];
1352
1353 if self.detach {
1355 args.push("--detach".to_string());
1356 }
1357 if self.interactive {
1358 args.push("--interactive".to_string());
1359 }
1360 if self.tty {
1361 args.push("--tty".to_string());
1362 }
1363 if self.remove {
1364 args.push("--rm".to_string());
1365 }
1366
1367 if let Some(ref name) = self.name {
1369 args.push("--name".to_string());
1370 args.push(name.clone());
1371 }
1372
1373 if let Some(ref workdir) = self.workdir {
1375 args.push("--workdir".to_string());
1376 args.push(workdir.to_string_lossy().to_string());
1377 }
1378
1379 if let Some(ref entrypoint) = self.entrypoint {
1381 args.push("--entrypoint".to_string());
1382 args.push(entrypoint.clone());
1383 }
1384
1385 args.extend(self.environment.build_args());
1387
1388 args.extend(self.ports.build_args());
1390
1391 for volume in &self.volumes {
1393 args.push("--volume".to_string());
1394 args.push(volume.to_string());
1395 }
1396
1397 if let Some(ref memory) = self.memory {
1399 args.push("--memory".to_string());
1400 args.push(memory.clone());
1401 }
1402 if let Some(ref cpus) = self.cpus {
1403 args.push("--cpus".to_string());
1404 args.push(cpus.clone());
1405 }
1406 if let Some(cpu_shares) = self.cpu_shares {
1407 args.push("--cpu-shares".to_string());
1408 args.push(cpu_shares.to_string());
1409 }
1410 if let Some(cpu_period) = self.cpu_period {
1411 args.push("--cpu-period".to_string());
1412 args.push(cpu_period.to_string());
1413 }
1414 if let Some(cpu_quota) = self.cpu_quota {
1415 args.push("--cpu-quota".to_string());
1416 args.push(cpu_quota.to_string());
1417 }
1418 if let Some(ref cpuset_cpus) = self.cpuset_cpus {
1419 args.push("--cpuset-cpus".to_string());
1420 args.push(cpuset_cpus.clone());
1421 }
1422 if let Some(ref cpuset_mems) = self.cpuset_mems {
1423 args.push("--cpuset-mems".to_string());
1424 args.push(cpuset_mems.clone());
1425 }
1426 if let Some(ref memory_swap) = self.memory_swap {
1427 args.push("--memory-swap".to_string());
1428 args.push(memory_swap.clone());
1429 }
1430 if let Some(ref memory_reservation) = self.memory_reservation {
1431 args.push("--memory-reservation".to_string());
1432 args.push(memory_reservation.clone());
1433 }
1434
1435 if let Some(ref user) = self.user {
1437 args.push("--user".to_string());
1438 args.push(user.clone());
1439 }
1440 if self.privileged {
1441 args.push("--privileged".to_string());
1442 }
1443 if let Some(ref hostname) = self.hostname {
1444 args.push("--hostname".to_string());
1445 args.push(hostname.clone());
1446 }
1447
1448 if let Some(ref restart) = self.restart {
1450 args.push("--restart".to_string());
1451 args.push(restart.clone());
1452 }
1453
1454 if let Some(ref platform) = self.platform {
1456 args.push("--platform".to_string());
1457 args.push(platform.clone());
1458 }
1459 if let Some(ref runtime) = self.runtime {
1460 args.push("--runtime".to_string());
1461 args.push(runtime.clone());
1462 }
1463 if let Some(ref isolation) = self.isolation {
1464 args.push("--isolation".to_string());
1465 args.push(isolation.clone());
1466 }
1467 if let Some(ref pull) = self.pull {
1468 args.push("--pull".to_string());
1469 args.push(pull.clone());
1470 }
1471 if let Some(ref cidfile) = self.cidfile {
1472 args.push("--cidfile".to_string());
1473 args.push(cidfile.clone());
1474 }
1475 if let Some(ref domainname) = self.domainname {
1476 args.push("--domainname".to_string());
1477 args.push(domainname.clone());
1478 }
1479 if let Some(ref mac_address) = self.mac_address {
1480 args.push("--mac-address".to_string());
1481 args.push(mac_address.clone());
1482 }
1483
1484 if let Some(ref log_driver) = self.log_driver {
1486 args.push("--log-driver".to_string());
1487 args.push(log_driver.clone());
1488 }
1489 if let Some(ref volume_driver) = self.volume_driver {
1490 args.push("--volume-driver".to_string());
1491 args.push(volume_driver.clone());
1492 }
1493
1494 if let Some(ref userns) = self.userns {
1496 args.push("--userns".to_string());
1497 args.push(userns.clone());
1498 }
1499 if let Some(ref uts) = self.uts {
1500 args.push("--uts".to_string());
1501 args.push(uts.clone());
1502 }
1503 if let Some(ref pid) = self.pid {
1504 args.push("--pid".to_string());
1505 args.push(pid.clone());
1506 }
1507 if let Some(ref ipc) = self.ipc {
1508 args.push("--ipc".to_string());
1509 args.push(ipc.clone());
1510 }
1511 if let Some(ref cgroupns) = self.cgroupns {
1512 args.push("--cgroupns".to_string());
1513 args.push(cgroupns.clone());
1514 }
1515 if let Some(ref cgroup_parent) = self.cgroup_parent {
1516 args.push("--cgroup-parent".to_string());
1517 args.push(cgroup_parent.clone());
1518 }
1519
1520 if let Some(ref kernel_memory) = self.kernel_memory {
1522 args.push("--kernel-memory".to_string());
1523 args.push(kernel_memory.clone());
1524 }
1525 if let Some(memory_swappiness) = self.memory_swappiness {
1526 args.push("--memory-swappiness".to_string());
1527 args.push(memory_swappiness.to_string());
1528 }
1529 if let Some(oom_score_adj) = self.oom_score_adj {
1530 args.push("--oom-score-adj".to_string());
1531 args.push(oom_score_adj.to_string());
1532 }
1533 if let Some(pids_limit) = self.pids_limit {
1534 args.push("--pids-limit".to_string());
1535 args.push(pids_limit.to_string());
1536 }
1537 if let Some(ref shm_size) = self.shm_size {
1538 args.push("--shm-size".to_string());
1539 args.push(shm_size.clone());
1540 }
1541
1542 if let Some(ref stop_signal) = self.stop_signal {
1544 args.push("--stop-signal".to_string());
1545 args.push(stop_signal.clone());
1546 }
1547 if let Some(stop_timeout) = self.stop_timeout {
1548 args.push("--stop-timeout".to_string());
1549 args.push(stop_timeout.to_string());
1550 }
1551 if let Some(ref detach_keys) = self.detach_keys {
1552 args.push("--detach-keys".to_string());
1553 args.push(detach_keys.clone());
1554 }
1555
1556 if !self.sig_proxy {
1558 args.push("--sig-proxy=false".to_string());
1559 }
1560 if self.read_only {
1561 args.push("--read-only".to_string());
1562 }
1563 if self.init {
1564 args.push("--init".to_string());
1565 }
1566 if self.oom_kill_disable {
1567 args.push("--oom-kill-disable".to_string());
1568 }
1569 if self.no_healthcheck {
1570 args.push("--no-healthcheck".to_string());
1571 }
1572 if !self.disable_content_trust {
1573 args.push("--disable-content-trust=false".to_string());
1574 }
1575 if self.publish_all {
1576 args.push("--publish-all".to_string());
1577 }
1578 if self.quiet {
1579 args.push("--quiet".to_string());
1580 }
1581
1582 for dns in &self.dns {
1585 args.push("--dns".to_string());
1586 args.push(dns.clone());
1587 }
1588 for dns_option in &self.dns_option {
1589 args.push("--dns-option".to_string());
1590 args.push(dns_option.clone());
1591 }
1592 for dns_search in &self.dns_search {
1593 args.push("--dns-search".to_string());
1594 args.push(dns_search.clone());
1595 }
1596 for add_host in &self.add_host {
1597 args.push("--add-host".to_string());
1598 args.push(add_host.clone());
1599 }
1600
1601 for cap_add in &self.cap_add {
1603 args.push("--cap-add".to_string());
1604 args.push(cap_add.clone());
1605 }
1606 for cap_drop in &self.cap_drop {
1607 args.push("--cap-drop".to_string());
1608 args.push(cap_drop.clone());
1609 }
1610 for security_opt in &self.security_opt {
1611 args.push("--security-opt".to_string());
1612 args.push(security_opt.clone());
1613 }
1614
1615 for device in &self.device {
1617 args.push("--device".to_string());
1618 args.push(device.clone());
1619 }
1620 for tmpfs in &self.tmpfs {
1621 args.push("--tmpfs".to_string());
1622 args.push(tmpfs.clone());
1623 }
1624 for expose in &self.expose {
1625 args.push("--expose".to_string());
1626 args.push(expose.clone());
1627 }
1628
1629 for env_file in &self.env_file {
1631 args.push("--env-file".to_string());
1632 args.push(env_file.to_string_lossy().to_string());
1633 }
1634 for label in &self.label {
1635 args.push("--label".to_string());
1636 args.push(label.clone());
1637 }
1638 for label_file in &self.label_file {
1639 args.push("--label-file".to_string());
1640 args.push(label_file.to_string_lossy().to_string());
1641 }
1642
1643 for network_alias in &self.network_alias {
1645 args.push("--network-alias".to_string());
1646 args.push(network_alias.clone());
1647 }
1648 for group_add in &self.group_add {
1649 args.push("--group-add".to_string());
1650 args.push(group_add.clone());
1651 }
1652 for attach in &self.attach {
1653 args.push("--attach".to_string());
1654 args.push(attach.clone());
1655 }
1656 for log_opt in &self.log_opt {
1657 args.push("--log-opt".to_string());
1658 args.push(log_opt.clone());
1659 }
1660 for storage_opt in &self.storage_opt {
1661 args.push("--storage-opt".to_string());
1662 args.push(storage_opt.clone());
1663 }
1664 for ulimit in &self.ulimit {
1665 args.push("--ulimit".to_string());
1666 args.push(ulimit.clone());
1667 }
1668 for volumes_from in &self.volumes_from {
1669 args.push("--volumes-from".to_string());
1670 args.push(volumes_from.clone());
1671 }
1672 for link in &self.link {
1673 args.push("--link".to_string());
1674 args.push(link.clone());
1675 }
1676 for link_local_ip in &self.link_local_ip {
1677 args.push("--link-local-ip".to_string());
1678 args.push(link_local_ip.clone());
1679 }
1680
1681 if let Some(ref health_cmd) = self.health_cmd {
1684 args.push("--health-cmd".to_string());
1685 args.push(health_cmd.clone());
1686 }
1687 if let Some(ref health_interval) = self.health_interval {
1688 args.push("--health-interval".to_string());
1689 args.push(health_interval.clone());
1690 }
1691 if let Some(health_retries) = self.health_retries {
1692 args.push("--health-retries".to_string());
1693 args.push(health_retries.to_string());
1694 }
1695 if let Some(ref health_timeout) = self.health_timeout {
1696 args.push("--health-timeout".to_string());
1697 args.push(health_timeout.clone());
1698 }
1699 if let Some(ref health_start_period) = self.health_start_period {
1700 args.push("--health-start-period".to_string());
1701 args.push(health_start_period.clone());
1702 }
1703 if let Some(ref health_start_interval) = self.health_start_interval {
1704 args.push("--health-start-interval".to_string());
1705 args.push(health_start_interval.clone());
1706 }
1707
1708 for mount in &self.mount {
1710 args.push("--mount".to_string());
1711 args.push(mount.clone());
1712 }
1713 for network in &self.network {
1714 args.push("--network".to_string());
1715 args.push(network.clone());
1716 }
1717 if let Some(ref gpus) = self.gpus {
1718 args.push("--gpus".to_string());
1719 args.push(gpus.clone());
1720 }
1721
1722 for annotation in &self.annotation {
1724 args.push("--annotation".to_string());
1725 args.push(annotation.clone());
1726 }
1727 for sysctl in &self.sysctl {
1728 args.push("--sysctl".to_string());
1729 args.push(sysctl.clone());
1730 }
1731
1732 if let Some(blkio_weight) = self.blkio_weight {
1735 args.push("--blkio-weight".to_string());
1736 args.push(blkio_weight.to_string());
1737 }
1738 for blkio_weight_device in &self.blkio_weight_device {
1739 args.push("--blkio-weight-device".to_string());
1740 args.push(blkio_weight_device.clone());
1741 }
1742 for device_read_bps in &self.device_read_bps {
1743 args.push("--device-read-bps".to_string());
1744 args.push(device_read_bps.clone());
1745 }
1746 for device_write_bps in &self.device_write_bps {
1747 args.push("--device-write-bps".to_string());
1748 args.push(device_write_bps.clone());
1749 }
1750 for device_read_iops in &self.device_read_iops {
1751 args.push("--device-read-iops".to_string());
1752 args.push(device_read_iops.clone());
1753 }
1754 for device_write_iops in &self.device_write_iops {
1755 args.push("--device-write-iops".to_string());
1756 args.push(device_write_iops.clone());
1757 }
1758
1759 if let Some(cpu_rt_period) = self.cpu_rt_period {
1761 args.push("--cpu-rt-period".to_string());
1762 args.push(cpu_rt_period.to_string());
1763 }
1764 if let Some(cpu_rt_runtime) = self.cpu_rt_runtime {
1765 args.push("--cpu-rt-runtime".to_string());
1766 args.push(cpu_rt_runtime.to_string());
1767 }
1768
1769 if let Some(ref ip) = self.ip {
1771 args.push("--ip".to_string());
1772 args.push(ip.clone());
1773 }
1774 if let Some(ref ip6) = self.ip6 {
1775 args.push("--ip6".to_string());
1776 args.push(ip6.clone());
1777 }
1778
1779 for device_cgroup_rule in &self.device_cgroup_rule {
1781 args.push("--device-cgroup-rule".to_string());
1782 args.push(device_cgroup_rule.clone());
1783 }
1784
1785 args.push(self.image.clone());
1787
1788 if let Some(ref command) = self.command {
1790 args.extend(command.clone());
1791 }
1792
1793 args.extend(self.executor.raw_args.clone());
1795
1796 args
1797 }
1798
1799 async fn execute(&self) -> Result<Self::Output> {
1800 let args = self.build_command_args();
1801 let output = self.execute_command(args).await?;
1802
1803 let container_id = output.stdout.trim().to_string();
1805 if container_id.is_empty() {
1806 return Err(Error::parse_error(
1807 "No container ID returned from docker run",
1808 ));
1809 }
1810
1811 Ok(ContainerId(container_id))
1812 }
1813}
1814
1815#[async_trait]
1817impl StreamableCommand for RunCommand {
1818 async fn stream<F>(&self, handler: F) -> Result<StreamResult>
1819 where
1820 F: FnMut(OutputLine) + Send + 'static,
1821 {
1822 if self.detach {
1824 return Err(Error::custom(
1825 "Cannot stream output for detached containers",
1826 ));
1827 }
1828
1829 let mut cmd = TokioCommand::new("docker");
1830 cmd.arg("run");
1831
1832 for arg in self.build_command_args() {
1833 cmd.arg(arg);
1834 }
1835
1836 crate::stream::stream_command(cmd, handler).await
1837 }
1838
1839 async fn stream_channel(&self) -> Result<(mpsc::Receiver<OutputLine>, StreamResult)> {
1840 if self.detach {
1842 return Err(Error::custom(
1843 "Cannot stream output for detached containers",
1844 ));
1845 }
1846
1847 let mut cmd = TokioCommand::new("docker");
1848 cmd.arg("run");
1849
1850 for arg in self.build_command_args() {
1851 cmd.arg(arg);
1852 }
1853
1854 crate::stream::stream_command_channel(cmd).await
1855 }
1856}
1857
1858impl RunCommand {
1859 pub async fn stream<F>(&self, handler: F) -> Result<StreamResult>
1882 where
1883 F: FnMut(OutputLine) + Send + 'static,
1884 {
1885 <Self as StreamableCommand>::stream(self, handler).await
1886 }
1887}
1888
1889#[cfg(test)]
1890mod tests {
1891 use super::*;
1892
1893 #[test]
1894 fn test_run_command_builder() {
1895 let cmd = RunCommand::new("nginx:latest")
1896 .name("test-nginx")
1897 .detach()
1898 .env("ENV_VAR", "value")
1899 .port(8080, 80)
1900 .volume("data", "/var/data")
1901 .workdir("/app")
1902 .remove();
1903
1904 let args = cmd.build_command_args();
1905
1906 assert!(args.contains(&"--detach".to_string()));
1907 assert!(args.contains(&"--name".to_string()));
1908 assert!(args.contains(&"test-nginx".to_string()));
1909 assert!(args.contains(&"--env".to_string()));
1910 assert!(args.contains(&"ENV_VAR=value".to_string()));
1911 assert!(args.contains(&"--publish".to_string()));
1912 assert!(args.contains(&"8080:80".to_string()));
1913 assert!(args.contains(&"--volume".to_string()));
1914 assert!(args.contains(&"data:/var/data".to_string()));
1915 assert!(args.contains(&"--workdir".to_string()));
1916 assert!(args.contains(&"/app".to_string()));
1917 assert!(args.contains(&"--rm".to_string()));
1918 assert!(args.contains(&"nginx:latest".to_string()));
1919 }
1920
1921 #[test]
1922 fn test_run_command_with_cmd() {
1923 let cmd =
1924 RunCommand::new("alpine:latest").cmd(vec!["echo".to_string(), "hello".to_string()]);
1925
1926 let args = cmd.build_command_args();
1927 assert!(args.contains(&"alpine:latest".to_string()));
1928 assert!(args.contains(&"echo".to_string()));
1929 assert!(args.contains(&"hello".to_string()));
1930 }
1931
1932 #[test]
1933 fn test_run_command_extensibility() {
1934 let mut cmd = RunCommand::new("test:latest");
1935 cmd.flag("privileged")
1936 .option("memory", "1g")
1937 .arg("--custom-option");
1938
1939 }
1942
1943 #[test]
1944 fn test_volume_mount_display() {
1945 let volume = VolumeMount {
1946 source: "data".to_string(),
1947 target: "/var/data".to_string(),
1948 mount_type: MountType::Volume,
1949 readonly: false,
1950 };
1951 assert_eq!(volume.to_string(), "data:/var/data");
1952
1953 let readonly_volume = VolumeMount {
1954 source: "/host/path".to_string(),
1955 target: "/container/path".to_string(),
1956 mount_type: MountType::Bind,
1957 readonly: true,
1958 };
1959 assert_eq!(readonly_volume.to_string(), "/host/path:/container/path:ro");
1960 }
1961
1962 #[test]
1963 fn test_run_command_resource_limits() {
1964 let cmd = RunCommand::new("alpine:latest")
1965 .memory("1g")
1966 .cpus("2.0")
1967 .cpu_shares(1024)
1968 .cpu_period(100_000)
1969 .cpu_quota(50_000)
1970 .cpuset_cpus("0-3")
1971 .cpuset_mems("0,1")
1972 .memory_swap("2g")
1973 .memory_reservation("500m");
1974
1975 let args = cmd.build_command_args();
1976
1977 assert!(args.contains(&"--memory".to_string()));
1978 assert!(args.contains(&"1g".to_string()));
1979 assert!(args.contains(&"--cpus".to_string()));
1980 assert!(args.contains(&"2.0".to_string()));
1981 assert!(args.contains(&"--cpu-shares".to_string()));
1982 assert!(args.contains(&"1024".to_string()));
1983 assert!(args.contains(&"--cpu-period".to_string()));
1984 assert!(args.contains(&"100000".to_string()));
1985 assert!(args.contains(&"--cpu-quota".to_string()));
1986 assert!(args.contains(&"50000".to_string()));
1987 assert!(args.contains(&"--cpuset-cpus".to_string()));
1988 assert!(args.contains(&"0-3".to_string()));
1989 assert!(args.contains(&"--cpuset-mems".to_string()));
1990 assert!(args.contains(&"0,1".to_string()));
1991 assert!(args.contains(&"--memory-swap".to_string()));
1992 assert!(args.contains(&"2g".to_string()));
1993 assert!(args.contains(&"--memory-reservation".to_string()));
1994 assert!(args.contains(&"500m".to_string()));
1995 }
1996
1997 #[test]
1998 fn test_run_command_security_and_user() {
1999 let cmd = RunCommand::new("alpine:latest")
2000 .user("1000:1000")
2001 .privileged()
2002 .hostname("test-host");
2003
2004 let args = cmd.build_command_args();
2005
2006 assert!(args.contains(&"--user".to_string()));
2007 assert!(args.contains(&"1000:1000".to_string()));
2008 assert!(args.contains(&"--privileged".to_string()));
2009 assert!(args.contains(&"--hostname".to_string()));
2010 assert!(args.contains(&"test-host".to_string()));
2011 }
2012
2013 #[test]
2014 fn test_run_command_lifecycle_management() {
2015 let cmd = RunCommand::new("alpine:latest").restart("always");
2016
2017 let args = cmd.build_command_args();
2018
2019 assert!(args.contains(&"--restart".to_string()));
2020 assert!(args.contains(&"always".to_string()));
2021 }
2022
2023 #[test]
2024 fn test_run_command_system_integration() {
2025 let cmd = RunCommand::new("alpine:latest")
2026 .platform("linux/amd64")
2027 .runtime("runc")
2028 .isolation("default")
2029 .pull("always")
2030 .cidfile("/tmp/container.cid")
2031 .domainname("example.com")
2032 .mac_address("92:d0:c6:0a:29:33");
2033
2034 let args = cmd.build_command_args();
2035
2036 assert!(args.contains(&"--platform".to_string()));
2037 assert!(args.contains(&"linux/amd64".to_string()));
2038 assert!(args.contains(&"--runtime".to_string()));
2039 assert!(args.contains(&"runc".to_string()));
2040 assert!(args.contains(&"--isolation".to_string()));
2041 assert!(args.contains(&"default".to_string()));
2042 assert!(args.contains(&"--pull".to_string()));
2043 assert!(args.contains(&"always".to_string()));
2044 assert!(args.contains(&"--cidfile".to_string()));
2045 assert!(args.contains(&"/tmp/container.cid".to_string()));
2046 assert!(args.contains(&"--domainname".to_string()));
2047 assert!(args.contains(&"example.com".to_string()));
2048 assert!(args.contains(&"--mac-address".to_string()));
2049 assert!(args.contains(&"92:d0:c6:0a:29:33".to_string()));
2050 }
2051
2052 #[test]
2053 fn test_run_command_logging_and_drivers() {
2054 let cmd = RunCommand::new("alpine:latest")
2055 .log_driver("json-file")
2056 .volume_driver("local");
2057
2058 let args = cmd.build_command_args();
2059
2060 assert!(args.contains(&"--log-driver".to_string()));
2061 assert!(args.contains(&"json-file".to_string()));
2062 assert!(args.contains(&"--volume-driver".to_string()));
2063 assert!(args.contains(&"local".to_string()));
2064 }
2065
2066 #[test]
2067 fn test_run_command_namespaces() {
2068 let cmd = RunCommand::new("alpine:latest")
2069 .userns("host")
2070 .uts("host")
2071 .pid("host")
2072 .ipc("host")
2073 .cgroupns("private")
2074 .cgroup_parent("/docker");
2075
2076 let args = cmd.build_command_args();
2077
2078 assert!(args.contains(&"--userns".to_string()));
2079 assert!(args.contains(&"host".to_string()));
2080 assert!(args.contains(&"--uts".to_string()));
2081 assert!(args.contains(&"--pid".to_string()));
2082 assert!(args.contains(&"--ipc".to_string()));
2083 assert!(args.contains(&"--cgroupns".to_string()));
2084 assert!(args.contains(&"private".to_string()));
2085 assert!(args.contains(&"--cgroup-parent".to_string()));
2086 assert!(args.contains(&"/docker".to_string()));
2087 }
2088
2089 #[test]
2090 fn test_run_command_advanced_memory_performance() {
2091 let cmd = RunCommand::new("alpine:latest")
2092 .kernel_memory("100m")
2093 .memory_swappiness(60)
2094 .oom_score_adj(-500)
2095 .pids_limit(100)
2096 .shm_size("64m");
2097
2098 let args = cmd.build_command_args();
2099
2100 assert!(args.contains(&"--kernel-memory".to_string()));
2101 assert!(args.contains(&"100m".to_string()));
2102 assert!(args.contains(&"--memory-swappiness".to_string()));
2103 assert!(args.contains(&"60".to_string()));
2104 assert!(args.contains(&"--oom-score-adj".to_string()));
2105 assert!(args.contains(&"-500".to_string()));
2106 assert!(args.contains(&"--pids-limit".to_string()));
2107 assert!(args.contains(&"100".to_string()));
2108 assert!(args.contains(&"--shm-size".to_string()));
2109 assert!(args.contains(&"64m".to_string()));
2110 }
2111
2112 #[test]
2113 fn test_run_command_process_control() {
2114 let cmd = RunCommand::new("alpine:latest")
2115 .stop_signal("SIGTERM")
2116 .stop_timeout(10)
2117 .detach_keys("ctrl-p,ctrl-q");
2118
2119 let args = cmd.build_command_args();
2120
2121 assert!(args.contains(&"--stop-signal".to_string()));
2122 assert!(args.contains(&"SIGTERM".to_string()));
2123 assert!(args.contains(&"--stop-timeout".to_string()));
2124 assert!(args.contains(&"10".to_string()));
2125 assert!(args.contains(&"--detach-keys".to_string()));
2126 assert!(args.contains(&"ctrl-p,ctrl-q".to_string()));
2127 }
2128
2129 #[test]
2130 fn test_run_command_simple_flags() {
2131 let cmd = RunCommand::new("alpine:latest")
2132 .no_sig_proxy()
2133 .read_only()
2134 .init()
2135 .oom_kill_disable()
2136 .no_healthcheck()
2137 .enable_content_trust()
2138 .publish_all()
2139 .quiet();
2140
2141 let args = cmd.build_command_args();
2142
2143 assert!(args.contains(&"--sig-proxy=false".to_string()));
2144 assert!(args.contains(&"--read-only".to_string()));
2145 assert!(args.contains(&"--init".to_string()));
2146 assert!(args.contains(&"--oom-kill-disable".to_string()));
2147 assert!(args.contains(&"--no-healthcheck".to_string()));
2148 assert!(args.contains(&"--disable-content-trust=false".to_string()));
2149 assert!(args.contains(&"--publish-all".to_string()));
2150 assert!(args.contains(&"--quiet".to_string()));
2151 }
2152
2153 #[test]
2154 fn test_run_command_comprehensive_builder() {
2155 let cmd = RunCommand::new("nginx:latest")
2156 .name("production-nginx")
2157 .detach()
2158 .memory("2g")
2159 .cpus("4.0")
2160 .user("nginx:nginx")
2161 .privileged()
2162 .restart("unless-stopped")
2163 .hostname("web-server")
2164 .platform("linux/amd64")
2165 .env("NGINX_PORT", "8080")
2166 .port(80, 8080)
2167 .volume("nginx-data", "/var/lib/nginx")
2168 .workdir("/usr/share/nginx/html")
2169 .read_only()
2170 .init()
2171 .remove();
2172
2173 let args = cmd.build_command_args();
2174
2175 assert!(args.contains(&"--name".to_string()));
2177 assert!(args.contains(&"production-nginx".to_string()));
2178 assert!(args.contains(&"--detach".to_string()));
2179 assert!(args.contains(&"--memory".to_string()));
2180 assert!(args.contains(&"2g".to_string()));
2181 assert!(args.contains(&"--cpus".to_string()));
2182 assert!(args.contains(&"4.0".to_string()));
2183 assert!(args.contains(&"--user".to_string()));
2184 assert!(args.contains(&"nginx:nginx".to_string()));
2185 assert!(args.contains(&"--privileged".to_string()));
2186 assert!(args.contains(&"--restart".to_string()));
2187 assert!(args.contains(&"unless-stopped".to_string()));
2188 assert!(args.contains(&"--hostname".to_string()));
2189 assert!(args.contains(&"web-server".to_string()));
2190 assert!(args.contains(&"--platform".to_string()));
2191 assert!(args.contains(&"linux/amd64".to_string()));
2192 assert!(args.contains(&"--read-only".to_string()));
2193 assert!(args.contains(&"--init".to_string()));
2194 assert!(args.contains(&"--rm".to_string()));
2195 assert!(args.contains(&"nginx:latest".to_string()));
2196
2197 let image_pos = args.iter().position(|x| x == "nginx:latest").unwrap();
2199 assert!(image_pos > 10); }
2201
2202 #[test]
2203 fn test_run_command_default_flag_values() {
2204 let cmd = RunCommand::new("alpine:latest");
2205 let args = cmd.build_command_args();
2206
2207 assert!(!args.contains(&"--sig-proxy=false".to_string()));
2209 assert!(!args.contains(&"--disable-content-trust=false".to_string()));
2210 assert!(!args.contains(&"--read-only".to_string()));
2211 assert!(!args.contains(&"--privileged".to_string()));
2212 assert!(!args.contains(&"--init".to_string()));
2213 }
2214
2215 #[test]
2216 fn test_container_id() {
2217 let id = ContainerId("abcdef123456789".to_string());
2218 assert_eq!(id.as_str(), "abcdef123456789");
2219 assert_eq!(id.short(), "abcdef123456");
2220 assert_eq!(id.to_string(), "abcdef123456789");
2221
2222 let short_id = ContainerId("abc".to_string());
2223 assert_eq!(short_id.short(), "abc");
2224 }
2225
2226 #[test]
2227 fn test_it_convenience_method() {
2228 let cmd = RunCommand::new("alpine:latest").it();
2229 let args = cmd.build_command_args();
2230 assert!(args.contains(&"--interactive".to_string()));
2231 assert!(args.contains(&"--tty".to_string()));
2232 }
2233
2234 #[test]
2235 fn test_run_command_dns_network_options() {
2236 let cmd = RunCommand::new("alpine:latest")
2237 .dns("8.8.8.8")
2238 .dns("8.8.4.4")
2239 .dns_servers(vec!["1.1.1.1".to_string(), "1.0.0.1".to_string()])
2240 .dns_option("ndots:2")
2241 .dns_option("timeout:1")
2242 .dns_search("example.com")
2243 .dns_search("test.local")
2244 .add_host("api.example.com:127.0.0.1")
2245 .add_host("db.example.com:192.168.1.100");
2246
2247 let args = cmd.build_command_args();
2248
2249 assert!(args.contains(&"--dns".to_string()));
2251 assert!(args.contains(&"8.8.8.8".to_string()));
2252 assert!(args.contains(&"8.8.4.4".to_string()));
2253 assert!(args.contains(&"1.1.1.1".to_string()));
2254 assert!(args.contains(&"1.0.0.1".to_string()));
2255
2256 assert!(args.contains(&"--dns-option".to_string()));
2258 assert!(args.contains(&"ndots:2".to_string()));
2259 assert!(args.contains(&"timeout:1".to_string()));
2260
2261 assert!(args.contains(&"--dns-search".to_string()));
2263 assert!(args.contains(&"example.com".to_string()));
2264 assert!(args.contains(&"test.local".to_string()));
2265
2266 assert!(args.contains(&"--add-host".to_string()));
2268 assert!(args.contains(&"api.example.com:127.0.0.1".to_string()));
2269 assert!(args.contains(&"db.example.com:192.168.1.100".to_string()));
2270 }
2271
2272 #[test]
2273 fn test_run_command_security_capabilities() {
2274 let cmd = RunCommand::new("alpine:latest")
2275 .cap_add("NET_ADMIN")
2276 .cap_add("SYS_TIME")
2277 .cap_drop("CHOWN")
2278 .cap_drop("DAC_OVERRIDE")
2279 .security_opt("no-new-privileges:true")
2280 .security_opt("seccomp=unconfined");
2281
2282 let args = cmd.build_command_args();
2283
2284 assert!(args.contains(&"--cap-add".to_string()));
2286 assert!(args.contains(&"NET_ADMIN".to_string()));
2287 assert!(args.contains(&"SYS_TIME".to_string()));
2288
2289 assert!(args.contains(&"--cap-drop".to_string()));
2291 assert!(args.contains(&"CHOWN".to_string()));
2292 assert!(args.contains(&"DAC_OVERRIDE".to_string()));
2293
2294 assert!(args.contains(&"--security-opt".to_string()));
2296 assert!(args.contains(&"no-new-privileges:true".to_string()));
2297 assert!(args.contains(&"seccomp=unconfined".to_string()));
2298 }
2299
2300 #[test]
2301 fn test_run_command_device_filesystem() {
2302 let cmd = RunCommand::new("alpine:latest")
2303 .device("/dev/sda:/dev/xvda:rwm")
2304 .device("/dev/zero")
2305 .tmpfs("/tmp:rw,size=100m")
2306 .tmpfs("/var/tmp:ro")
2307 .expose("80")
2308 .expose("443")
2309 .expose("8080/tcp");
2310
2311 let args = cmd.build_command_args();
2312
2313 assert!(args.contains(&"--device".to_string()));
2315 assert!(args.contains(&"/dev/sda:/dev/xvda:rwm".to_string()));
2316 assert!(args.contains(&"/dev/zero".to_string()));
2317
2318 assert!(args.contains(&"--tmpfs".to_string()));
2320 assert!(args.contains(&"/tmp:rw,size=100m".to_string()));
2321 assert!(args.contains(&"/var/tmp:ro".to_string()));
2322
2323 assert!(args.contains(&"--expose".to_string()));
2325 assert!(args.contains(&"80".to_string()));
2326 assert!(args.contains(&"443".to_string()));
2327 assert!(args.contains(&"8080/tcp".to_string()));
2328 }
2329
2330 #[test]
2331 fn test_run_command_environment_labels() {
2332 use std::path::PathBuf;
2333
2334 let cmd = RunCommand::new("alpine:latest")
2335 .env_file(PathBuf::from("/etc/environment"))
2336 .env_file(PathBuf::from("./app.env"))
2337 .label("version=1.0.0")
2338 .label("maintainer=team@example.com")
2339 .label("app=myapp")
2340 .label_file(PathBuf::from("/etc/labels"))
2341 .label_file(PathBuf::from("./metadata.labels"));
2342
2343 let args = cmd.build_command_args();
2344
2345 assert!(args.contains(&"--env-file".to_string()));
2347 assert!(args.contains(&"/etc/environment".to_string()));
2348 assert!(args.contains(&"./app.env".to_string()));
2349
2350 assert!(args.contains(&"--label".to_string()));
2352 assert!(args.contains(&"version=1.0.0".to_string()));
2353 assert!(args.contains(&"maintainer=team@example.com".to_string()));
2354 assert!(args.contains(&"app=myapp".to_string()));
2355
2356 assert!(args.contains(&"--label-file".to_string()));
2358 assert!(args.contains(&"/etc/labels".to_string()));
2359 assert!(args.contains(&"./metadata.labels".to_string()));
2360 }
2361
2362 #[test]
2363 fn test_run_command_all_high_impact_options() {
2364 use std::path::PathBuf;
2365
2366 let cmd = RunCommand::new("nginx:latest")
2367 .name("production-nginx")
2368 .dns("8.8.8.8")
2370 .dns_option("ndots:2")
2371 .dns_search("example.com")
2372 .add_host("api.example.com:127.0.0.1")
2373 .cap_add("NET_ADMIN")
2375 .cap_drop("CHOWN")
2376 .security_opt("no-new-privileges:true")
2377 .device("/dev/null")
2379 .tmpfs("/tmp:rw,size=100m")
2380 .expose("80")
2381 .env_file(PathBuf::from(".env"))
2383 .label("version=1.0.0")
2384 .label_file(PathBuf::from("labels"));
2385
2386 let args = cmd.build_command_args();
2387
2388 assert!(args.contains(&"--dns".to_string()));
2390 assert!(args.contains(&"--dns-option".to_string()));
2391 assert!(args.contains(&"--dns-search".to_string()));
2392 assert!(args.contains(&"--add-host".to_string()));
2393 assert!(args.contains(&"--cap-add".to_string()));
2394 assert!(args.contains(&"--cap-drop".to_string()));
2395 assert!(args.contains(&"--security-opt".to_string()));
2396 assert!(args.contains(&"--device".to_string()));
2397 assert!(args.contains(&"--tmpfs".to_string()));
2398 assert!(args.contains(&"--expose".to_string()));
2399 assert!(args.contains(&"--env-file".to_string()));
2400 assert!(args.contains(&"--label".to_string()));
2401 assert!(args.contains(&"--label-file".to_string()));
2402
2403 let image_pos = args.iter().position(|x| x == "nginx:latest").unwrap();
2405 assert!(image_pos > 0); assert!(image_pos < args.len() - 1 || args.len() == image_pos + 1); }
2408
2409 #[test]
2410 fn test_run_command_empty_lists_not_added() {
2411 let cmd = RunCommand::new("alpine:latest");
2412 let args = cmd.build_command_args();
2413
2414 assert!(!args.contains(&"--dns".to_string()));
2416 assert!(!args.contains(&"--dns-option".to_string()));
2417 assert!(!args.contains(&"--dns-search".to_string()));
2418 assert!(!args.contains(&"--add-host".to_string()));
2419 assert!(!args.contains(&"--cap-add".to_string()));
2420 assert!(!args.contains(&"--cap-drop".to_string()));
2421 assert!(!args.contains(&"--security-opt".to_string()));
2422 assert!(!args.contains(&"--device".to_string()));
2423 assert!(!args.contains(&"--tmpfs".to_string()));
2424 assert!(!args.contains(&"--expose".to_string()));
2425 assert!(!args.contains(&"--env-file".to_string()));
2426 assert!(!args.contains(&"--label".to_string()));
2427 assert!(!args.contains(&"--label-file".to_string()));
2428
2429 assert!(!args.contains(&"--network-alias".to_string()));
2431 assert!(!args.contains(&"--group-add".to_string()));
2432 assert!(!args.contains(&"--attach".to_string()));
2433 assert!(!args.contains(&"--log-opt".to_string()));
2434 assert!(!args.contains(&"--storage-opt".to_string()));
2435 assert!(!args.contains(&"--ulimit".to_string()));
2436 assert!(!args.contains(&"--volumes-from".to_string()));
2437 assert!(!args.contains(&"--link".to_string()));
2438 assert!(!args.contains(&"--link-local-ip".to_string()));
2439
2440 assert!(args.contains(&"alpine:latest".to_string()));
2442 }
2443
2444 #[test]
2445 fn test_run_command_additional_list_options() {
2446 let cmd = RunCommand::new("alpine:latest")
2447 .network_alias("web")
2448 .network_alias("frontend")
2449 .group_add("staff")
2450 .group_add("docker")
2451 .attach("stdout")
2452 .attach("stderr")
2453 .log_opt("max-size=10m")
2454 .log_opt("max-file=3")
2455 .storage_opt("size=20G")
2456 .ulimit("nofile=1024:65536")
2457 .ulimit("nproc=1024")
2458 .volumes_from("data-container")
2459 .volumes_from("config-container:ro")
2460 .link("db:database")
2461 .link("cache:redis")
2462 .link_local_ip("169.254.1.1")
2463 .link_local_ip("fe80::1");
2464
2465 let args = cmd.build_command_args();
2466
2467 assert!(args.contains(&"--network-alias".to_string()));
2469 assert!(args.contains(&"web".to_string()));
2470 assert!(args.contains(&"frontend".to_string()));
2471
2472 assert!(args.contains(&"--group-add".to_string()));
2474 assert!(args.contains(&"staff".to_string()));
2475 assert!(args.contains(&"docker".to_string()));
2476
2477 assert!(args.contains(&"--attach".to_string()));
2479 assert!(args.contains(&"stdout".to_string()));
2480 assert!(args.contains(&"stderr".to_string()));
2481
2482 assert!(args.contains(&"--log-opt".to_string()));
2484 assert!(args.contains(&"max-size=10m".to_string()));
2485 assert!(args.contains(&"max-file=3".to_string()));
2486
2487 assert!(args.contains(&"--storage-opt".to_string()));
2489 assert!(args.contains(&"size=20G".to_string()));
2490
2491 assert!(args.contains(&"--ulimit".to_string()));
2493 assert!(args.contains(&"nofile=1024:65536".to_string()));
2494 assert!(args.contains(&"nproc=1024".to_string()));
2495
2496 assert!(args.contains(&"--volumes-from".to_string()));
2498 assert!(args.contains(&"data-container".to_string()));
2499 assert!(args.contains(&"config-container:ro".to_string()));
2500
2501 assert!(args.contains(&"--link".to_string()));
2503 assert!(args.contains(&"db:database".to_string()));
2504 assert!(args.contains(&"cache:redis".to_string()));
2505
2506 assert!(args.contains(&"--link-local-ip".to_string()));
2508 assert!(args.contains(&"169.254.1.1".to_string()));
2509 assert!(args.contains(&"fe80::1".to_string()));
2510 }
2511
2512 #[test]
2513 fn test_run_command_additional_list_individual_options() {
2514 let network_cmd = RunCommand::new("alpine:latest").network_alias("api");
2516 let network_args = network_cmd.build_command_args();
2517 assert!(network_args.contains(&"--network-alias".to_string()));
2518 assert!(network_args.contains(&"api".to_string()));
2519
2520 let group_cmd = RunCommand::new("alpine:latest").group_add("wheel");
2521 let group_args = group_cmd.build_command_args();
2522 assert!(group_args.contains(&"--group-add".to_string()));
2523 assert!(group_args.contains(&"wheel".to_string()));
2524
2525 let attach_cmd = RunCommand::new("alpine:latest").attach("stdin");
2526 let attach_args = attach_cmd.build_command_args();
2527 assert!(attach_args.contains(&"--attach".to_string()));
2528 assert!(attach_args.contains(&"stdin".to_string()));
2529
2530 let log_cmd = RunCommand::new("alpine:latest").log_opt("compress=true");
2531 let log_args = log_cmd.build_command_args();
2532 assert!(log_args.contains(&"--log-opt".to_string()));
2533 assert!(log_args.contains(&"compress=true".to_string()));
2534
2535 let storage_cmd =
2536 RunCommand::new("alpine:latest").storage_opt("dm.thinpooldev=/dev/mapper/thin-pool");
2537 let storage_args = storage_cmd.build_command_args();
2538 assert!(storage_args.contains(&"--storage-opt".to_string()));
2539 assert!(storage_args.contains(&"dm.thinpooldev=/dev/mapper/thin-pool".to_string()));
2540
2541 let ulimit_cmd = RunCommand::new("alpine:latest").ulimit("memlock=-1:-1");
2542 let ulimit_args = ulimit_cmd.build_command_args();
2543 assert!(ulimit_args.contains(&"--ulimit".to_string()));
2544 assert!(ulimit_args.contains(&"memlock=-1:-1".to_string()));
2545
2546 let volumes_cmd = RunCommand::new("alpine:latest").volumes_from("shared-data");
2547 let volumes_args = volumes_cmd.build_command_args();
2548 assert!(volumes_args.contains(&"--volumes-from".to_string()));
2549 assert!(volumes_args.contains(&"shared-data".to_string()));
2550
2551 let link_cmd = RunCommand::new("alpine:latest").link("mysql:db");
2552 let link_args = link_cmd.build_command_args();
2553 assert!(link_args.contains(&"--link".to_string()));
2554 assert!(link_args.contains(&"mysql:db".to_string()));
2555
2556 let ip_cmd = RunCommand::new("alpine:latest").link_local_ip("169.254.100.1");
2557 let ip_args = ip_cmd.build_command_args();
2558 assert!(ip_args.contains(&"--link-local-ip".to_string()));
2559 assert!(ip_args.contains(&"169.254.100.1".to_string()));
2560 }
2561
2562 #[test]
2563 fn test_run_command_health_check_options() {
2564 let cmd = RunCommand::new("nginx:latest")
2565 .health_cmd("curl -f http://localhost/ || exit 1")
2566 .health_interval("30s")
2567 .health_retries(3)
2568 .health_timeout("5s")
2569 .health_start_period("60s")
2570 .health_start_interval("5s");
2571
2572 let args = cmd.build_command_args();
2573
2574 assert!(args.contains(&"--health-cmd".to_string()));
2576 assert!(args.contains(&"curl -f http://localhost/ || exit 1".to_string()));
2577 assert!(args.contains(&"--health-interval".to_string()));
2578 assert!(args.contains(&"30s".to_string()));
2579 assert!(args.contains(&"--health-retries".to_string()));
2580 assert!(args.contains(&"3".to_string()));
2581 assert!(args.contains(&"--health-timeout".to_string()));
2582 assert!(args.contains(&"5s".to_string()));
2583 assert!(args.contains(&"--health-start-period".to_string()));
2584 assert!(args.contains(&"60s".to_string()));
2585 assert!(args.contains(&"--health-start-interval".to_string()));
2586 }
2588
2589 #[test]
2590 fn test_run_command_advanced_mount_network_options() {
2591 let cmd = RunCommand::new("alpine:latest")
2592 .mount("type=bind,source=/host/path,target=/container/path")
2593 .mount("type=volume,source=data-vol,target=/data")
2594 .network("frontend")
2595 .network("backend")
2596 .gpus("all")
2597 .annotation("io.kubernetes.cri-o.Devices", "/dev/fuse")
2598 .annotation("io.kubernetes.cri-o.ShmSize", "64m")
2599 .sysctl("net.core.somaxconn", "1024")
2600 .sysctl("kernel.shm_rmid_forced", "1");
2601
2602 let args = cmd.build_command_args();
2603
2604 assert!(args.contains(&"--mount".to_string()));
2606 assert!(args.contains(&"type=bind,source=/host/path,target=/container/path".to_string()));
2607 assert!(args.contains(&"type=volume,source=data-vol,target=/data".to_string()));
2608
2609 assert!(args.contains(&"--network".to_string()));
2611 assert!(args.contains(&"frontend".to_string()));
2612 assert!(args.contains(&"backend".to_string()));
2613
2614 assert!(args.contains(&"--gpus".to_string()));
2616 assert!(args.contains(&"all".to_string()));
2617
2618 assert!(args.contains(&"--annotation".to_string()));
2620 assert!(args.contains(&"io.kubernetes.cri-o.Devices=/dev/fuse".to_string()));
2621 assert!(args.contains(&"io.kubernetes.cri-o.ShmSize=64m".to_string()));
2622
2623 assert!(args.contains(&"--sysctl".to_string()));
2625 assert!(args.contains(&"net.core.somaxconn=1024".to_string()));
2626 assert!(args.contains(&"kernel.shm_rmid_forced=1".to_string()));
2627 }
2628
2629 #[test]
2630 fn test_run_command_health_advanced_individual_options() {
2631 let health_cmd = RunCommand::new("alpine:latest").health_cmd("ping -c 1 localhost");
2633 let health_args = health_cmd.build_command_args();
2634 assert!(health_args.contains(&"--health-cmd".to_string()));
2635 assert!(health_args.contains(&"ping -c 1 localhost".to_string()));
2636
2637 let health_interval = RunCommand::new("alpine:latest").health_interval("10s");
2638 let interval_args = health_interval.build_command_args();
2639 assert!(interval_args.contains(&"--health-interval".to_string()));
2640 assert!(interval_args.contains(&"10s".to_string()));
2641
2642 let health_retries = RunCommand::new("alpine:latest").health_retries(5);
2643 let retries_args = health_retries.build_command_args();
2644 assert!(retries_args.contains(&"--health-retries".to_string()));
2645 assert!(retries_args.contains(&"5".to_string()));
2646
2647 let mount_cmd = RunCommand::new("alpine:latest").mount("type=tmpfs,destination=/app");
2648 let mount_args = mount_cmd.build_command_args();
2649 assert!(mount_args.contains(&"--mount".to_string()));
2650 assert!(mount_args.contains(&"type=tmpfs,destination=/app".to_string()));
2651
2652 let network_cmd = RunCommand::new("alpine:latest").network("my-network");
2653 let network_args = network_cmd.build_command_args();
2654 assert!(network_args.contains(&"--network".to_string()));
2655 assert!(network_args.contains(&"my-network".to_string()));
2656
2657 let gpu_cmd = RunCommand::new("alpine:latest").gpus("device=0");
2658 let gpu_args = gpu_cmd.build_command_args();
2659 assert!(gpu_args.contains(&"--gpus".to_string()));
2660 assert!(gpu_args.contains(&"device=0".to_string()));
2661
2662 let annotation_cmd = RunCommand::new("alpine:latest").annotation("key", "value");
2663 let annotation_args = annotation_cmd.build_command_args();
2664 assert!(annotation_args.contains(&"--annotation".to_string()));
2665 assert!(annotation_args.contains(&"key=value".to_string()));
2666
2667 let sysctl_cmd = RunCommand::new("alpine:latest").sysctl("net.ipv4.ip_forward", "1");
2668 let sysctl_args = sysctl_cmd.build_command_args();
2669 assert!(sysctl_args.contains(&"--sysctl".to_string()));
2670 assert!(sysctl_args.contains(&"net.ipv4.ip_forward=1".to_string()));
2671 }
2672
2673 #[test]
2674 fn test_run_command_comprehensive_health_advanced_integration() {
2675 let cmd = RunCommand::new("web-app:latest")
2676 .name("production-web-app")
2677 .health_cmd("curl -f http://localhost:8080/health || exit 1")
2679 .health_interval("30s")
2680 .health_retries(3)
2681 .health_timeout("10s")
2682 .health_start_period("120s")
2683 .mount("type=bind,source=/var/log/app,target=/app/logs")
2685 .mount("type=volume,source=app-data,target=/app/data")
2686 .network("frontend")
2687 .network("backend")
2688 .gpus("device=0,1")
2690 .annotation(
2692 "io.kubernetes.container.apparmor.security.beta.kubernetes.io/app",
2693 "runtime/default",
2694 )
2695 .annotation(
2696 "io.kubernetes.container.seccomp.security.alpha.kubernetes.io/app",
2697 "runtime/default",
2698 )
2699 .sysctl("net.core.somaxconn", "65535")
2701 .sysctl("net.ipv4.tcp_keepalive_time", "600")
2702 .port(8080, 8080)
2704 .env("NODE_ENV", "production")
2705 .memory("2g")
2706 .cpus("2.0")
2707 .restart("unless-stopped")
2708 .detach();
2709
2710 let args = cmd.build_command_args();
2711
2712 assert!(args.contains(&"--health-cmd".to_string()));
2714 assert!(args.contains(&"--health-interval".to_string()));
2715 assert!(args.contains(&"--health-retries".to_string()));
2716 assert!(args.contains(&"--health-timeout".to_string()));
2717 assert!(args.contains(&"--health-start-period".to_string()));
2718 assert!(args.contains(&"--mount".to_string()));
2719 assert!(args.contains(&"--network".to_string()));
2720 assert!(args.contains(&"--gpus".to_string()));
2721 assert!(args.contains(&"--annotation".to_string()));
2722 assert!(args.contains(&"--sysctl".to_string()));
2723
2724 let image_pos = args.iter().position(|x| x == "web-app:latest").unwrap();
2726 assert!(image_pos > 0);
2727 }
2728
2729 #[test]
2730 fn test_run_command_block_io_controls() {
2731 let cmd = RunCommand::new("alpine:latest")
2732 .blkio_weight(500)
2733 .blkio_weight_device("/dev/sda:300")
2734 .blkio_weight_device("/dev/sdb:700")
2735 .device_read_bps("/dev/sda:50mb")
2736 .device_write_bps("/dev/sda:30mb")
2737 .device_read_iops("/dev/sda:1000")
2738 .device_write_iops("/dev/sda:800");
2739
2740 let args = cmd.build_command_args();
2741
2742 assert!(args.contains(&"--blkio-weight".to_string()));
2744 assert!(args.contains(&"500".to_string()));
2745
2746 assert!(args.contains(&"--blkio-weight-device".to_string()));
2748 assert!(args.contains(&"/dev/sda:300".to_string()));
2749 assert!(args.contains(&"/dev/sdb:700".to_string()));
2750
2751 assert!(args.contains(&"--device-read-bps".to_string()));
2753 assert!(args.contains(&"/dev/sda:50mb".to_string()));
2754 assert!(args.contains(&"--device-write-bps".to_string()));
2755 assert!(args.contains(&"/dev/sda:30mb".to_string()));
2756
2757 assert!(args.contains(&"--device-read-iops".to_string()));
2759 assert!(args.contains(&"/dev/sda:1000".to_string()));
2760 assert!(args.contains(&"--device-write-iops".to_string()));
2761 assert!(args.contains(&"/dev/sda:800".to_string()));
2762 }
2763
2764 #[test]
2765 fn test_run_command_realtime_cpu_networking() {
2766 let cmd = RunCommand::new("alpine:latest")
2767 .cpu_rt_period(1_000_000)
2768 .cpu_rt_runtime(950_000)
2769 .ip("172.30.100.104")
2770 .ip6("2001:db8::33")
2771 .device_cgroup_rule("c 1:3 mr")
2772 .device_cgroup_rule("a 7:* rmw");
2773
2774 let args = cmd.build_command_args();
2775
2776 assert!(args.contains(&"--cpu-rt-period".to_string()));
2778 assert!(args.contains(&"1000000".to_string()));
2779 assert!(args.contains(&"--cpu-rt-runtime".to_string()));
2780 assert!(args.contains(&"950000".to_string()));
2781
2782 assert!(args.contains(&"--ip".to_string()));
2784 assert!(args.contains(&"172.30.100.104".to_string()));
2785 assert!(args.contains(&"--ip6".to_string()));
2786 assert!(args.contains(&"2001:db8::33".to_string()));
2787
2788 assert!(args.contains(&"--device-cgroup-rule".to_string()));
2790 assert!(args.contains(&"c 1:3 mr".to_string()));
2791 assert!(args.contains(&"a 7:* rmw".to_string()));
2792 }
2793
2794 #[test]
2795 fn test_run_command_advanced_system_individual_options() {
2796 let blkio_cmd = RunCommand::new("alpine:latest").blkio_weight(100);
2798 let blkio_args = blkio_cmd.build_command_args();
2799 assert!(blkio_args.contains(&"--blkio-weight".to_string()));
2800 assert!(blkio_args.contains(&"100".to_string()));
2801
2802 let weight_device_cmd =
2803 RunCommand::new("alpine:latest").blkio_weight_device("/dev/sda:500");
2804 let weight_device_args = weight_device_cmd.build_command_args();
2805 assert!(weight_device_args.contains(&"--blkio-weight-device".to_string()));
2806 assert!(weight_device_args.contains(&"/dev/sda:500".to_string()));
2807
2808 let read_bps_cmd = RunCommand::new("alpine:latest").device_read_bps("/dev/sda:1mb");
2809 let read_bps_args = read_bps_cmd.build_command_args();
2810 assert!(read_bps_args.contains(&"--device-read-bps".to_string()));
2811 assert!(read_bps_args.contains(&"/dev/sda:1mb".to_string()));
2812
2813 let write_bps_cmd = RunCommand::new("alpine:latest").device_write_bps("/dev/sda:1mb");
2814 let write_bps_args = write_bps_cmd.build_command_args();
2815 assert!(write_bps_args.contains(&"--device-write-bps".to_string()));
2816 assert!(write_bps_args.contains(&"/dev/sda:1mb".to_string()));
2817
2818 let read_iops_cmd = RunCommand::new("alpine:latest").device_read_iops("/dev/sda:100");
2819 let read_iops_args = read_iops_cmd.build_command_args();
2820 assert!(read_iops_args.contains(&"--device-read-iops".to_string()));
2821 assert!(read_iops_args.contains(&"/dev/sda:100".to_string()));
2822
2823 let write_iops_cmd = RunCommand::new("alpine:latest").device_write_iops("/dev/sda:100");
2824 let write_iops_args = write_iops_cmd.build_command_args();
2825 assert!(write_iops_args.contains(&"--device-write-iops".to_string()));
2826 assert!(write_iops_args.contains(&"/dev/sda:100".to_string()));
2827
2828 let rt_period_cmd = RunCommand::new("alpine:latest").cpu_rt_period(100_000);
2829 let rt_period_args = rt_period_cmd.build_command_args();
2830 assert!(rt_period_args.contains(&"--cpu-rt-period".to_string()));
2831 assert!(rt_period_args.contains(&"100000".to_string()));
2832
2833 let rt_runtime_cmd = RunCommand::new("alpine:latest").cpu_rt_runtime(95_000);
2834 let rt_runtime_args = rt_runtime_cmd.build_command_args();
2835 assert!(rt_runtime_args.contains(&"--cpu-rt-runtime".to_string()));
2836 assert!(rt_runtime_args.contains(&"95000".to_string()));
2837
2838 let ip_cmd = RunCommand::new("alpine:latest").ip("192.168.1.100");
2839 let ip_args = ip_cmd.build_command_args();
2840 assert!(ip_args.contains(&"--ip".to_string()));
2841 assert!(ip_args.contains(&"192.168.1.100".to_string()));
2842
2843 let ipv6_cmd = RunCommand::new("alpine:latest").ip6("fe80::1");
2844 let ipv6_args = ipv6_cmd.build_command_args();
2845 assert!(ipv6_args.contains(&"--ip6".to_string()));
2846 assert!(ipv6_args.contains(&"fe80::1".to_string()));
2847
2848 let cgroup_rule_cmd = RunCommand::new("alpine:latest").device_cgroup_rule("c 1:1 rwm");
2849 let cgroup_rule_args = cgroup_rule_cmd.build_command_args();
2850 assert!(cgroup_rule_args.contains(&"--device-cgroup-rule".to_string()));
2851 assert!(cgroup_rule_args.contains(&"c 1:1 rwm".to_string()));
2852 }
2853
2854 #[test]
2855 #[allow(clippy::too_many_lines)]
2856 fn test_run_command_complete_100_percent_coverage() {
2857 use std::path::PathBuf;
2858
2859 let cmd = RunCommand::new("enterprise-app:latest")
2861 .name("production-enterprise")
2862 .detach()
2864 .interactive()
2865 .tty()
2866 .remove()
2867 .env("NODE_ENV", "production")
2869 .port(8080, 8080)
2870 .volume("/data", "/app/data")
2871 .workdir("/app")
2872 .entrypoint("/app/start.sh")
2873 .memory("4g")
2875 .cpus("2.0")
2876 .cpu_shares(1024)
2877 .cpu_period(100_000)
2878 .cpu_quota(50000)
2879 .cpuset_cpus("0-1")
2880 .cpuset_mems("0")
2881 .memory_swap("8g")
2882 .memory_reservation("2g")
2883 .user("app:app")
2885 .privileged()
2886 .hostname("enterprise-app")
2887 .restart("unless-stopped")
2889 .platform("linux/amd64")
2891 .runtime("runc")
2892 .isolation("default")
2893 .pull("always")
2894 .cidfile("/tmp/container.cid")
2895 .domainname("enterprise.local")
2896 .mac_address("02:42:ac:11:00:02")
2897 .log_driver("json-file")
2899 .volume_driver("local")
2900 .userns("host")
2902 .uts("host")
2903 .pid("host")
2904 .ipc("host")
2905 .cgroupns("host")
2906 .cgroup_parent("/docker")
2907 .kernel_memory("1g")
2909 .memory_swappiness(10)
2910 .oom_score_adj(-500)
2911 .pids_limit(1000)
2912 .shm_size("64m")
2913 .stop_signal("SIGTERM")
2915 .stop_timeout(30)
2916 .detach_keys("ctrl-p,ctrl-q")
2917 .no_sig_proxy()
2919 .read_only()
2920 .init()
2921 .oom_kill_disable()
2922 .no_healthcheck()
2923 .enable_content_trust()
2924 .publish_all()
2925 .quiet()
2926 .dns("8.8.8.8")
2928 .dns_option("ndots:2")
2929 .dns_search("enterprise.local")
2930 .add_host("api.enterprise.local:10.0.1.100")
2931 .cap_add("NET_ADMIN")
2932 .cap_drop("ALL")
2933 .security_opt("no-new-privileges:true")
2934 .device("/dev/null")
2935 .tmpfs("/tmp:size=100m")
2936 .expose("9090")
2937 .env_file(PathBuf::from(".env.production"))
2938 .label("app=enterprise")
2939 .label_file(PathBuf::from("labels.txt"))
2940 .network_alias("enterprise-primary")
2942 .group_add("staff")
2943 .attach("stdout")
2944 .log_opt("max-size=10m")
2945 .storage_opt("size=100G")
2946 .ulimit("nofile=65536:65536")
2947 .volumes_from("data-container")
2948 .link("db:database")
2949 .link_local_ip("169.254.1.1")
2950 .health_cmd("curl -f http://localhost:8080/health || exit 1")
2952 .health_interval("30s")
2953 .health_retries(3)
2954 .health_timeout("10s")
2955 .health_start_period("60s")
2956 .health_start_interval("5s")
2957 .mount("type=bind,source=/host/config,target=/app/config")
2958 .network("enterprise-net")
2959 .gpus("device=0")
2960 .annotation("io.kubernetes.cri-o.TTY", "true")
2961 .sysctl("net.core.somaxconn", "65535")
2962 .blkio_weight(500)
2964 .blkio_weight_device("/dev/sda:300")
2965 .device_read_bps("/dev/sda:100mb")
2966 .device_write_bps("/dev/sda:50mb")
2967 .device_read_iops("/dev/sda:1000")
2968 .device_write_iops("/dev/sda:500")
2969 .cpu_rt_period(1_000_000)
2970 .cpu_rt_runtime(950_000)
2971 .ip("10.0.1.50")
2972 .ip6("2001:db8::50")
2973 .device_cgroup_rule("c 1:1 rwm");
2974
2975 let args = cmd.build_command_args();
2976
2977 assert!(args.len() > 150); assert!(args.contains(&"--detach".to_string()));
2982 assert!(args.contains(&"--memory".to_string()));
2983 assert!(args.contains(&"--dns".to_string()));
2984 assert!(args.contains(&"--network-alias".to_string()));
2985 assert!(args.contains(&"--health-cmd".to_string()));
2986 assert!(args.contains(&"--blkio-weight".to_string()));
2987
2988 let image_pos = args
2990 .iter()
2991 .position(|x| x == "enterprise-app:latest")
2992 .unwrap();
2993 assert!(image_pos > 100); assert_eq!(args[args.len() - 1], "enterprise-app:latest");
2995
2996 println!("COMPLETE! All 96 Docker run options implemented and tested!");
2997 }
2998}