docker_wrapper/command/builder/
build.rs1use crate::command::build::{BuildCommand, BuildOutput};
6use crate::command::{CommandExecutor, DockerCommand};
7use crate::error::Result;
8use async_trait::async_trait;
9
10#[derive(Debug, Clone)]
34pub struct BuilderBuildCommand {
35 inner: BuildCommand,
37}
38
39impl BuilderBuildCommand {
40 pub fn new(context: impl Into<String>) -> Self {
45 Self {
46 inner: BuildCommand::new(context),
47 }
48 }
49
50 #[must_use]
52 pub fn dockerfile(mut self, path: impl Into<String>) -> Self {
53 self.inner = self.inner.file(path.into());
54 self
55 }
56
57 #[must_use]
59 pub fn tag(mut self, tag: impl Into<String>) -> Self {
60 self.inner = self.inner.tag(tag);
61 self
62 }
63
64 #[must_use]
66 pub fn no_cache(mut self) -> Self {
67 self.inner = self.inner.no_cache();
68 self
69 }
70
71 #[must_use]
73 pub fn build_arg(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
74 self.inner = self.inner.build_arg(key, value);
75 self
76 }
77
78 #[must_use]
80 pub fn target(mut self, target: impl Into<String>) -> Self {
81 self.inner = self.inner.target(target);
82 self
83 }
84
85 #[must_use]
87 pub fn platform(mut self, platform: impl Into<String>) -> Self {
88 self.inner = self.inner.platform(platform);
89 self
90 }
91
92 #[must_use]
94 pub fn buildkit(mut self) -> Self {
95 self.inner
98 .executor
99 .raw_args
100 .push("DOCKER_BUILDKIT=1".to_string());
101 self
102 }
103
104 #[must_use]
106 pub fn quiet(mut self) -> Self {
107 self.inner = self.inner.quiet();
108 self
109 }
110
111 #[must_use]
113 pub fn force_rm(mut self) -> Self {
114 self.inner = self.inner.force_rm();
115 self
116 }
117
118 #[must_use]
120 pub fn rm(self) -> Self {
121 self
123 }
124
125 #[must_use]
127 pub fn no_rm(mut self) -> Self {
128 self.inner = self.inner.no_rm();
129 self
130 }
131
132 #[must_use]
134 pub fn pull(mut self) -> Self {
135 self.inner = self.inner.pull();
136 self
137 }
138}
139
140#[async_trait]
141impl DockerCommand for BuilderBuildCommand {
142 type Output = BuildOutput;
143
144 fn get_executor(&self) -> &CommandExecutor {
145 &self.inner.executor
146 }
147
148 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
149 &mut self.inner.executor
150 }
151
152 fn build_command_args(&self) -> Vec<String> {
153 let mut inner_args = self.inner.build_command_args();
155
156 if !inner_args.is_empty() && inner_args[0] == "build" {
158 inner_args[0] = "builder".to_string();
159 inner_args.insert(1, "build".to_string());
160 }
161
162 inner_args
163 }
164
165 async fn execute(&self) -> Result<Self::Output> {
166 let args = self.build_command_args();
168 let output = self.inner.executor.execute_command("docker", args).await?;
169
170 let image_id = extract_image_id(&output.stdout);
172
173 Ok(BuildOutput {
174 stdout: output.stdout,
175 stderr: output.stderr,
176 exit_code: output.exit_code,
177 image_id,
178 })
179 }
180}
181
182fn extract_image_id(stdout: &str) -> Option<String> {
184 for line in stdout.lines().rev() {
186 if line.contains("Successfully built") {
187 return line.split_whitespace().last().map(String::from);
188 }
189 if line.contains("writing image sha256:") {
190 if let Some(id) = line.split("sha256:").nth(1) {
191 return Some(format!(
192 "sha256:{}",
193 id.split_whitespace()
194 .next()?
195 .trim_end_matches('"')
196 .trim_end_matches('}')
197 ));
198 }
199 }
200 }
201 None
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_builder_build_basic() {
210 let cmd = BuilderBuildCommand::new(".");
211 let args = cmd.build_command_args();
212 assert_eq!(&args[0..2], &["builder", "build"]);
213 assert!(args.contains(&".".to_string()));
214 }
215
216 #[test]
217 fn test_builder_build_with_options() {
218 let cmd = BuilderBuildCommand::new("/app")
219 .tag("myapp:latest")
220 .dockerfile("custom.Dockerfile")
221 .no_cache()
222 .build_arg("VERSION", "1.0");
223
224 let args = cmd.build_command_args();
225 assert_eq!(&args[0..2], &["builder", "build"]);
226 assert!(args.contains(&"--tag".to_string()));
227 assert!(args.contains(&"myapp:latest".to_string()));
228 assert!(args.contains(&"--file".to_string()));
229 assert!(args.contains(&"custom.Dockerfile".to_string()));
230 assert!(args.contains(&"--no-cache".to_string()));
231 assert!(args.contains(&"--build-arg".to_string()));
232 assert!(args.contains(&"VERSION=1.0".to_string()));
233 }
234
235 #[test]
236 fn test_builder_build_buildkit() {
237 let mut cmd = BuilderBuildCommand::new(".");
238 cmd = cmd.buildkit();
239
240 assert!(cmd
242 .inner
243 .executor
244 .raw_args
245 .contains(&"DOCKER_BUILDKIT=1".to_string()));
246 }
247
248 #[test]
249 fn test_builder_build_platform() {
250 let cmd = BuilderBuildCommand::new(".")
251 .platform("linux/amd64")
252 .target("production");
253
254 let args = cmd.build_command_args();
255 assert!(args.contains(&"--platform".to_string()));
256 assert!(args.contains(&"linux/amd64".to_string()));
257 assert!(args.contains(&"--target".to_string()));
258 assert!(args.contains(&"production".to_string()));
259 }
260
261 #[test]
262 fn test_builder_build_extensibility() {
263 let mut cmd = BuilderBuildCommand::new(".");
264 cmd.inner
265 .executor
266 .raw_args
267 .push("--custom-flag".to_string());
268
269 let args = cmd.build_command_args();
270 assert!(args.contains(&"--custom-flag".to_string()));
271 }
272}