docker_wrapper/command/
run.rs

1//! Docker run command implementation.
2//!
3//! This module provides a comprehensive implementation of the `docker run` command
4//! with support for common options and an extensible architecture for any additional options.
5
6use 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/// Docker run command builder with fluent API
16#[derive(Debug, Clone)]
17#[allow(clippy::struct_excessive_bools)]
18pub struct RunCommand {
19    /// The Docker image to run
20    image: String,
21    /// Command executor for extensibility
22    pub executor: CommandExecutor,
23    /// Container name
24    name: Option<String>,
25    /// Run in detached mode
26    detach: bool,
27    /// Environment variables
28    environment: EnvironmentBuilder,
29    /// Port mappings
30    ports: PortBuilder,
31    /// Volume mounts
32    volumes: Vec<VolumeMount>,
33    /// Working directory
34    workdir: Option<PathBuf>,
35    /// Entrypoint override
36    entrypoint: Option<String>,
37    /// Command to run in container
38    command: Option<Vec<String>>,
39    /// Interactive mode
40    interactive: bool,
41    /// Allocate TTY
42    tty: bool,
43    /// Remove container on exit
44    remove: bool,
45
46    // Resource Limits
47    /// Memory limit
48    memory: Option<String>,
49    /// Number of CPUs
50    cpus: Option<String>,
51    /// CPU shares (relative weight)
52    cpu_shares: Option<i64>,
53    /// CPU CFS period
54    cpu_period: Option<i64>,
55    /// CPU CFS quota
56    cpu_quota: Option<i64>,
57    /// CPUs in which to allow execution
58    cpuset_cpus: Option<String>,
59    /// MEMs in which to allow execution
60    cpuset_mems: Option<String>,
61    /// Memory + swap limit
62    memory_swap: Option<String>,
63    /// Memory soft limit
64    memory_reservation: Option<String>,
65
66    // Security & User Context
67    /// Username or UID
68    user: Option<String>,
69    /// Give extended privileges
70    privileged: bool,
71    /// Container host name
72    hostname: Option<String>,
73
74    // Lifecycle Management
75    /// Restart policy
76    restart: Option<String>,
77
78    // System Integration
79    /// Set platform if server is multi-platform capable
80    platform: Option<String>,
81    /// Runtime to use for this container
82    runtime: Option<String>,
83    /// Container isolation technology
84    isolation: Option<String>,
85    /// Pull image before running
86    pull: Option<String>,
87    /// Write the container ID to the file
88    cidfile: Option<String>,
89    /// Container NIS domain name
90    domainname: Option<String>,
91    /// Container MAC address
92    mac_address: Option<String>,
93
94    // Logging & Drivers
95    /// Logging driver for the container
96    log_driver: Option<String>,
97    /// Optional volume driver for the container
98    volume_driver: Option<String>,
99
100    // Namespaces
101    /// User namespace to use
102    userns: Option<String>,
103    /// UTS namespace to use
104    uts: Option<String>,
105    /// PID namespace to use
106    pid: Option<String>,
107    /// IPC mode to use
108    ipc: Option<String>,
109    /// Cgroup namespace to use
110    cgroupns: Option<String>,
111    /// Optional parent cgroup for the container
112    cgroup_parent: Option<String>,
113
114    // Advanced Memory & Performance
115    /// Kernel memory limit
116    kernel_memory: Option<String>,
117    /// Tune container memory swappiness (0 to 100)
118    memory_swappiness: Option<i32>,
119    /// Tune host's OOM preferences (-1000 to 1000)
120    oom_score_adj: Option<i32>,
121    /// Tune container pids limit
122    pids_limit: Option<i64>,
123    /// Size of /dev/shm
124    shm_size: Option<String>,
125
126    // Process Control
127    /// Signal to stop the container
128    stop_signal: Option<String>,
129    /// Timeout (in seconds) to stop a container
130    stop_timeout: Option<i32>,
131    /// Override the key sequence for detaching a container
132    detach_keys: Option<String>,
133
134    // Simple Flags
135    /// Proxy received signals to the process
136    sig_proxy: bool,
137    /// Mount the container's root filesystem as read only
138    read_only: bool,
139    /// Run an init inside the container
140    init: bool,
141    /// Disable OOM Killer
142    oom_kill_disable: bool,
143    /// Disable any container-specified HEALTHCHECK
144    no_healthcheck: bool,
145    /// Skip image verification
146    disable_content_trust: bool,
147    /// Publish all exposed ports to random ports
148    publish_all: bool,
149    /// Suppress the pull output
150    quiet: bool,
151
152    // High-Impact List Options
153    // DNS & Network
154    /// Custom DNS servers
155    dns: Vec<String>,
156    /// DNS options
157    dns_option: Vec<String>,
158    /// DNS search domains
159    dns_search: Vec<String>,
160    /// Add host-to-IP mappings (host:ip)
161    add_host: Vec<String>,
162
163    // Security & Capabilities
164    /// Add Linux capabilities
165    cap_add: Vec<String>,
166    /// Drop Linux capabilities
167    cap_drop: Vec<String>,
168    /// Security options
169    security_opt: Vec<String>,
170
171    // Device & Filesystem
172    /// Add host devices to container
173    device: Vec<String>,
174    /// Mount tmpfs directories
175    tmpfs: Vec<String>,
176    /// Expose ports without publishing them
177    expose: Vec<String>,
178
179    // Environment & Labels
180    /// Read environment from files
181    env_file: Vec<PathBuf>,
182    /// Set metadata labels
183    label: Vec<String>,
184    /// Read labels from files
185    label_file: Vec<PathBuf>,
186
187    // Additional List/Vec Options
188    /// Network aliases for the container
189    network_alias: Vec<String>,
190    /// Additional groups for the user
191    group_add: Vec<String>,
192    /// Attach to STDIN, STDOUT or STDERR
193    attach: Vec<String>,
194    /// Log driver options
195    log_opt: Vec<String>,
196    /// Storage driver options
197    storage_opt: Vec<String>,
198    /// Ulimit options
199    ulimit: Vec<String>,
200    /// Mount volumes from other containers
201    volumes_from: Vec<String>,
202    /// Add link to another container (deprecated)
203    link: Vec<String>,
204    /// Container IPv4/IPv6 link-local addresses
205    link_local_ip: Vec<String>,
206
207    // Health Check Options
208    // Health checks
209    /// Command to run to check health
210    health_cmd: Option<String>,
211    /// Time between running the check (ms|s|m|h)
212    health_interval: Option<String>,
213    /// Consecutive failures needed to report unhealthy
214    health_retries: Option<i32>,
215    /// Maximum time to allow one check to run (ms|s|m|h)
216    health_timeout: Option<String>,
217    /// Start period for the container to initialize before health-checking (ms|s|m|h)
218    health_start_period: Option<String>,
219    /// Time between health checks during the start period (ms|s|m|h)
220    health_start_interval: Option<String>,
221
222    // Advanced options
223    /// Advanced mount configuration
224    mount: Vec<String>,
225    /// Connect to a network
226    network: Vec<String>,
227    /// GPU devices to add to the container
228    gpus: Option<String>,
229
230    // Map-based options (stored as Vec<String> in key=value format)
231    /// Add custom annotations
232    annotation: Vec<String>,
233    /// Kernel parameters to set
234    sysctl: Vec<String>,
235
236    // Advanced System Options
237    // Block I/O controls
238    /// Block IO weight (relative weight)
239    blkio_weight: Option<u16>,
240    /// Block IO weight per device
241    blkio_weight_device: Vec<String>,
242    /// Limit read rate (bytes per second) from a device
243    device_read_bps: Vec<String>,
244    /// Limit write rate (bytes per second) to a device
245    device_write_bps: Vec<String>,
246    /// Limit read rate (IO per second) from a device
247    device_read_iops: Vec<String>,
248    /// Limit write rate (IO per second) to a device
249    device_write_iops: Vec<String>,
250
251    // Real-time CPU scheduling
252    /// Limit CPU real-time period in microseconds
253    cpu_rt_period: Option<i64>,
254    /// Limit CPU real-time runtime in microseconds
255    cpu_rt_runtime: Option<i64>,
256
257    // Advanced networking
258    /// Container IPv4 address
259    ip: Option<String>,
260    /// Container IPv6 address
261    ip6: Option<String>,
262
263    // Advanced system options
264    /// Cgroup rule for devices
265    device_cgroup_rule: Vec<String>,
266}
267
268/// Volume mount configuration
269#[derive(Debug, Clone)]
270pub struct VolumeMount {
271    /// Source path on host or volume name
272    pub source: String,
273    /// Target path in container
274    pub target: String,
275    /// Mount type (bind, volume, tmpfs)
276    pub mount_type: MountType,
277    /// Read-only mount
278    pub readonly: bool,
279}
280
281/// Type of volume mount
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub enum MountType {
284    /// Bind mount from host filesystem
285    Bind,
286    /// Named volume
287    Volume,
288    /// Temporary filesystem
289    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/// Container ID returned by docker run
300#[derive(Debug, Clone, PartialEq, Eq)]
301pub struct ContainerId(pub String);
302
303impl ContainerId {
304    /// Get the container ID as a string
305    #[must_use]
306    pub fn as_str(&self) -> &str {
307        &self.0
308    }
309
310    /// Get the short form of the container ID (first 12 characters)
311    #[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    /// Get port mappings for this container
321    ///
322    /// This queries Docker for the actual mapped ports of the running container.
323    /// Useful when using dynamic port allocation (e.g., `-p 6379` without specifying host port).
324    ///
325    /// # Example
326    ///
327    /// ```no_run
328    /// use docker_wrapper::{DockerCommand, RunCommand};
329    ///
330    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
331    /// // Run Redis with dynamic port allocation
332    /// let container_id = RunCommand::new("redis:alpine")
333    ///     .name("my-redis")
334    ///     .port_dyn(6379)  // Dynamic port allocation
335    ///     .detach()
336    ///     .rm()
337    ///     .execute()
338    ///     .await?;
339    ///
340    /// // Get the actual mapped port
341    /// let port_mappings = container_id.port_mappings().await?;
342    /// if let Some(mapping) = port_mappings.first() {
343    ///     println!("Redis is available at {}:{}", mapping.host_ip, mapping.host_port);
344    /// }
345    /// # Ok(())
346    /// # }
347    /// ```
348    ///
349    /// # Errors
350    ///
351    /// Returns an error if:
352    /// - The container doesn't exist or has been removed
353    /// - The Docker daemon is not running
354    /// - There's a communication error with Docker
355    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    /// Get a specific port mapping for this container
361    ///
362    /// # Example
363    ///
364    /// ```no_run
365    /// use docker_wrapper::{DockerCommand, RunCommand};
366    ///
367    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
368    /// let container_id = RunCommand::new("nginx:alpine")
369    ///     .port_dyn(80)
370    ///     .detach()
371    ///     .rm()
372    ///     .execute()
373    ///     .await?;
374    ///
375    /// // Get the mapping for port 80
376    /// if let Some(mapping) = container_id.port_mapping(80).await? {
377    ///     println!("Nginx is available at {}:{}", mapping.host_ip, mapping.host_port);
378    /// }
379    /// # Ok(())
380    /// # }
381    /// ```
382    ///
383    /// # Errors
384    ///
385    /// Returns an error if:
386    /// - The container doesn't exist or has been removed
387    /// - The Docker daemon is not running
388    /// - There's a communication error with Docker
389    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    /// Create a new run command for the specified image
403    #[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            // Resource Limits
421            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            // Security & User Context
432            user: None,
433            privileged: false,
434            hostname: None,
435
436            // Lifecycle Management
437            restart: None,
438
439            // System Integration
440            platform: None,
441            runtime: None,
442            isolation: None,
443            pull: None,
444            cidfile: None,
445            domainname: None,
446            mac_address: None,
447
448            // Logging & Drivers
449            log_driver: None,
450            volume_driver: None,
451
452            // Namespaces
453            userns: None,
454            uts: None,
455            pid: None,
456            ipc: None,
457            cgroupns: None,
458            cgroup_parent: None,
459
460            // Advanced Memory & Performance
461            kernel_memory: None,
462            memory_swappiness: None,
463            oom_score_adj: None,
464            pids_limit: None,
465            shm_size: None,
466
467            // Process Control
468            stop_signal: None,
469            stop_timeout: None,
470            detach_keys: None,
471
472            // Simple Flags
473            sig_proxy: true, // Default is true in Docker
474            read_only: false,
475            init: false,
476            oom_kill_disable: false,
477            no_healthcheck: false,
478            disable_content_trust: true, // Default is true in Docker
479            publish_all: false,
480            quiet: false,
481
482            // High-Impact List Options
483            // DNS & Network
484            dns: Vec::new(),
485            dns_option: Vec::new(),
486            dns_search: Vec::new(),
487            add_host: Vec::new(),
488
489            // Security & Capabilities
490            cap_add: Vec::new(),
491            cap_drop: Vec::new(),
492            security_opt: Vec::new(),
493
494            // Device & Filesystem
495            device: Vec::new(),
496            tmpfs: Vec::new(),
497            expose: Vec::new(),
498
499            // Environment & Labels
500            env_file: Vec::new(),
501            label: Vec::new(),
502            label_file: Vec::new(),
503
504            // Additional List/Vec Options
505            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 Check Options
516            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            // Advanced System Options
529            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    /// Set the container name
544    #[must_use]
545    pub fn name(mut self, name: impl Into<String>) -> Self {
546        self.name = Some(name.into());
547        self
548    }
549
550    /// Run in detached mode (background)
551    #[must_use]
552    pub fn detach(mut self) -> Self {
553        self.detach = true;
554        self
555    }
556
557    /// Add an environment variable
558    #[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    /// Add multiple environment variables
565    #[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    /// Add a port mapping
572    #[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    /// Add a dynamic port mapping (Docker assigns host port)
579    #[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    /// Alias for `dynamic_port()` - Add a dynamic port mapping (Docker assigns host port)
586    #[must_use]
587    pub fn port_dyn(self, container_port: u16) -> Self {
588        self.dynamic_port(container_port)
589    }
590
591    /// Add a volume mount
592    #[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    /// Add a bind mount
604    #[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    /// Add a read-only volume mount
616    #[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    /// Set working directory
628    #[must_use]
629    pub fn workdir(mut self, workdir: impl Into<PathBuf>) -> Self {
630        self.workdir = Some(workdir.into());
631        self
632    }
633
634    /// Override entrypoint
635    #[must_use]
636    pub fn entrypoint(mut self, entrypoint: impl Into<String>) -> Self {
637        self.entrypoint = Some(entrypoint.into());
638        self
639    }
640
641    /// Set command to run in container
642    #[must_use]
643    pub fn cmd(mut self, command: Vec<String>) -> Self {
644        self.command = Some(command);
645        self
646    }
647
648    /// Enable interactive mode
649    #[must_use]
650    pub fn interactive(mut self) -> Self {
651        self.interactive = true;
652        self
653    }
654
655    /// Allocate a TTY
656    #[must_use]
657    pub fn tty(mut self) -> Self {
658        self.tty = true;
659        self
660    }
661
662    /// Remove container automatically when it exits
663    #[must_use]
664    pub fn remove(mut self) -> Self {
665        self.remove = true;
666        self
667    }
668
669    /// Alias for `remove()` - Remove container automatically when it exits (--rm flag)
670    #[must_use]
671    pub fn rm(self) -> Self {
672        self.remove()
673    }
674
675    /// Convenience method for interactive TTY mode
676    #[must_use]
677    pub fn it(self) -> Self {
678        self.interactive().tty()
679    }
680
681    // Resource Limits
682    /// Set memory limit (e.g., "1g", "512m")
683    #[must_use]
684    pub fn memory(mut self, memory: impl Into<String>) -> Self {
685        self.memory = Some(memory.into());
686        self
687    }
688
689    /// Set number of CPUs (e.g., "2.0", "1.5")
690    #[must_use]
691    pub fn cpus(mut self, cpus: impl Into<String>) -> Self {
692        self.cpus = Some(cpus.into());
693        self
694    }
695
696    /// Set CPU shares (relative weight)
697    #[must_use]
698    pub fn cpu_shares(mut self, shares: i64) -> Self {
699        self.cpu_shares = Some(shares);
700        self
701    }
702
703    /// Set CPU CFS period in microseconds
704    #[must_use]
705    pub fn cpu_period(mut self, period: i64) -> Self {
706        self.cpu_period = Some(period);
707        self
708    }
709
710    /// Set CPU CFS quota in microseconds
711    #[must_use]
712    pub fn cpu_quota(mut self, quota: i64) -> Self {
713        self.cpu_quota = Some(quota);
714        self
715    }
716
717    /// Set CPUs in which to allow execution (e.g., "0-3", "0,1")
718    #[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    /// Set MEMs in which to allow execution (e.g., "0-3", "0,1")
725    #[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    /// Set memory + swap limit (e.g., "2g", "-1" for unlimited)
732    #[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    /// Set memory soft limit (e.g., "500m")
739    #[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    // Security & User Context
746    /// Set username or UID (format: <name|uid>[:<group|gid>])
747    #[must_use]
748    pub fn user(mut self, user: impl Into<String>) -> Self {
749        self.user = Some(user.into());
750        self
751    }
752
753    /// Give extended privileges to this container
754    #[must_use]
755    pub fn privileged(mut self) -> Self {
756        self.privileged = true;
757        self
758    }
759
760    /// Set container host name
761    #[must_use]
762    pub fn hostname(mut self, hostname: impl Into<String>) -> Self {
763        self.hostname = Some(hostname.into());
764        self
765    }
766
767    // Lifecycle Management
768    /// Set restart policy (e.g., "always", "unless-stopped", "on-failure", "no")
769    #[must_use]
770    pub fn restart(mut self, restart: impl Into<String>) -> Self {
771        self.restart = Some(restart.into());
772        self
773    }
774
775    // System Integration
776    /// Set platform if server is multi-platform capable (e.g., "linux/amd64")
777    #[must_use]
778    pub fn platform(mut self, platform: impl Into<String>) -> Self {
779        self.platform = Some(platform.into());
780        self
781    }
782
783    /// Set runtime to use for this container
784    #[must_use]
785    pub fn runtime(mut self, runtime: impl Into<String>) -> Self {
786        self.runtime = Some(runtime.into());
787        self
788    }
789
790    /// Set container isolation technology
791    #[must_use]
792    pub fn isolation(mut self, isolation: impl Into<String>) -> Self {
793        self.isolation = Some(isolation.into());
794        self
795    }
796
797    /// Set pull image policy ("always", "missing", "never")
798    #[must_use]
799    pub fn pull(mut self, pull: impl Into<String>) -> Self {
800        self.pull = Some(pull.into());
801        self
802    }
803
804    /// Write the container ID to the specified file
805    #[must_use]
806    pub fn cidfile(mut self, cidfile: impl Into<String>) -> Self {
807        self.cidfile = Some(cidfile.into());
808        self
809    }
810
811    /// Set container NIS domain name
812    #[must_use]
813    pub fn domainname(mut self, domainname: impl Into<String>) -> Self {
814        self.domainname = Some(domainname.into());
815        self
816    }
817
818    /// Set container MAC address (e.g., "92:d0:c6:0a:29:33")
819    #[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    // Logging & Drivers
826    /// Set logging driver for the container
827    #[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    /// Set optional volume driver for the container
834    #[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    // Namespaces
841    /// Set user namespace to use
842    #[must_use]
843    pub fn userns(mut self, userns: impl Into<String>) -> Self {
844        self.userns = Some(userns.into());
845        self
846    }
847
848    /// Set UTS namespace to use
849    #[must_use]
850    pub fn uts(mut self, uts: impl Into<String>) -> Self {
851        self.uts = Some(uts.into());
852        self
853    }
854
855    /// Set PID namespace to use
856    #[must_use]
857    pub fn pid(mut self, pid: impl Into<String>) -> Self {
858        self.pid = Some(pid.into());
859        self
860    }
861
862    /// Set IPC mode to use
863    #[must_use]
864    pub fn ipc(mut self, ipc: impl Into<String>) -> Self {
865        self.ipc = Some(ipc.into());
866        self
867    }
868
869    /// Set cgroup namespace to use (host|private)
870    #[must_use]
871    pub fn cgroupns(mut self, cgroupns: impl Into<String>) -> Self {
872        self.cgroupns = Some(cgroupns.into());
873        self
874    }
875
876    /// Set optional parent cgroup for the container
877    #[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    // Advanced Memory & Performance
884    /// Set kernel memory limit
885    #[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    /// Tune container memory swappiness (0 to 100)
892    #[must_use]
893    pub fn memory_swappiness(mut self, swappiness: i32) -> Self {
894        self.memory_swappiness = Some(swappiness);
895        self
896    }
897
898    /// Tune host's OOM preferences (-1000 to 1000)
899    #[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    /// Tune container pids limit (set -1 for unlimited)
906    #[must_use]
907    pub fn pids_limit(mut self, limit: i64) -> Self {
908        self.pids_limit = Some(limit);
909        self
910    }
911
912    /// Set size of /dev/shm (e.g., "64m")
913    #[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    // Process Control
920    /// Set signal to stop the container (e.g., "SIGTERM", "SIGKILL")
921    #[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    /// Set timeout (in seconds) to stop a container
928    #[must_use]
929    pub fn stop_timeout(mut self, timeout: i32) -> Self {
930        self.stop_timeout = Some(timeout);
931        self
932    }
933
934    /// Override the key sequence for detaching a container
935    #[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    // Simple Flags
942    /// Disable proxying received signals to the process
943    #[must_use]
944    pub fn no_sig_proxy(mut self) -> Self {
945        self.sig_proxy = false;
946        self
947    }
948
949    /// Mount the container's root filesystem as read only
950    #[must_use]
951    pub fn read_only(mut self) -> Self {
952        self.read_only = true;
953        self
954    }
955
956    /// Run an init inside the container that forwards signals and reaps processes
957    #[must_use]
958    pub fn init(mut self) -> Self {
959        self.init = true;
960        self
961    }
962
963    /// Disable OOM Killer
964    #[must_use]
965    pub fn oom_kill_disable(mut self) -> Self {
966        self.oom_kill_disable = true;
967        self
968    }
969
970    /// Disable any container-specified HEALTHCHECK
971    #[must_use]
972    pub fn no_healthcheck(mut self) -> Self {
973        self.no_healthcheck = true;
974        self
975    }
976
977    /// Enable image verification (disable content trust is false)
978    #[must_use]
979    pub fn enable_content_trust(mut self) -> Self {
980        self.disable_content_trust = false;
981        self
982    }
983
984    /// Publish all exposed ports to random ports
985    #[must_use]
986    pub fn publish_all(mut self) -> Self {
987        self.publish_all = true;
988        self
989    }
990
991    /// Suppress the pull output
992    #[must_use]
993    pub fn quiet(mut self) -> Self {
994        self.quiet = true;
995        self
996    }
997
998    // High-Impact List Options
999
1000    // DNS & Network
1001    /// Add custom DNS server
1002    #[must_use]
1003    pub fn dns(mut self, dns: impl Into<String>) -> Self {
1004        self.dns.push(dns.into());
1005        self
1006    }
1007
1008    /// Add multiple DNS servers
1009    #[must_use]
1010    pub fn dns_servers(mut self, servers: Vec<String>) -> Self {
1011        self.dns.extend(servers);
1012        self
1013    }
1014
1015    /// Add DNS option
1016    #[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    /// Add DNS search domain
1023    #[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    /// Add host-to-IP mapping (format: "hostname:ip")
1030    #[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    // Security & Capabilities
1037    /// Add Linux capability
1038    #[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    /// Drop Linux capability
1045    #[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    /// Add security option
1052    #[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    // Device & Filesystem
1059    /// Add host device to container
1060    #[must_use]
1061    pub fn device(mut self, device: impl Into<String>) -> Self {
1062        self.device.push(device.into());
1063        self
1064    }
1065
1066    /// Mount tmpfs directory
1067    #[must_use]
1068    pub fn tmpfs(mut self, path: impl Into<String>) -> Self {
1069        self.tmpfs.push(path.into());
1070        self
1071    }
1072
1073    /// Expose port without publishing it
1074    #[must_use]
1075    pub fn expose(mut self, port: impl Into<String>) -> Self {
1076        self.expose.push(port.into());
1077        self
1078    }
1079
1080    // Environment & Labels
1081    /// Read environment variables from file
1082    #[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    /// Add metadata label
1089    #[must_use]
1090    pub fn label(mut self, label: impl Into<String>) -> Self {
1091        self.label.push(label.into());
1092        self
1093    }
1094
1095    /// Read labels from file
1096    #[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    // Additional List/Vec Options
1103
1104    /// Add network alias for the container
1105    #[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    /// Add supplementary group for the user
1112    #[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    /// Attach to STDIN, STDOUT or STDERR
1119    #[must_use]
1120    pub fn attach(mut self, stream: impl Into<String>) -> Self {
1121        self.attach.push(stream.into());
1122        self
1123    }
1124
1125    /// Add log driver option
1126    #[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    /// Add storage driver option
1133    #[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    /// Set ulimit option
1140    #[must_use]
1141    pub fn ulimit(mut self, limit: impl Into<String>) -> Self {
1142        self.ulimit.push(limit.into());
1143        self
1144    }
1145
1146    /// Mount volumes from another container
1147    #[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    /// Add link to another container (deprecated)
1154    #[must_use]
1155    pub fn link(mut self, link: impl Into<String>) -> Self {
1156        self.link.push(link.into());
1157        self
1158    }
1159
1160    /// Add container IPv4/IPv6 link-local address
1161    #[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    // Health Check Options
1168
1169    // Health check methods
1170    /// Set health check command
1171    #[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    /// Set health check interval
1178    #[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    /// Set health check retries
1185    #[must_use]
1186    pub fn health_retries(mut self, retries: i32) -> Self {
1187        self.health_retries = Some(retries);
1188        self
1189    }
1190
1191    /// Set health check timeout
1192    #[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    /// Set health check start period
1199    #[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    /// Set health check start interval
1206    #[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    // Advanced options
1213    /// Add advanced mount configuration
1214    #[must_use]
1215    pub fn mount(mut self, mount: impl Into<String>) -> Self {
1216        self.mount.push(mount.into());
1217        self
1218    }
1219
1220    /// Connect to a network
1221    #[must_use]
1222    pub fn network(mut self, network: impl Into<String>) -> Self {
1223        self.network.push(network.into());
1224        self
1225    }
1226
1227    /// Set GPU devices to add to the container
1228    #[must_use]
1229    pub fn gpus(mut self, gpus: impl Into<String>) -> Self {
1230        self.gpus = Some(gpus.into());
1231        self
1232    }
1233
1234    /// Add custom annotation (key=value format)
1235    #[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    /// Set kernel parameter (key=value format)
1243    #[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    // Advanced System Options
1250
1251    // Block I/O controls
1252    /// Set block IO weight (10-1000)
1253    #[must_use]
1254    pub fn blkio_weight(mut self, weight: u16) -> Self {
1255        self.blkio_weight = Some(weight);
1256        self
1257    }
1258
1259    /// Set block IO weight for a specific device (format: DEVICE:WEIGHT)
1260    #[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    /// Limit read rate (bytes per second) from a device (format: DEVICE:RATE)
1267    #[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    /// Limit write rate (bytes per second) to a device (format: DEVICE:RATE)
1274    #[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    /// Limit read rate (IO per second) from a device (format: DEVICE:RATE)
1281    #[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    /// Limit write rate (IO per second) to a device (format: DEVICE:RATE)
1288    #[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    // Real-time CPU scheduling
1295    /// Limit CPU real-time period in microseconds
1296    #[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    /// Limit CPU real-time runtime in microseconds
1303    #[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    // Advanced networking
1310    /// Set container IPv4 address (e.g., "172.30.100.104")
1311    #[must_use]
1312    pub fn ip(mut self, ip: impl Into<String>) -> Self {
1313        self.ip = Some(ip.into());
1314        self
1315    }
1316
1317    /// Set container IPv6 address (e.g., "`2001:db8::33`")
1318    #[must_use]
1319    pub fn ip6(mut self, ip6: impl Into<String>) -> Self {
1320        self.ip6 = Some(ip6.into());
1321        self
1322    }
1323
1324    /// Add device cgroup rule
1325    #[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        // Add flags
1349        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        // Add container name
1363        if let Some(ref name) = self.name {
1364            args.push("--name".to_string());
1365            args.push(name.clone());
1366        }
1367
1368        // Add working directory
1369        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        // Add entrypoint
1375        if let Some(ref entrypoint) = self.entrypoint {
1376            args.push("--entrypoint".to_string());
1377            args.push(entrypoint.clone());
1378        }
1379
1380        // Add environment variables
1381        args.extend(self.environment.build_args());
1382
1383        // Add port mappings
1384        args.extend(self.ports.build_args());
1385
1386        // Add volume mounts
1387        for volume in &self.volumes {
1388            args.push("--volume".to_string());
1389            args.push(volume.to_string());
1390        }
1391
1392        // Resource Limits
1393        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        // Security & User Context
1431        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        // Lifecycle Management
1444        if let Some(ref restart) = self.restart {
1445            args.push("--restart".to_string());
1446            args.push(restart.clone());
1447        }
1448
1449        // System Integration
1450        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        // Logging & Drivers
1480        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        // Namespaces
1490        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        // Advanced Memory & Performance
1516        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        // Process Control
1538        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        // Simple Flags
1552        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        // High-Impact List Options
1578        // DNS & Network
1579        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        // Security & Capabilities
1597        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        // Device & Filesystem
1611        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        // Environment & Labels
1625        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        // Additional List/Vec Options
1639        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        // Health Check Options
1677        // Health checks
1678        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        // Advanced options
1704        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        // Map-based options
1718        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        // Advanced System Options
1728        // Block I/O controls
1729        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        // Real-time CPU scheduling
1755        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        // Advanced networking
1765        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        // Advanced system options
1775        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        // Add image
1781        args.push(self.image.clone());
1782
1783        // Add command if specified
1784        if let Some(ref command) = self.command {
1785            args.extend(command.clone());
1786        }
1787
1788        // Add raw arguments from executor
1789        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        // Parse container ID from output
1799        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// Streaming support for RunCommand
1811#[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        // Don't stream if running detached
1818        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        // Don't stream if running detached
1836        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    /// Run the container with streaming output
1855    ///
1856    /// Note: This will fail if the container is run in detached mode.
1857    ///
1858    /// # Examples
1859    ///
1860    /// ```no_run
1861    /// use docker_wrapper::RunCommand;
1862    /// use docker_wrapper::StreamHandler;
1863    ///
1864    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1865    /// let result = RunCommand::new("alpine")
1866    ///     .cmd(vec!["echo".to_string(), "Hello, World!".to_string()])
1867    ///     .stream(StreamHandler::print())
1868    ///     .await?;
1869    /// # Ok(())
1870    /// # }
1871    /// ```
1872    ///
1873    /// # Errors
1874    ///
1875    /// Returns an error if the container is detached or encounters an I/O error
1876    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        // The extensibility is tested via the trait methods
1935        // Full integration testing would require actual Docker execution
1936    }
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        // Verify comprehensive options are all present
2171        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        // Image should be at the end (before any command args)
2193        let image_pos = args.iter().position(|x| x == "nginx:latest").unwrap();
2194        assert!(image_pos > 10); // Should be after all the options
2195    }
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        // Default flags should not appear in args unless explicitly changed
2203        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        // DNS servers
2245        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        // DNS options
2252        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        // DNS search domains
2257        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        // Host mappings
2262        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        // Capabilities to add
2280        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        // Capabilities to drop
2285        assert!(args.contains(&"--cap-drop".to_string()));
2286        assert!(args.contains(&"CHOWN".to_string()));
2287        assert!(args.contains(&"DAC_OVERRIDE".to_string()));
2288
2289        // Security options
2290        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        // Devices
2309        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        // Tmpfs mounts
2314        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        // Exposed ports
2319        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        // Environment files
2341        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        // Labels
2346        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        // Label files
2352        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 & Network
2364            .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            // Security & Capabilities
2369            .cap_add("NET_ADMIN")
2370            .cap_drop("CHOWN")
2371            .security_opt("no-new-privileges:true")
2372            // Device & Filesystem
2373            .device("/dev/null")
2374            .tmpfs("/tmp:rw,size=100m")
2375            .expose("80")
2376            // Environment & Labels
2377            .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        // Verify all option types are present
2384        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        // Verify image is still at the end
2399        let image_pos = args.iter().position(|x| x == "nginx:latest").unwrap();
2400        assert!(image_pos > 0); // Should not be first
2401        assert!(image_pos < args.len() - 1 || args.len() == image_pos + 1); // Should be near end
2402    }
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        // Ensure empty lists don't add any arguments
2410        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        // Additional list options should also not be present
2425        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        // But image should still be there
2436        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        // Network aliases
2463        assert!(args.contains(&"--network-alias".to_string()));
2464        assert!(args.contains(&"web".to_string()));
2465        assert!(args.contains(&"frontend".to_string()));
2466
2467        // Group additions
2468        assert!(args.contains(&"--group-add".to_string()));
2469        assert!(args.contains(&"staff".to_string()));
2470        assert!(args.contains(&"docker".to_string()));
2471
2472        // Stream attachments
2473        assert!(args.contains(&"--attach".to_string()));
2474        assert!(args.contains(&"stdout".to_string()));
2475        assert!(args.contains(&"stderr".to_string()));
2476
2477        // Log options
2478        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        // Storage options
2483        assert!(args.contains(&"--storage-opt".to_string()));
2484        assert!(args.contains(&"size=20G".to_string()));
2485
2486        // Ulimits
2487        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        // Volumes from containers
2492        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        // Container links
2497        assert!(args.contains(&"--link".to_string()));
2498        assert!(args.contains(&"db:database".to_string()));
2499        assert!(args.contains(&"cache:redis".to_string()));
2500
2501        // Link-local IPs
2502        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        // Test each additional list option individually for proper argument generation
2510        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        // Health check options
2570        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        // Note: "5s" appears twice, so we can't easily test for this specific one
2582    }
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        // Mount options
2600        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        // Network options
2605        assert!(args.contains(&"--network".to_string()));
2606        assert!(args.contains(&"frontend".to_string()));
2607        assert!(args.contains(&"backend".to_string()));
2608
2609        // GPU options
2610        assert!(args.contains(&"--gpus".to_string()));
2611        assert!(args.contains(&"all".to_string()));
2612
2613        // Annotation options
2614        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        // Sysctl options
2619        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        // Test each health check and advanced option individually
2627        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 checks for production readiness
2673            .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            // Advanced mounting and networking
2679            .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            // GPU support for ML workloads
2684            .gpus("device=0,1")
2685            // Kubernetes annotations
2686            .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            // System tuning
2695            .sysctl("net.core.somaxconn", "65535")
2696            .sysctl("net.ipv4.tcp_keepalive_time", "600")
2697            // Additional standard options
2698            .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        // Verify all health check and advanced options are present
2708        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        // Verify image is still at the end
2720        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        // Block I/O weight
2738        assert!(args.contains(&"--blkio-weight".to_string()));
2739        assert!(args.contains(&"500".to_string()));
2740
2741        // Block I/O weight per device
2742        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        // Device read/write BPS
2747        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        // Device read/write IOPS
2753        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        // Real-time CPU scheduling
2772        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        // Advanced networking
2778        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        // Device cgroup rules
2784        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        // Test each advanced system option individually
2792        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        // Test demonstrating ALL 96 Docker run options are now implemented
2855        let cmd = RunCommand::new("enterprise-app:latest")
2856            .name("production-enterprise")
2857            // Basic options
2858            .detach()
2859            .interactive()
2860            .tty()
2861            .remove()
2862            // Environment and ports
2863            .env("NODE_ENV", "production")
2864            .port(8080, 8080)
2865            .volume("/data", "/app/data")
2866            .workdir("/app")
2867            .entrypoint("/app/start.sh")
2868            // Resource limits (Phase 2)
2869            .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            // Security & User Context (Phase 2)
2879            .user("app:app")
2880            .privileged()
2881            .hostname("enterprise-app")
2882            // Lifecycle Management (Phase 2)
2883            .restart("unless-stopped")
2884            // System Integration (Phase 2)
2885            .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            // Logging & Drivers (Phase 2)
2893            .log_driver("json-file")
2894            .volume_driver("local")
2895            // Namespaces (Phase 2)
2896            .userns("host")
2897            .uts("host")
2898            .pid("host")
2899            .ipc("host")
2900            .cgroupns("host")
2901            .cgroup_parent("/docker")
2902            // Advanced Memory & Performance (Phase 2)
2903            .kernel_memory("1g")
2904            .memory_swappiness(10)
2905            .oom_score_adj(-500)
2906            .pids_limit(1000)
2907            .shm_size("64m")
2908            // Process Control (Phase 2)
2909            .stop_signal("SIGTERM")
2910            .stop_timeout(30)
2911            .detach_keys("ctrl-p,ctrl-q")
2912            // Simple Flags (Phase 2)
2913            .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            // High-Impact List Options (Phase 3)
2922            .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            // Batch 1: Additional List/Vec Options
2936            .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            // Batch 2: Medium Complexity Options
2946            .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            // Batch 3: Complex/Advanced Options
2958            .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        // Verify we have a substantial command with all option types
2973        assert!(args.len() > 150); // Should be a very long command
2974
2975        // Verify key options from each batch are present
2976        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        // Verify image is still at the end
2984        let image_pos = args
2985            .iter()
2986            .position(|x| x == "enterprise-app:latest")
2987            .unwrap();
2988        assert!(image_pos > 100); // Should be very far into the command
2989        assert_eq!(args[args.len() - 1], "enterprise-app:latest");
2990
2991        println!("COMPLETE! All 96 Docker run options implemented and tested!");
2992    }
2993}