docker_wrapper/compose/
attach.rs1use crate::compose::{ComposeCommandV2 as ComposeCommand, ComposeConfig};
4use crate::error::Result;
5use async_trait::async_trait;
6
7#[derive(Debug, Clone, Default)]
11pub struct ComposeAttachCommand {
12 pub config: ComposeConfig,
14 pub service: String,
16 pub detach_keys: Option<String>,
18 pub index: Option<u32>,
20 pub no_stdin: bool,
22 pub sig_proxy: bool,
24}
25
26#[derive(Debug, Clone)]
28pub struct AttachResult {
29 pub output: String,
31 pub success: bool,
33}
34
35impl ComposeAttachCommand {
36 #[must_use]
38 pub fn new(service: impl Into<String>) -> Self {
39 Self {
40 service: service.into(),
41 sig_proxy: true, ..Default::default()
43 }
44 }
45
46 #[must_use]
48 pub fn file<P: Into<std::path::PathBuf>>(mut self, file: P) -> Self {
49 self.config.files.push(file.into());
50 self
51 }
52
53 #[must_use]
55 pub fn project_name(mut self, name: impl Into<String>) -> Self {
56 self.config.project_name = Some(name.into());
57 self
58 }
59
60 #[must_use]
62 pub fn detach_keys(mut self, keys: impl Into<String>) -> Self {
63 self.detach_keys = Some(keys.into());
64 self
65 }
66
67 #[must_use]
69 pub fn index(mut self, index: u32) -> Self {
70 self.index = Some(index);
71 self
72 }
73
74 #[must_use]
76 pub fn no_stdin(mut self) -> Self {
77 self.no_stdin = true;
78 self
79 }
80
81 #[must_use]
83 pub fn no_sig_proxy(mut self) -> Self {
84 self.sig_proxy = false;
85 self
86 }
87
88 fn build_args(&self) -> Vec<String> {
89 let mut args = vec!["attach".to_string()];
90
91 if let Some(ref keys) = self.detach_keys {
93 args.push("--detach-keys".to_string());
94 args.push(keys.clone());
95 }
96
97 if let Some(index) = self.index {
98 args.push("--index".to_string());
99 args.push(index.to_string());
100 }
101
102 if self.no_stdin {
103 args.push("--no-stdin".to_string());
104 }
105
106 if !self.sig_proxy {
107 args.push("--sig-proxy=false".to_string());
108 }
109
110 args.push(self.service.clone());
112
113 args
114 }
115}
116
117#[async_trait]
118impl ComposeCommand for ComposeAttachCommand {
119 type Output = AttachResult;
120
121 fn get_config(&self) -> &ComposeConfig {
122 &self.config
123 }
124
125 fn get_config_mut(&mut self) -> &mut ComposeConfig {
126 &mut self.config
127 }
128
129 async fn execute_compose(&self, args: Vec<String>) -> Result<Self::Output> {
130 let output = self.execute_compose_command(args).await?;
131
132 Ok(AttachResult {
133 output: output.stdout,
134 success: output.success,
135 })
136 }
137
138 async fn execute(&self) -> Result<Self::Output> {
139 let args = self.build_args();
140 self.execute_compose(args).await
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_attach_command_basic() {
150 let cmd = ComposeAttachCommand::new("web");
151 let args = cmd.build_args();
152 assert_eq!(args[0], "attach");
153 assert!(args.contains(&"web".to_string()));
154 }
155
156 #[test]
157 fn test_attach_command_with_detach_keys() {
158 let cmd = ComposeAttachCommand::new("web").detach_keys("ctrl-p,ctrl-q");
159 let args = cmd.build_args();
160 assert!(args.contains(&"--detach-keys".to_string()));
161 assert!(args.contains(&"ctrl-p,ctrl-q".to_string()));
162 }
163
164 #[test]
165 fn test_attach_command_with_index() {
166 let cmd = ComposeAttachCommand::new("web").index(2).no_stdin();
167 let args = cmd.build_args();
168 assert!(args.contains(&"--index".to_string()));
169 assert!(args.contains(&"2".to_string()));
170 assert!(args.contains(&"--no-stdin".to_string()));
171 }
172
173 #[test]
174 fn test_attach_command_with_no_sig_proxy() {
175 let cmd = ComposeAttachCommand::new("worker").no_sig_proxy();
176 let args = cmd.build_args();
177 assert!(args.contains(&"--sig-proxy=false".to_string()));
178 }
179}