docker_wrapper/command/
attach.rs

1//! Docker attach command implementation.
2//!
3//! This module provides the `docker attach` command for attaching to a running container.
4
5use super::{CommandExecutor, CommandOutput, DockerCommand};
6use crate::error::Result;
7use async_trait::async_trait;
8
9/// Docker attach command builder
10///
11/// Attach local standard input, output, and error streams to a running container.
12///
13/// # Example
14///
15/// ```no_run
16/// use docker_wrapper::AttachCommand;
17///
18/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
19/// // Attach to a running container
20/// AttachCommand::new("my-container")
21///     .run()
22///     .await?;
23///
24/// // Attach without stdin
25/// AttachCommand::new("my-container")
26///     .no_stdin()
27///     .run()
28///     .await?;
29/// # Ok(())
30/// # }
31/// ```
32#[derive(Debug, Clone)]
33pub struct AttachCommand {
34    /// Container name or ID
35    container: String,
36    /// Override the key sequence for detaching
37    detach_keys: Option<String>,
38    /// Do not attach STDIN
39    no_stdin: bool,
40    /// Proxy all received signals to the process
41    sig_proxy: bool,
42    /// Command executor
43    pub executor: CommandExecutor,
44}
45
46impl AttachCommand {
47    /// Create a new attach command
48    ///
49    /// # Example
50    ///
51    /// ```
52    /// use docker_wrapper::AttachCommand;
53    ///
54    /// let cmd = AttachCommand::new("my-container");
55    /// ```
56    #[must_use]
57    pub fn new(container: impl Into<String>) -> Self {
58        Self {
59            container: container.into(),
60            detach_keys: None,
61            no_stdin: false,
62            sig_proxy: true, // Docker default is true
63            executor: CommandExecutor::new(),
64        }
65    }
66
67    /// Override the key sequence for detaching a container
68    ///
69    /// # Example
70    ///
71    /// ```
72    /// use docker_wrapper::AttachCommand;
73    ///
74    /// let cmd = AttachCommand::new("my-container")
75    ///     .detach_keys("ctrl-a,ctrl-d");
76    /// ```
77    #[must_use]
78    pub fn detach_keys(mut self, keys: impl Into<String>) -> Self {
79        self.detach_keys = Some(keys.into());
80        self
81    }
82
83    /// Do not attach STDIN
84    ///
85    /// # Example
86    ///
87    /// ```
88    /// use docker_wrapper::AttachCommand;
89    ///
90    /// let cmd = AttachCommand::new("my-container")
91    ///     .no_stdin();
92    /// ```
93    #[must_use]
94    pub fn no_stdin(mut self) -> Self {
95        self.no_stdin = true;
96        self
97    }
98
99    /// Do not proxy signals
100    ///
101    /// # Example
102    ///
103    /// ```
104    /// use docker_wrapper::AttachCommand;
105    ///
106    /// let cmd = AttachCommand::new("my-container")
107    ///     .no_sig_proxy();
108    /// ```
109    #[must_use]
110    pub fn no_sig_proxy(mut self) -> Self {
111        self.sig_proxy = false;
112        self
113    }
114
115    /// Execute the attach command
116    ///
117    /// # Errors
118    /// Returns an error if:
119    /// - The Docker daemon is not running
120    /// - The container doesn't exist
121    /// - The container is not running
122    ///
123    /// # Example
124    ///
125    /// ```no_run
126    /// use docker_wrapper::AttachCommand;
127    ///
128    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
129    /// let result = AttachCommand::new("my-container")
130    ///     .run()
131    ///     .await?;
132    ///
133    /// if result.success() {
134    ///     println!("Successfully attached to container");
135    /// }
136    /// # Ok(())
137    /// # }
138    /// ```
139    pub async fn run(&self) -> Result<AttachResult> {
140        let output = self.execute().await?;
141        Ok(AttachResult {
142            output,
143            container: self.container.clone(),
144        })
145    }
146}
147
148#[async_trait]
149impl DockerCommand for AttachCommand {
150    type Output = CommandOutput;
151
152    fn get_executor(&self) -> &CommandExecutor {
153        &self.executor
154    }
155
156    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
157        &mut self.executor
158    }
159
160    fn build_command_args(&self) -> Vec<String> {
161        let mut args = vec!["attach".to_string()];
162
163        if let Some(ref keys) = self.detach_keys {
164            args.push("--detach-keys".to_string());
165            args.push(keys.clone());
166        }
167
168        if self.no_stdin {
169            args.push("--no-stdin".to_string());
170        }
171
172        if !self.sig_proxy {
173            args.push("--sig-proxy=false".to_string());
174        }
175
176        // Add container name/ID
177        args.push(self.container.clone());
178
179        // Add raw arguments from executor
180        args.extend(self.executor.raw_args.clone());
181
182        args
183    }
184
185    async fn execute(&self) -> Result<Self::Output> {
186        let args = self.build_command_args();
187        self.execute_command(args).await
188    }
189}
190
191/// Result from the attach command
192#[derive(Debug, Clone)]
193pub struct AttachResult {
194    /// Raw command output
195    pub output: CommandOutput,
196    /// Container that was attached to
197    pub container: String,
198}
199
200impl AttachResult {
201    /// Check if the attach was successful
202    #[must_use]
203    pub fn success(&self) -> bool {
204        self.output.success
205    }
206
207    /// Get the container name
208    #[must_use]
209    pub fn container(&self) -> &str {
210        &self.container
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn test_attach_basic() {
220        let cmd = AttachCommand::new("test-container");
221        let args = cmd.build_command_args();
222        assert_eq!(args, vec!["attach", "test-container"]);
223    }
224
225    #[test]
226    fn test_attach_with_detach_keys() {
227        let cmd = AttachCommand::new("test-container").detach_keys("ctrl-a,ctrl-d");
228        let args = cmd.build_command_args();
229        assert_eq!(
230            args,
231            vec!["attach", "--detach-keys", "ctrl-a,ctrl-d", "test-container"]
232        );
233    }
234
235    #[test]
236    fn test_attach_no_stdin() {
237        let cmd = AttachCommand::new("test-container").no_stdin();
238        let args = cmd.build_command_args();
239        assert_eq!(args, vec!["attach", "--no-stdin", "test-container"]);
240    }
241
242    #[test]
243    fn test_attach_no_sig_proxy() {
244        let cmd = AttachCommand::new("test-container").no_sig_proxy();
245        let args = cmd.build_command_args();
246        assert_eq!(args, vec!["attach", "--sig-proxy=false", "test-container"]);
247    }
248
249    #[test]
250    fn test_attach_all_options() {
251        let cmd = AttachCommand::new("test-container")
252            .detach_keys("ctrl-x,ctrl-y")
253            .no_stdin()
254            .no_sig_proxy();
255        let args = cmd.build_command_args();
256        assert_eq!(
257            args,
258            vec![
259                "attach",
260                "--detach-keys",
261                "ctrl-x,ctrl-y",
262                "--no-stdin",
263                "--sig-proxy=false",
264                "test-container"
265            ]
266        );
267    }
268}