docker_wrapper/command/compose/
scale.rs1use crate::command::{CommandExecutor, ComposeCommand, ComposeConfig, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone)]
10pub struct ComposeScaleCommand {
11 pub executor: CommandExecutor,
13 pub config: ComposeConfig,
15 pub services: HashMap<String, u32>,
17 pub no_deps: bool,
19}
20
21#[derive(Debug, Clone)]
23pub struct ComposeScaleResult {
24 pub stdout: String,
26 pub stderr: String,
28 pub success: bool,
30 pub scaled_services: HashMap<String, u32>,
32}
33
34impl ComposeScaleCommand {
35 #[must_use]
37 pub fn new() -> Self {
38 Self {
39 executor: CommandExecutor::new(),
40 config: ComposeConfig::new(),
41 services: HashMap::new(),
42 no_deps: false,
43 }
44 }
45
46 #[must_use]
48 pub fn service(mut self, service: impl Into<String>, replicas: u32) -> Self {
49 self.services.insert(service.into(), replicas);
50 self
51 }
52
53 #[must_use]
55 pub fn services(mut self, services: HashMap<String, u32>) -> Self {
56 self.services.extend(services);
57 self
58 }
59
60 #[must_use]
62 pub fn no_deps(mut self) -> Self {
63 self.no_deps = true;
64 self
65 }
66}
67
68impl Default for ComposeScaleCommand {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74#[async_trait]
75impl DockerCommand for ComposeScaleCommand {
76 type Output = ComposeScaleResult;
77
78 fn get_executor(&self) -> &CommandExecutor {
79 &self.executor
80 }
81
82 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
83 &mut self.executor
84 }
85
86 fn build_command_args(&self) -> Vec<String> {
87 <Self as ComposeCommand>::build_command_args(self)
88 }
89
90 async fn execute(&self) -> Result<Self::Output> {
91 let args = <Self as ComposeCommand>::build_command_args(self);
92 let output = self.execute_command(args).await?;
93
94 Ok(ComposeScaleResult {
95 stdout: output.stdout,
96 stderr: output.stderr,
97 success: output.success,
98 scaled_services: self.services.clone(),
99 })
100 }
101}
102
103impl ComposeCommand for ComposeScaleCommand {
104 fn get_config(&self) -> &ComposeConfig {
105 &self.config
106 }
107
108 fn get_config_mut(&mut self) -> &mut ComposeConfig {
109 &mut self.config
110 }
111
112 fn subcommand(&self) -> &'static str {
113 "scale"
114 }
115
116 fn build_subcommand_args(&self) -> Vec<String> {
117 let mut args = Vec::new();
118
119 if self.no_deps {
120 args.push("--no-deps".to_string());
121 }
122
123 for (service, replicas) in &self.services {
124 args.push(format!("{service}={replicas}"));
125 }
126
127 args
128 }
129}
130
131impl ComposeScaleResult {
132 #[must_use]
134 pub fn success(&self) -> bool {
135 self.success
136 }
137
138 #[must_use]
140 pub fn scaled_services(&self) -> &HashMap<String, u32> {
141 &self.scaled_services
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_compose_scale_basic() {
151 let cmd = ComposeScaleCommand::new().service("web", 3);
152 let args = cmd.build_subcommand_args();
153 assert!(args.contains(&"web=3".to_string()));
154
155 let full_args = ComposeCommand::build_command_args(&cmd);
156 assert_eq!(full_args[0], "compose");
157 assert!(full_args.contains(&"scale".to_string()));
158 }
159
160 #[test]
161 fn test_compose_scale_multiple_services() {
162 let cmd = ComposeScaleCommand::new()
163 .service("web", 3)
164 .service("worker", 2);
165
166 let args = cmd.build_subcommand_args();
167 assert!(args.contains(&"web=3".to_string()));
168 assert!(args.contains(&"worker=2".to_string()));
169 }
170
171 #[test]
172 fn test_compose_scale_no_deps() {
173 let cmd = ComposeScaleCommand::new().service("api", 5).no_deps();
174
175 let args = cmd.build_subcommand_args();
176 assert!(args.contains(&"--no-deps".to_string()));
177 assert!(args.contains(&"api=5".to_string()));
178 }
179}