docker_wrapper/command/
compose_attach.rs1use super::{CommandExecutor, ComposeCommand, ComposeConfig, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7#[derive(Debug, Clone, Default)]
11pub struct ComposeAttachCommand {
12 pub executor: CommandExecutor,
14 pub config: ComposeConfig,
16 pub service: String,
18 pub detach_keys: Option<String>,
20 pub index: Option<u32>,
22 pub no_stdin: bool,
24 pub sig_proxy: bool,
26}
27
28#[derive(Debug, Clone)]
30pub struct AttachResult {
31 pub output: String,
33 pub success: bool,
35}
36
37impl ComposeAttachCommand {
38 #[must_use]
40 pub fn new(service: impl Into<String>) -> Self {
41 Self {
42 executor: CommandExecutor::new(),
43 config: ComposeConfig::new(),
44 service: service.into(),
45 sig_proxy: true, ..Default::default()
47 }
48 }
49
50 #[must_use]
52 pub fn detach_keys(mut self, keys: impl Into<String>) -> Self {
53 self.detach_keys = Some(keys.into());
54 self
55 }
56
57 #[must_use]
59 pub fn index(mut self, index: u32) -> Self {
60 self.index = Some(index);
61 self
62 }
63
64 #[must_use]
66 pub fn no_stdin(mut self) -> Self {
67 self.no_stdin = true;
68 self
69 }
70
71 #[must_use]
73 pub fn no_sig_proxy(mut self) -> Self {
74 self.sig_proxy = false;
75 self
76 }
77}
78
79#[async_trait]
80impl DockerCommand for ComposeAttachCommand {
81 type Output = AttachResult;
82
83 fn get_executor(&self) -> &CommandExecutor {
84 &self.executor
85 }
86
87 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
88 &mut self.executor
89 }
90
91 fn build_command_args(&self) -> Vec<String> {
92 <Self as ComposeCommand>::build_command_args(self)
94 }
95
96 async fn execute(&self) -> Result<Self::Output> {
97 let args = <Self as ComposeCommand>::build_command_args(self);
98 let output = self.execute_command(args).await?;
99
100 Ok(AttachResult {
101 output: output.stdout,
102 success: output.success,
103 })
104 }
105}
106
107impl ComposeCommand for ComposeAttachCommand {
108 fn get_config(&self) -> &ComposeConfig {
109 &self.config
110 }
111
112 fn get_config_mut(&mut self) -> &mut ComposeConfig {
113 &mut self.config
114 }
115
116 fn subcommand(&self) -> &'static str {
117 "attach"
118 }
119
120 fn build_subcommand_args(&self) -> Vec<String> {
121 let mut args = Vec::new();
122
123 if let Some(ref keys) = self.detach_keys {
125 args.push("--detach-keys".to_string());
126 args.push(keys.clone());
127 }
128
129 if let Some(index) = self.index {
130 args.push("--index".to_string());
131 args.push(index.to_string());
132 }
133
134 if self.no_stdin {
135 args.push("--no-stdin".to_string());
136 }
137
138 if !self.sig_proxy {
139 args.push("--sig-proxy=false".to_string());
140 }
141
142 args.push(self.service.clone());
144
145 args
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_attach_command_basic() {
155 let cmd = ComposeAttachCommand::new("web");
156 let args = cmd.build_subcommand_args();
157 assert!(args.contains(&"web".to_string()));
158
159 let full_args = ComposeCommand::build_command_args(&cmd);
160 assert_eq!(full_args[0], "compose");
161 assert!(full_args.contains(&"attach".to_string()));
162 assert!(full_args.contains(&"web".to_string()));
163 }
164
165 #[test]
166 fn test_attach_command_with_detach_keys() {
167 let cmd = ComposeAttachCommand::new("web").detach_keys("ctrl-p,ctrl-q");
168 let args = cmd.build_subcommand_args();
169 assert!(args.contains(&"--detach-keys".to_string()));
170 assert!(args.contains(&"ctrl-p,ctrl-q".to_string()));
171 }
172
173 #[test]
174 fn test_attach_command_with_index() {
175 let cmd = ComposeAttachCommand::new("web").index(2).no_stdin();
176 let args = cmd.build_subcommand_args();
177 assert!(args.contains(&"--index".to_string()));
178 assert!(args.contains(&"2".to_string()));
179 assert!(args.contains(&"--no-stdin".to_string()));
180 }
181
182 #[test]
183 fn test_attach_command_with_no_sig_proxy() {
184 let cmd = ComposeAttachCommand::new("worker").no_sig_proxy();
185 let args = cmd.build_subcommand_args();
186 assert!(args.contains(&"--sig-proxy=false".to_string()));
187 }
188
189 #[test]
190 fn test_compose_config_integration() {
191 let cmd = ComposeAttachCommand::new("web")
192 .file("docker-compose.yml")
193 .project_name("my-project");
194
195 let args = ComposeCommand::build_command_args(&cmd);
196 assert!(args.contains(&"--file".to_string()));
197 assert!(args.contains(&"docker-compose.yml".to_string()));
198 assert!(args.contains(&"--project-name".to_string()));
199 assert!(args.contains(&"my-project".to_string()));
200 }
201}