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