docker_wrapper/command/swarm/
join_token.rs1use crate::command::{CommandExecutor, CommandOutput, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum SwarmNodeRole {
10 Worker,
12 Manager,
14}
15
16impl SwarmNodeRole {
17 #[must_use]
19 pub fn as_str(&self) -> &'static str {
20 match self {
21 Self::Worker => "worker",
22 Self::Manager => "manager",
23 }
24 }
25}
26
27impl std::fmt::Display for SwarmNodeRole {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 write!(f, "{}", self.as_str())
30 }
31}
32
33#[derive(Debug, Clone)]
35pub struct SwarmJoinTokenResult {
36 pub token: Option<String>,
38 pub join_command: Option<String>,
40 pub role: SwarmNodeRole,
42 pub output: String,
44}
45
46impl SwarmJoinTokenResult {
47 fn parse(output: &CommandOutput, role: SwarmNodeRole, quiet: bool) -> Self {
49 let stdout = output.stdout.trim();
50
51 if quiet {
52 Self {
54 token: Some(stdout.to_string()),
55 join_command: None,
56 role,
57 output: stdout.to_string(),
58 }
59 } else {
60 let mut token = None;
67 let mut join_command = None;
68
69 for line in stdout.lines() {
70 let trimmed = line.trim();
71 if trimmed.starts_with("docker swarm join") {
72 join_command = Some(trimmed.to_string());
73
74 let parts: Vec<&str> = trimmed.split_whitespace().collect();
76 for (i, part) in parts.iter().enumerate() {
77 if *part == "--token" {
78 if let Some(t) = parts.get(i + 1) {
79 token = Some((*t).to_string());
80 }
81 }
82 }
83 }
84 }
85
86 Self {
87 token,
88 join_command,
89 role,
90 output: stdout.to_string(),
91 }
92 }
93 }
94}
95
96#[derive(Debug, Clone)]
100pub struct SwarmJoinTokenCommand {
101 role: SwarmNodeRole,
103 quiet: bool,
105 rotate: bool,
107 pub executor: CommandExecutor,
109}
110
111impl SwarmJoinTokenCommand {
112 #[must_use]
114 pub fn new(role: SwarmNodeRole) -> Self {
115 Self {
116 role,
117 quiet: false,
118 rotate: false,
119 executor: CommandExecutor::default(),
120 }
121 }
122
123 #[must_use]
125 pub fn worker() -> Self {
126 Self::new(SwarmNodeRole::Worker)
127 }
128
129 #[must_use]
131 pub fn manager() -> Self {
132 Self::new(SwarmNodeRole::Manager)
133 }
134
135 #[must_use]
137 pub fn quiet(mut self) -> Self {
138 self.quiet = true;
139 self
140 }
141
142 #[must_use]
144 pub fn rotate(mut self) -> Self {
145 self.rotate = true;
146 self
147 }
148
149 fn build_args(&self) -> Vec<String> {
151 let mut args = vec!["swarm".to_string(), "join-token".to_string()];
152
153 if self.quiet {
154 args.push("--quiet".to_string());
155 }
156
157 if self.rotate {
158 args.push("--rotate".to_string());
159 }
160
161 args.push(self.role.as_str().to_string());
162
163 args
164 }
165}
166
167impl Default for SwarmJoinTokenCommand {
168 fn default() -> Self {
169 Self::worker()
170 }
171}
172
173#[async_trait]
174impl DockerCommand for SwarmJoinTokenCommand {
175 type Output = SwarmJoinTokenResult;
176
177 fn get_executor(&self) -> &CommandExecutor {
178 &self.executor
179 }
180
181 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
182 &mut self.executor
183 }
184
185 fn build_command_args(&self) -> Vec<String> {
186 self.build_args()
187 }
188
189 async fn execute(&self) -> Result<Self::Output> {
190 let args = self.build_args();
191 let output = self.execute_command(args).await?;
192 Ok(SwarmJoinTokenResult::parse(&output, self.role, self.quiet))
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_join_token_worker() {
202 let cmd = SwarmJoinTokenCommand::worker();
203 let args = cmd.build_args();
204 assert_eq!(args, vec!["swarm", "join-token", "worker"]);
205 }
206
207 #[test]
208 fn test_join_token_manager() {
209 let cmd = SwarmJoinTokenCommand::manager();
210 let args = cmd.build_args();
211 assert_eq!(args, vec!["swarm", "join-token", "manager"]);
212 }
213
214 #[test]
215 fn test_join_token_quiet() {
216 let cmd = SwarmJoinTokenCommand::worker().quiet();
217 let args = cmd.build_args();
218 assert!(args.contains(&"--quiet".to_string()));
219 assert!(args.contains(&"worker".to_string()));
220 }
221
222 #[test]
223 fn test_join_token_rotate() {
224 let cmd = SwarmJoinTokenCommand::manager().rotate();
225 let args = cmd.build_args();
226 assert!(args.contains(&"--rotate".to_string()));
227 assert!(args.contains(&"manager".to_string()));
228 }
229
230 #[test]
231 fn test_join_token_all_options() {
232 let cmd = SwarmJoinTokenCommand::worker().quiet().rotate();
233 let args = cmd.build_args();
234 assert_eq!(
235 args,
236 vec!["swarm", "join-token", "--quiet", "--rotate", "worker"]
237 );
238 }
239
240 #[test]
241 fn test_node_role_display() {
242 assert_eq!(SwarmNodeRole::Worker.to_string(), "worker");
243 assert_eq!(SwarmNodeRole::Manager.to_string(), "manager");
244 }
245}