docker_wrapper/command/builder/
create.rs

1//! Docker buildx create command implementation.
2
3use crate::command::{CommandExecutor, CommandOutput, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Result of buildx create command
8#[derive(Debug, Clone)]
9pub struct BuildxCreateResult {
10    /// The name of the created builder
11    pub name: String,
12    /// Raw output from the command
13    pub output: String,
14    /// Whether the command succeeded
15    pub success: bool,
16}
17
18impl BuildxCreateResult {
19    /// Parse the buildx create output
20    fn parse(output: &CommandOutput) -> Self {
21        // The output is typically just the builder name
22        let name = output.stdout.trim().to_string();
23        Self {
24            name,
25            output: output.stdout.clone(),
26            success: output.success,
27        }
28    }
29}
30
31/// Docker buildx create command builder
32///
33/// Creates a new builder instance for multi-platform builds.
34///
35/// # Example
36///
37/// ```rust,no_run
38/// use docker_wrapper::{DockerCommand, BuildxCreateCommand};
39///
40/// # #[tokio::main]
41/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
42/// let result = BuildxCreateCommand::new()
43///     .name("mybuilder")
44///     .driver("docker-container")
45///     .platform("linux/amd64")
46///     .platform("linux/arm64")
47///     .use_builder()
48///     .bootstrap()
49///     .execute()
50///     .await?;
51///
52/// println!("Created builder: {}", result.name);
53/// # Ok(())
54/// # }
55/// ```
56#[derive(Debug, Clone, Default)]
57#[allow(clippy::struct_excessive_bools)]
58pub struct BuildxCreateCommand {
59    /// Context or endpoint
60    context: Option<String>,
61    /// Append a node to builder instead of changing it
62    append: bool,
63    /// Boot builder after creation
64    bootstrap: bool,
65    /// `BuildKit` daemon config file
66    buildkitd_config: Option<String>,
67    /// `BuildKit` daemon flags
68    buildkitd_flags: Option<String>,
69    /// Driver to use
70    driver: Option<String>,
71    /// Options for the driver
72    driver_opts: Vec<String>,
73    /// Remove a node from builder instead of changing it
74    leave: bool,
75    /// Builder instance name
76    name: Option<String>,
77    /// Create/modify node with given name
78    node: Option<String>,
79    /// Fixed platforms for current node
80    platforms: Vec<String>,
81    /// Set the current builder instance
82    use_builder: bool,
83    /// Command executor
84    pub executor: CommandExecutor,
85}
86
87impl BuildxCreateCommand {
88    /// Create a new buildx create command
89    #[must_use]
90    pub fn new() -> Self {
91        Self::default()
92    }
93
94    /// Set the context or endpoint
95    #[must_use]
96    pub fn context(mut self, context: impl Into<String>) -> Self {
97        self.context = Some(context.into());
98        self
99    }
100
101    /// Append a node to builder instead of changing it
102    #[must_use]
103    pub fn append(mut self) -> Self {
104        self.append = true;
105        self
106    }
107
108    /// Boot builder after creation
109    #[must_use]
110    pub fn bootstrap(mut self) -> Self {
111        self.bootstrap = true;
112        self
113    }
114
115    /// Set the `BuildKit` daemon config file
116    #[must_use]
117    pub fn buildkitd_config(mut self, config: impl Into<String>) -> Self {
118        self.buildkitd_config = Some(config.into());
119        self
120    }
121
122    /// Set the `BuildKit` daemon flags
123    #[must_use]
124    pub fn buildkitd_flags(mut self, flags: impl Into<String>) -> Self {
125        self.buildkitd_flags = Some(flags.into());
126        self
127    }
128
129    /// Set the driver to use (docker-container, kubernetes, remote)
130    #[must_use]
131    pub fn driver(mut self, driver: impl Into<String>) -> Self {
132        self.driver = Some(driver.into());
133        self
134    }
135
136    /// Add a driver option
137    #[must_use]
138    pub fn driver_opt(mut self, opt: impl Into<String>) -> Self {
139        self.driver_opts.push(opt.into());
140        self
141    }
142
143    /// Remove a node from builder instead of changing it
144    #[must_use]
145    pub fn leave(mut self) -> Self {
146        self.leave = true;
147        self
148    }
149
150    /// Set the builder instance name
151    #[must_use]
152    pub fn name(mut self, name: impl Into<String>) -> Self {
153        self.name = Some(name.into());
154        self
155    }
156
157    /// Create/modify node with given name
158    #[must_use]
159    pub fn node(mut self, node: impl Into<String>) -> Self {
160        self.node = Some(node.into());
161        self
162    }
163
164    /// Add a fixed platform for current node
165    #[must_use]
166    pub fn platform(mut self, platform: impl Into<String>) -> Self {
167        self.platforms.push(platform.into());
168        self
169    }
170
171    /// Set the current builder instance after creation
172    #[must_use]
173    pub fn use_builder(mut self) -> Self {
174        self.use_builder = true;
175        self
176    }
177
178    /// Build the command arguments
179    fn build_args(&self) -> Vec<String> {
180        let mut args = vec!["buildx".to_string(), "create".to_string()];
181
182        if self.append {
183            args.push("--append".to_string());
184        }
185
186        if self.bootstrap {
187            args.push("--bootstrap".to_string());
188        }
189
190        if let Some(ref config) = self.buildkitd_config {
191            args.push("--buildkitd-config".to_string());
192            args.push(config.clone());
193        }
194
195        if let Some(ref flags) = self.buildkitd_flags {
196            args.push("--buildkitd-flags".to_string());
197            args.push(flags.clone());
198        }
199
200        if let Some(ref driver) = self.driver {
201            args.push("--driver".to_string());
202            args.push(driver.clone());
203        }
204
205        for opt in &self.driver_opts {
206            args.push("--driver-opt".to_string());
207            args.push(opt.clone());
208        }
209
210        if self.leave {
211            args.push("--leave".to_string());
212        }
213
214        if let Some(ref name) = self.name {
215            args.push("--name".to_string());
216            args.push(name.clone());
217        }
218
219        if let Some(ref node) = self.node {
220            args.push("--node".to_string());
221            args.push(node.clone());
222        }
223
224        for platform in &self.platforms {
225            args.push("--platform".to_string());
226            args.push(platform.clone());
227        }
228
229        if self.use_builder {
230            args.push("--use".to_string());
231        }
232
233        if let Some(ref context) = self.context {
234            args.push(context.clone());
235        }
236
237        args.extend(self.executor.raw_args.clone());
238
239        args
240    }
241}
242
243#[async_trait]
244impl DockerCommand for BuildxCreateCommand {
245    type Output = BuildxCreateResult;
246
247    fn get_executor(&self) -> &CommandExecutor {
248        &self.executor
249    }
250
251    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
252        &mut self.executor
253    }
254
255    fn build_command_args(&self) -> Vec<String> {
256        self.build_args()
257    }
258
259    async fn execute(&self) -> Result<Self::Output> {
260        let args = self.build_args();
261        let output = self.execute_command(args).await?;
262        Ok(BuildxCreateResult::parse(&output))
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269
270    #[test]
271    fn test_buildx_create_basic() {
272        let cmd = BuildxCreateCommand::new();
273        let args = cmd.build_args();
274        assert_eq!(args, vec!["buildx", "create"]);
275    }
276
277    #[test]
278    fn test_buildx_create_with_name() {
279        let cmd = BuildxCreateCommand::new().name("mybuilder");
280        let args = cmd.build_args();
281        assert!(args.contains(&"--name".to_string()));
282        assert!(args.contains(&"mybuilder".to_string()));
283    }
284
285    #[test]
286    fn test_buildx_create_with_driver() {
287        let cmd = BuildxCreateCommand::new().driver("docker-container");
288        let args = cmd.build_args();
289        assert!(args.contains(&"--driver".to_string()));
290        assert!(args.contains(&"docker-container".to_string()));
291    }
292
293    #[test]
294    fn test_buildx_create_with_platforms() {
295        let cmd = BuildxCreateCommand::new()
296            .platform("linux/amd64")
297            .platform("linux/arm64");
298        let args = cmd.build_args();
299        assert!(args.contains(&"--platform".to_string()));
300        assert!(args.contains(&"linux/amd64".to_string()));
301        assert!(args.contains(&"linux/arm64".to_string()));
302    }
303
304    #[test]
305    fn test_buildx_create_all_options() {
306        let cmd = BuildxCreateCommand::new()
307            .name("mybuilder")
308            .driver("docker-container")
309            .driver_opt("network=host")
310            .platform("linux/amd64")
311            .bootstrap()
312            .use_builder()
313            .append();
314        let args = cmd.build_args();
315        assert!(args.contains(&"--name".to_string()));
316        assert!(args.contains(&"--driver".to_string()));
317        assert!(args.contains(&"--driver-opt".to_string()));
318        assert!(args.contains(&"--platform".to_string()));
319        assert!(args.contains(&"--bootstrap".to_string()));
320        assert!(args.contains(&"--use".to_string()));
321        assert!(args.contains(&"--append".to_string()));
322    }
323}