docker_wrapper/compose/
scale.rs1use crate::compose::{ComposeCommandV2 as ComposeCommand, ComposeConfig};
4use crate::error::Result;
5use async_trait::async_trait;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Default)]
12pub struct ComposeScaleCommand {
13 pub config: ComposeConfig,
15 pub scales: HashMap<String, u32>,
17 pub no_deps: bool,
19}
20
21#[derive(Debug, Clone)]
23pub struct ScaleResult {
24 pub output: String,
26 pub success: bool,
28}
29
30impl ComposeScaleCommand {
31 #[must_use]
33 pub fn new() -> Self {
34 Self::default()
35 }
36
37 #[must_use]
39 pub fn file<P: Into<std::path::PathBuf>>(mut self, file: P) -> Self {
40 self.config.files.push(file.into());
41 self
42 }
43
44 #[must_use]
46 pub fn project_name(mut self, name: impl Into<String>) -> Self {
47 self.config.project_name = Some(name.into());
48 self
49 }
50
51 #[must_use]
53 pub fn scale(mut self, service: impl Into<String>, instances: u32) -> Self {
54 self.scales.insert(service.into(), instances);
55 self
56 }
57
58 #[must_use]
60 pub fn scales<I, S>(mut self, scales: I) -> Self
61 where
62 I: IntoIterator<Item = (S, u32)>,
63 S: Into<String>,
64 {
65 for (service, count) in scales {
66 self.scales.insert(service.into(), count);
67 }
68 self
69 }
70
71 #[must_use]
73 pub fn no_deps(mut self) -> Self {
74 self.no_deps = true;
75 self
76 }
77
78 fn build_args(&self) -> Vec<String> {
79 let mut args = vec!["scale".to_string()];
80
81 if self.no_deps {
83 args.push("--no-deps".to_string());
84 }
85
86 for (service, count) in &self.scales {
88 args.push(format!("{service}={count}"));
89 }
90
91 args
92 }
93}
94
95#[async_trait]
96impl ComposeCommand for ComposeScaleCommand {
97 type Output = ScaleResult;
98
99 fn get_config(&self) -> &ComposeConfig {
100 &self.config
101 }
102
103 fn get_config_mut(&mut self) -> &mut ComposeConfig {
104 &mut self.config
105 }
106
107 async fn execute_compose(&self, args: Vec<String>) -> Result<Self::Output> {
108 let output = self.execute_compose_command(args).await?;
109
110 Ok(ScaleResult {
111 output: output.stdout,
112 success: output.success,
113 })
114 }
115
116 async fn execute(&self) -> Result<Self::Output> {
117 let args = self.build_args();
118 self.execute_compose(args).await
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_scale_command_basic() {
128 let cmd = ComposeScaleCommand::new();
129 let args = cmd.build_args();
130 assert_eq!(args[0], "scale");
131 }
132
133 #[test]
134 fn test_scale_command_with_service() {
135 let cmd = ComposeScaleCommand::new()
136 .scale("web", 3)
137 .scale("worker", 5);
138 let args = cmd.build_args();
139 assert!(args.iter().any(|arg| arg == "web=3" || arg == "worker=5"));
140 }
141
142 #[test]
143 fn test_scale_command_with_no_deps() {
144 let cmd = ComposeScaleCommand::new().scale("web", 2).no_deps();
145 let args = cmd.build_args();
146 assert!(args.contains(&"--no-deps".to_string()));
147 assert!(args.iter().any(|arg| arg == "web=2"));
148 }
149
150 #[test]
151 fn test_scale_command_with_multiple() {
152 let scales = vec![("app", 4), ("cache", 2)];
153 let cmd = ComposeScaleCommand::new().scales(scales);
154 let args = cmd.build_args();
155 assert!(args.iter().any(|arg| arg == "app=4" || arg == "cache=2"));
156 }
157}