dockerfile_builder/instruction_builder.rs
1//! # Type-safe interfaces for building Instructions
2//!
3//!
4//! This module provides the definition of Instruction Builders and their fields, following the
5//! [Dockerfile reference].
6//!
7//! [Dockerfile reference]: https://docs.docker.com/engine/reference/builder/
8//!
9//! ## Usage
10//!
11//! All build and setter methods for Instruction Builders are automatically generated and follow the same format.
12//!
13//! For example:
14//!
15//! `ExposeBuilder` is the builder struct for `Expose`.
16//!
17//! ```rust
18//! pub struct ExposeBuilder {
19//! pub port: u16,
20//! pub protocol: Option<String>,
21//! }
22//! ```
23//!
24//! `Expose` can be constructed as follow:
25//!
26//! ```rust
27//! # use dockerfile_builder::instruction_builder::{ExposeBuilder, PortProtocol};
28//! let expose = ExposeBuilder::builder()
29//! .port(80)
30//! .protocol(PortProtocol::Tcp)
31//! .build()
32//! .unwrap();
33//! ```
34//!
35//! Note that:
36//! * The setter method names are identical to the fields names.
37//! * For fields with `Option<inner_type>` type: The argument type is the inner_type. It is
38//! optional to set these fields.
39//! * Use `build()` to complete building the instruction. `build()` returns
40//! `Result<InstructionBuilder, std::err::Err>` to safely handle errors.
41//!
42//!
43//! For fields with `Vec<_>` or `Option<Vec<_>>` type, it is possible to set each element of the Vec.
44//!
45//! For example:
46//!
47//! `RunBuilder` is the builder struct for `Run`.
48//!
49//! ```
50//! pub struct RunBuilder {
51//! pub commands: Vec<String>,
52//! }
53//! ```
54//!
55//! `Run` can be constructed as follow:
56//! ```
57//! # use dockerfile_builder::instruction_builder::RunBuilder;
58//! let run = RunBuilder::builder()
59//! .command("source $HOME/.bashrc")
60//! .command("echo $HOME")
61//! .build()
62//! .unwrap();
63//! assert_eq!(
64//! run.to_string(),
65//! "RUN source $HOME/.bashrc && echo $HOME",
66//! );
67//! ```
68//!
69
70use std::time::Duration;
71
72use crate::instruction::{
73 Instruction, ADD, ARG, CMD, COPY, ENTRYPOINT, ENV, EXPOSE, FROM, HEALTHCHECK, LABEL, ONBUILD,
74 RUN, SHELL, STOPSIGNAL, USER, VOLUME, WORKDIR,
75};
76use anyhow::{anyhow, Result};
77use dockerfile_builder_macros::InstructionBuilder;
78use url::Url;
79
80/// Builder struct for [`FROM`] instruction
81///
82/// Format according to [Dockerfile
83/// reference](https://docs.docker.com/engine/reference/builder/#from):
84/// * `FROM [--platform=<platform>] <image> [AS <name>]`
85/// or
86/// * `FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]`
87/// or
88/// * `FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]`
89///
90/// Example:
91/// ```
92/// # use dockerfile_builder::instruction_builder::FromBuilder;
93/// // Build FROM with image and name
94/// let from = FromBuilder::builder()
95/// .image("cargo-chef")
96/// .name("chef")
97/// .build()
98/// .unwrap();
99/// assert_eq!(from.to_string(), "FROM cargo-chef AS chef");
100///
101/// // Build FROM with image, name, and tag
102/// let from = FromBuilder::builder()
103/// .image("cargo-chef")
104/// .tag("latest")
105/// .name("chef")
106/// .build()
107/// .unwrap();
108/// assert_eq!(from.to_string(), "FROM cargo-chef:latest AS chef");
109/// ```
110///
111/// [FROM]: dockerfile_builder::instruction::FROM
112#[derive(Debug, InstructionBuilder)]
113#[instruction_builder(
114 instruction_name = FROM,
115 value_method = value,
116)]
117pub struct FromBuilder {
118 pub image: String,
119 pub name: Option<String>,
120 pub tag: Option<String>,
121 pub digest: Option<String>,
122 pub platform: Option<String>,
123}
124
125impl FromBuilder {
126 fn value(&self) -> Result<String> {
127 if self.tag.is_some() && self.digest.is_some() {
128 return Err(anyhow!("Dockerfile image can only have tag OR digest"));
129 }
130
131 let tag_or_digest = if let Some(t) = &self.tag {
132 Some(format!(":{}", t))
133 } else {
134 self.digest.as_ref().map(|d| format!("@{}", d))
135 };
136
137 Ok(format!(
138 "{}{}{}{}",
139 self.platform
140 .as_ref()
141 .map(|s| format!("--platform={} ", s))
142 .unwrap_or_default(),
143 &self.image,
144 tag_or_digest
145 .as_ref()
146 .map(|s| s.to_string())
147 .unwrap_or_default(),
148 self.name
149 .as_ref()
150 .map(|s| format!(" AS {}", s))
151 .unwrap_or_default(),
152 ))
153 }
154}
155
156/// Builder struct for [`ENV`] instruction
157///
158/// Format according to [Dockerfile
159/// reference](https://docs.docker.com/engine/reference/builder/#env):
160/// * `ENV <key>=<value>`
161///
162/// Example:
163/// ```
164/// # use dockerfile_builder::instruction_builder::EnvBuilder;
165/// let env = EnvBuilder::builder()
166/// .key("foo")
167/// .value("bar")
168/// .build()
169/// .unwrap();
170/// assert_eq!(env.to_string(), "ENV foo=\"bar\"");
171/// ```
172///
173/// [ENV]: dockerfile_builder::instruction::ENV
174#[derive(Debug, InstructionBuilder)]
175#[instruction_builder(
176 instruction_name = ENV,
177 value_method = value,
178)]
179pub struct EnvBuilder {
180 pub key: String,
181 pub value: String,
182}
183
184impl EnvBuilder {
185 fn value(&self) -> Result<String> {
186 Ok(format!("{}=\"{}\"", self.key, self.value))
187 }
188}
189
190/// Builder struct for [`RUN`] instruction (shell form)
191///
192/// * `RunBuilder` constructs the shell form for [`RUN`] by default.
193/// To construct the exec form of `RUN`, use [`RunExecBuilder`].
194///
195/// Format according to [Dockerfile
196/// reference](https://docs.docker.com/engine/reference/builder/#run):
197/// * `RUN command`
198///
199/// Example:
200/// ```
201/// # use dockerfile_builder::instruction_builder::RunBuilder;
202/// // build RUN with a single command
203/// let run = RunBuilder::builder()
204/// .command("source $HOME/.bashrc")
205/// .build().unwrap();
206/// assert_eq!(run.to_string(), "RUN source $HOME/.bashrc");
207///
208/// // build RUN with multiple commands, commands are separated by ` && `
209/// let run = RunBuilder::builder()
210/// .command("source $HOME/.bashrc")
211/// .command("echo $HOME")
212/// .build().unwrap();
213/// assert_eq!(
214/// run.to_string(),
215/// "RUN source $HOME/.bashrc && echo $HOME",
216/// );
217///
218/// // build RUN with multiple commands using a Vec
219/// let run = RunBuilder::builder()
220/// .commands(vec!["source $HOME/.bashrc", "echo $HOME"])
221/// .build().unwrap();
222/// assert_eq!(
223/// run.to_string(),
224/// "RUN source $HOME/.bashrc && echo $HOME",
225/// );
226/// ```
227///
228/// [RUN]: dockerfile_builder::instruction::RUN
229// TODO: Flag options for RUN
230#[derive(Debug, InstructionBuilder)]
231#[instruction_builder(
232 instruction_name = RUN,
233 value_method = value,
234)]
235pub struct RunBuilder {
236 #[instruction_builder(each = command)]
237 pub commands: Vec<String>,
238}
239
240impl RunBuilder {
241 fn value(&self) -> Result<String> {
242 Ok(self.commands.join(" && "))
243 }
244}
245
246/// Builder struct for [`RUN`] instruction (exec form)
247///
248/// * RunBuilder constructs the exec form for [`RUN`].
249/// To construct the shell form, use [`RunBuilder`].
250///
251/// Format according to [Dockerfile
252/// reference](https://docs.docker.com/engine/reference/builder/#run):
253/// * `RUN ["executable", "param1", "param2"]`
254///
255/// Example:
256/// ```
257/// # use dockerfile_builder::instruction_builder::RunExecBuilder;
258/// // build RUN by adding multiple params
259/// let run = RunExecBuilder::builder()
260/// .executable("mybin.exe")
261/// .param("-f")
262/// .param("-c")
263/// .build().unwrap();
264/// assert_eq!(run.to_string(), r#"RUN ["mybin.exe", "-f", "-c"]"#);
265///
266/// // build RUN with multiple params using a vec
267/// let run = RunExecBuilder::builder()
268/// .executable("mybin.exe")
269/// .params(vec!["-f", "-c"])
270/// .build().unwrap();
271/// assert_eq!(run.to_string(), r#"RUN ["mybin.exe", "-f", "-c"]"#);
272/// ```
273///
274/// [RUN]: dockerfile_builder::instruction::RUN
275#[derive(Debug, InstructionBuilder)]
276#[instruction_builder(
277 instruction_name = RUN,
278 value_method = value,
279)]
280pub struct RunExecBuilder {
281 pub executable: String,
282 #[instruction_builder(each = param)]
283 pub params: Option<Vec<String>>,
284}
285
286impl RunExecBuilder {
287 fn value(&self) -> Result<String> {
288 let params = match self.params.clone() {
289 Some(param_vec) => {
290 if param_vec.is_empty() {
291 String::new()
292 } else {
293 format!(r#", "{}""#, param_vec.join(r#"", ""#))
294 }
295 }
296 None => String::new(),
297 };
298 Ok(format!(r#"["{}"{}]"#, self.executable, params))
299 }
300}
301
302/// Builder struct for [`CMD`] instruction (shell form)
303///
304/// * CmdBuilder constructs the shell form for [`CMD`] by default.
305/// To construct the exec form or CMD in combination with ENTRYPOINT, use [`CmdExecBuilder`].
306///
307/// Format according to [Dockerfile
308/// reference](https://docs.docker.com/engine/reference/builder/#cmd):
309/// * `CMD command param1 param2`
310///
311/// Example:
312/// ```
313/// # use dockerfile_builder::instruction_builder::CmdBuilder;
314/// // build CMD by adding multiple params
315/// let cmd = CmdBuilder::builder()
316/// .command(r#"echo "this is a test""#)
317/// .param("| wc")
318/// .param("-l")
319/// .build().unwrap();
320/// assert_eq!(cmd.to_string(), r#"CMD echo "this is a test" | wc -l"#);
321///
322/// // build CMD with multiple params using a vec
323/// let cmd = CmdBuilder::builder()
324/// .command(r#"echo "this is a test""#)
325/// .params(vec!["| wc", "-l"])
326/// .build().unwrap();
327/// assert_eq!(cmd.to_string(), r#"CMD echo "this is a test" | wc -l"#);
328/// ```
329///
330/// [CMD]: dockerfile_builder::instruction::CMD
331#[derive(Debug, InstructionBuilder)]
332#[instruction_builder(
333 instruction_name = CMD,
334 value_method = value,
335)]
336pub struct CmdBuilder {
337 pub command: String,
338 #[instruction_builder(each = param)]
339 pub params: Option<Vec<String>>,
340}
341
342impl CmdBuilder {
343 fn value(&self) -> Result<String> {
344 let params = match self.params.clone() {
345 Some(param_vec) => {
346 if param_vec.is_empty() {
347 String::new()
348 } else {
349 format!(r#" {}"#, param_vec.join(" "))
350 }
351 }
352 None => String::new(),
353 };
354 Ok(format!("{}{}", self.command, params))
355 }
356}
357
358/// Builder struct for [`CMD`] instruction (exec form)
359///
360/// * CmdBuilder constructs the exec form for [`CMD`].
361/// To construct the shell form, use [`CmdBuilder`].
362///
363/// Format according to [Dockerfile
364/// reference](https://docs.docker.com/engine/reference/builder/#cmd):
365/// * `CMD ["executable", "param1", "param2"]`
366/// OR
367/// * `CMD ["param1","param2"]` (as default parameters to ENTRYPOINT)
368///
369/// Example:
370/// ```
371/// # use dockerfile_builder::instruction_builder::CmdExecBuilder;
372/// // build CMD with a single param
373/// let cmd = CmdExecBuilder::builder()
374/// .executable("/usr/bin/wc")
375/// .param("--help")
376/// .build().unwrap();
377/// assert_eq!(cmd.to_string(), r#"CMD ["/usr/bin/wc", "--help"]"#);
378///
379/// // build CMD for ENTRYPOINT
380/// let cmd = CmdExecBuilder::builder()
381/// .param("-l")
382/// .param("8000")
383/// .build().unwrap();
384/// assert_eq!(cmd.to_string(), r#"CMD ["-l", "8000"]"#);
385/// ```
386///
387/// [CMD]: dockerfile_builder::instruction::CMD
388#[derive(Debug, InstructionBuilder)]
389#[instruction_builder(
390 instruction_name = CMD,
391 value_method = value,
392)]
393pub struct CmdExecBuilder {
394 pub executable: Option<String>,
395 #[instruction_builder(each = param)]
396 pub params: Option<Vec<String>>,
397}
398
399impl CmdExecBuilder {
400 fn value(&self) -> Result<String> {
401 if self.executable.is_none() && self.params.is_none() {
402 return Err(anyhow!("CMD cannot be empty"));
403 }
404 let params = match self.params.clone() {
405 Some(param_vec) => {
406 if self.executable.is_none() && param_vec.is_empty() {
407 return Err(anyhow!("CMD cannot be empty"));
408 } else if param_vec.is_empty() {
409 String::new()
410 } else if self.executable.is_none() {
411 format!(r#""{}""#, param_vec.join(r#"", ""#))
412 } else {
413 format!(r#", "{}""#, param_vec.join(r#"", ""#))
414 }
415 }
416 None => String::new(),
417 };
418 Ok(format!(
419 r#"[{}{}]"#,
420 self.executable
421 .as_ref()
422 .map(|e| format!(r#""{}""#, e))
423 .unwrap_or_default(),
424 params,
425 ))
426 }
427}
428
429/// Builder struct for [`LABEL`] instruction
430///
431/// Format according to [Dockerfile
432/// reference](https://docs.docker.com/engine/reference/builder/#label):
433/// * `LABEL <key>=<value>`
434///
435/// Example:
436/// ```
437/// # use dockerfile_builder::instruction_builder::LabelBuilder;
438/// let label = LabelBuilder::builder()
439/// .key("foo")
440/// .value("bar")
441/// .build()
442/// .unwrap();
443/// assert_eq!(label.to_string(), "LABEL foo=\"bar\"");
444/// ```
445///
446/// [LABEL]: dockerfile_builder::instruction::LABEL
447///
448// TODO: The official format is
449// * `LABEL <key>=<value> <key>=<value> <key>=<value> ...`
450// Use `each` to support the multiple format.
451#[derive(Debug, InstructionBuilder)]
452#[instruction_builder(
453 instruction_name = LABEL,
454 value_method = value,
455)]
456pub struct LabelBuilder {
457 pub key: String,
458 pub value: String,
459}
460
461impl LabelBuilder {
462 fn value(&self) -> Result<String> {
463 Ok(format!("{}=\"{}\"", self.key, self.value))
464 }
465}
466
467/// Builder struct for [`EXPOSE`] instruction
468///
469/// Format according to [Dockerfile
470/// reference](https://docs.docker.com/engine/reference/builder/#expose):
471/// * `EXPOSE <port>`
472/// or
473/// * `EXPOSE <port>/<protocol>`
474///
475/// Example:
476/// ```
477/// # use dockerfile_builder::instruction_builder::{ExposeBuilder, PortProtocol};
478/// let expose = ExposeBuilder::builder()
479/// .port(80)
480/// .protocol(PortProtocol::Udp)
481/// .build()
482/// .unwrap();
483/// assert_eq!(expose.to_string(), "EXPOSE 80/udp");
484/// ```
485///
486/// [EXPOSE]: dockerfile_builder::instruction::EXPOSE
487#[derive(Debug, InstructionBuilder)]
488#[instruction_builder(
489 instruction_name = EXPOSE,
490 value_method = value,
491)]
492pub struct ExposeBuilder {
493 pub port: u16,
494 pub protocol: Option<PortProtocol>,
495}
496
497#[derive(Clone, Debug)]
498pub enum PortProtocol {
499 Tcp,
500 Udp,
501}
502
503impl ToString for PortProtocol {
504 fn to_string(&self) -> String {
505 match self {
506 Self::Tcp => "tcp",
507 Self::Udp => "udp",
508 }
509 .to_string()
510 }
511}
512
513impl ExposeBuilder {
514 fn value(&self) -> Result<String> {
515 Ok(format!(
516 "{}{}",
517 self.port,
518 self.protocol
519 .as_ref()
520 .map(|p| format!("/{}", p.to_string()))
521 .unwrap_or_default()
522 ))
523 }
524}
525
526/// Builder struct for [`ADD`] instruction
527///
528/// Format according to [Dockerfile
529/// reference](https://docs.docker.com/engine/reference/builder/#add):
530/// * `ADD [--chown=<chown>] [--chmod=<chmod>] <src>... <dest>`
531///
532/// Example:
533/// ```
534/// # use dockerfile_builder::instruction_builder::AddBuilder;
535/// let add = AddBuilder::builder()
536/// .chown("myuser:mygroup")
537/// .chmod(655)
538/// .src("home")
539/// .dest("/mydir/")
540/// .build().unwrap();
541/// assert_eq!(add.to_string(), "ADD --chown=myuser:mygroup --chmod=655 home /mydir/");
542/// ```
543///
544/// [ADD]: dockerfile_builder::instruction::ADD
545#[derive(Debug, InstructionBuilder)]
546#[instruction_builder(
547 instruction_name = ADD,
548 value_method = value,
549)]
550pub struct AddBuilder {
551 #[instruction_builder(each = src)]
552 pub sources: Vec<String>,
553 pub dest: String,
554 pub chown: Option<String>,
555 pub chmod: Option<u16>,
556}
557
558impl AddBuilder {
559 fn value(&self) -> Result<String> {
560 Ok(format!(
561 "{}{}{} {}",
562 self.chown
563 .as_ref()
564 .map(|c| format!("--chown={} ", c))
565 .unwrap_or_default(),
566 self.chmod
567 .as_ref()
568 .map(|c| format!("--chmod={} ", c))
569 .unwrap_or_default(),
570 self.sources.join(" "),
571 self.dest,
572 ))
573 }
574}
575
576/// Builder struct for [`ADD`] instruction (http src)
577///
578/// Format according to [Dockerfile
579/// reference](https://docs.docker.com/engine/reference/builder/#add):
580/// * `ADD --checksum=<checksum> <src> <dest>`
581///
582/// Example:
583/// ```
584/// # use dockerfile_builder::instruction_builder::AddHttpBuilder;
585/// let add = AddHttpBuilder::builder()
586/// .checksum("sha256::123")
587/// .src("http://example.com/foobar")
588/// .dest("/")
589/// .build().unwrap();
590/// assert_eq!(add.to_string(), "ADD --checksum=sha256::123 http://example.com/foobar /");
591/// ```
592///
593/// [ADD]: dockerfile_builder::instruction::ADD
594#[derive(Debug, InstructionBuilder)]
595#[instruction_builder(
596 instruction_name = ADD,
597 value_method = value,
598)]
599pub struct AddHttpBuilder {
600 pub src: String,
601 pub dest: String,
602 pub checksum: Option<String>,
603}
604
605impl AddHttpBuilder {
606 fn value(&self) -> Result<String> {
607 if let Err(e) = Url::parse(&self.src) {
608 return Err(anyhow!("{e}"));
609 }
610 Ok(format!(
611 "{}{} {}",
612 self.checksum
613 .as_ref()
614 .map(|c| format!("--checksum={} ", c))
615 .unwrap_or_default(),
616 self.src,
617 self.dest,
618 ))
619 }
620}
621
622/// Builder struct for [`ADD`] instruction (git repository)
623///
624/// Format according to [Dockerfile
625/// reference](https://docs.docker.com/engine/reference/builder/#add):
626/// * `ADD [--keep-git-dir=<boolean>] <git ref> <dir>`
627///
628/// [ADD]: dockerfile_builder::instruction::ADD
629#[derive(Debug, InstructionBuilder)]
630#[instruction_builder(
631 instruction_name = ADD,
632 value_method = value,
633)]
634pub struct AddGitBuilder {
635 pub git_ref: String,
636 pub dir: String,
637 pub keep_git_dir: Option<bool>,
638}
639
640impl AddGitBuilder {
641 fn value(&self) -> Result<String> {
642 if let Err(e) = Url::parse(&self.git_ref) {
643 return Err(anyhow!("{e}"));
644 }
645 Ok(format!(
646 "{}{} {}",
647 self.keep_git_dir
648 .as_ref()
649 .map(|c| format!("--keep-git-dir={} ", c))
650 .unwrap_or_default(),
651 self.git_ref,
652 self.dir,
653 ))
654 }
655}
656
657/// Builder struct for [`COPY`] instruction
658///
659/// Format according to [Dockerfile
660/// reference](https://docs.docker.com/engine/reference/builder/#copy):
661/// * `COPY [--chown=<chown>] [--chmod=<chmod>] [--from=<from>] [--link] <src>... <dest>`
662///
663/// Example:
664/// ```
665/// # use dockerfile_builder::instruction_builder::CopyBuilder;
666/// let copy = CopyBuilder::builder()
667/// .chown("55:mygroup")
668/// .chmod(644)
669/// .src("files")
670/// .src("more_files")
671/// .dest("/somedir/")
672/// .build().unwrap();
673/// assert_eq!(copy.to_string(), "COPY --chown=55:mygroup --chmod=644 files more_files /somedir/");
674/// ```
675///
676/// [COPY]: dockerfile_builder::instruction::COPY
677// TODO: Add flag [--from=]
678#[derive(Debug, InstructionBuilder)]
679#[instruction_builder(
680 instruction_name = COPY,
681 value_method = value,
682)]
683pub struct CopyBuilder {
684 #[instruction_builder(each = src)]
685 pub sources: Vec<String>,
686 pub dest: String,
687 pub chown: Option<String>,
688 pub chmod: Option<u16>,
689 pub from: Option<String>,
690 pub link: Option<bool>,
691}
692
693impl CopyBuilder {
694 fn value(&self) -> Result<String> {
695 Ok(format!(
696 "{}{}{}{}{} {}",
697 self.chown
698 .as_ref()
699 .map(|c| format!("--chown={} ", c))
700 .unwrap_or_default(),
701 self.chmod
702 .as_ref()
703 .map(|c| format!("--chmod={} ", c))
704 .unwrap_or_default(),
705 self.link
706 .as_ref()
707 .map(|c| match c {
708 true => "--link ".to_string(),
709 false => "".to_string(),
710 })
711 .unwrap_or_default(),
712 self.from
713 .as_ref()
714 .map(|c| format!("--from={} ", c))
715 .unwrap_or_default(),
716 self.sources.join(" "),
717 self.dest,
718 ))
719 }
720}
721
722/// Builder struct for [`ENTRYPOINT`] instruction (shell form)
723///
724/// * EntrypointBuilder constructs the shell form for [`ENTRYPOINT`] by default.
725/// To construct the exec form, use [`EntrypointExecBuilder`].
726///
727/// Format according to [Dockerfile
728/// reference](https://docs.docker.com/engine/reference/builder/#entrypoint):
729/// * `ENTRYPOINT command param1 param2`
730///
731/// Example:
732/// ```
733/// # use dockerfile_builder::instruction_builder::EntrypointBuilder;
734/// // build ENTRYPOINT with params
735/// let entrypoint = EntrypointBuilder::builder()
736/// .command("some command")
737/// .param("-f")
738/// .param("-c")
739/// .build().unwrap();
740/// assert_eq!(entrypoint.to_string(), "ENTRYPOINT some command -f -c");
741///
742/// // build ENTRYPOINT with a param vec
743/// let entrypoint = EntrypointBuilder::builder()
744/// .command("some command")
745/// .params(vec!["-f", "-c"])
746/// .build().unwrap();
747/// assert_eq!(entrypoint.to_string(), "ENTRYPOINT some command -f -c");
748/// ```
749///
750/// [ENTRYPOINT]: dockerfile_builder::instruction::ENTRYPOINT
751#[derive(Debug, InstructionBuilder)]
752#[instruction_builder(
753 instruction_name = ENTRYPOINT,
754 value_method = value,
755)]
756pub struct EntrypointBuilder {
757 pub command: String,
758 #[instruction_builder(each = param)]
759 pub params: Option<Vec<String>>,
760}
761
762impl EntrypointBuilder {
763 fn value(&self) -> Result<String> {
764 let params = match self.params.clone() {
765 Some(param_vec) => {
766 if param_vec.is_empty() {
767 String::new()
768 } else {
769 format!(r#" {}"#, param_vec.join(r#" "#))
770 }
771 }
772 None => String::new(),
773 };
774
775 Ok(format!("{}{}", self.command, params))
776 }
777}
778
779/// Builder struct for [`ENTRYPOINT`] instruction (exec form)
780///
781/// * EntrypointExecBuilder constructs the exec form for [`ENTRYPOINT`].
782/// To construct the shell form, use [`EntrypointBuilder`].
783///
784/// Format according to [Dockerfile
785/// reference](https://docs.docker.com/engine/reference/builder/#entrypoint):
786/// * `ENTRYPOINT ["executable", "param1", "param2"]`
787///
788/// Example:
789/// ```
790/// # use dockerfile_builder::instruction_builder::EntrypointExecBuilder;
791/// // build ENTRYPOINT with params
792/// let entrypoint = EntrypointExecBuilder::builder()
793/// .executable("/usr/sbin/apache2ctl")
794/// .param("-D")
795/// .param("FOREGROUND")
796/// .build().unwrap();
797/// assert_eq!(entrypoint.to_string(), r#"ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]"#);
798///
799/// // build ENTRYPOINT with a param vec
800/// let entrypoint = EntrypointExecBuilder::builder()
801/// .executable("/usr/sbin/apache2ctl")
802/// .params(vec!["-D", "FOREGROUND"])
803/// .build().unwrap();
804/// assert_eq!(entrypoint.to_string(), r#"ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]"#);
805/// ```
806///
807/// [ENTRYPOINT]: dockerfile_builder::instruction::ENTRYPOINT
808#[derive(Debug, InstructionBuilder)]
809#[instruction_builder(
810 instruction_name = ENTRYPOINT,
811 value_method = value,
812)]
813pub struct EntrypointExecBuilder {
814 pub executable: String,
815 #[instruction_builder(each = param)]
816 pub params: Option<Vec<String>>,
817}
818
819impl EntrypointExecBuilder {
820 fn value(&self) -> Result<String> {
821 let params = match self.params.clone() {
822 Some(param_vec) => {
823 if param_vec.is_empty() {
824 String::new()
825 } else {
826 format!(r#", "{}""#, param_vec.join(r#"", ""#))
827 }
828 }
829 None => String::new(),
830 };
831 Ok(format!(r#"["{}"{}]"#, self.executable, params))
832 }
833}
834
835/// Builder struct for [`VOLUME`] instruction
836///
837/// Format according to [Dockerfile
838/// reference](https://docs.docker.com/engine/reference/builder/#volume):
839/// * `VOLUME <path>...`
840///
841/// [VOLUME]: dockerfile_builder::instruction::VOLUME
842#[derive(Debug, InstructionBuilder)]
843#[instruction_builder(
844 instruction_name = VOLUME,
845 value_method = value,
846)]
847pub struct VolumeBuilder {
848 #[instruction_builder(each = path)]
849 pub paths: Vec<String>,
850}
851
852impl VolumeBuilder {
853 fn value(&self) -> Result<String> {
854 Ok(self.paths.join(" "))
855 }
856}
857
858/// Builder struct for [`USER`] instruction
859///
860/// Format according to [Dockerfile
861/// reference](https://docs.docker.com/engine/reference/builder/#user):
862/// * `USER <user>`
863/// or
864/// * `USER <user>:<group>`
865/// [USER]: dockerfile_builder::instruction::USER
866#[derive(Debug, InstructionBuilder)]
867#[instruction_builder(
868 instruction_name = USER,
869 value_method = value,
870)]
871pub struct UserBuilder {
872 pub user: String,
873 pub group: Option<String>,
874}
875
876impl UserBuilder {
877 fn value(&self) -> Result<String> {
878 Ok(format!(
879 "{}{}",
880 self.user,
881 self.group
882 .as_ref()
883 .map(|g| format!(":{}", g))
884 .unwrap_or_default()
885 ))
886 }
887}
888
889/// Builder struct for [`WORKDIR`] instruction
890///
891/// Format according to [Dockerfile
892/// reference](https://docs.docker.com/engine/reference/builder/#workdir):
893/// * `WORKDIR <path>`
894///
895/// [WORKDIR]: dockerfile_builder::instruction::WORKDIR
896#[derive(Debug, InstructionBuilder)]
897#[instruction_builder(
898 instruction_name = WORKDIR,
899 value_method = value,
900)]
901pub struct WorkdirBuilder {
902 pub path: String,
903}
904
905impl WorkdirBuilder {
906 fn value(&self) -> Result<String> {
907 Ok(self.path.to_string())
908 }
909}
910
911/// Builder struct for [`ARG`] instruction
912///
913/// Format according to [Dockerfile
914/// reference](https://docs.docker.com/engine/reference/builder/#arg):
915/// * `ARG <name>[=<value>]`
916///
917/// [ARG]: dockerfile_builder::instruction::ARG
918#[derive(Debug, InstructionBuilder)]
919#[instruction_builder(
920 instruction_name = ARG,
921 value_method = value,
922)]
923pub struct ArgBuilder {
924 pub name: String,
925 pub value: Option<String>,
926}
927
928impl ArgBuilder {
929 fn value(&self) -> Result<String> {
930 let value = match &self.value {
931 Some(value) => format!("{}=\"{}\"", self.name, value),
932 None => self.name.to_string(),
933 };
934 Ok(value)
935 }
936}
937
938/// Builder struct for [`ONBUILD`] instruction
939///
940/// Format according to [Dockerfile
941/// reference](https://docs.docker.com/engine/reference/builder/#onbuild):
942/// * `ONBUILD <INSTRUCTION>`
943///
944/// [ONBUILD]: dockerfile_builder::instruction::ONBUILD
945#[derive(Debug, InstructionBuilder)]
946#[instruction_builder(
947 instruction_name = ONBUILD,
948 value_method = value,
949)]
950pub struct OnbuildBuilder {
951 pub instruction: Instruction,
952}
953
954impl OnbuildBuilder {
955 fn value(&self) -> Result<String> {
956 match &self.instruction {
957 Instruction::ONBUILD(_) => Err(anyhow!(
958 "Chaining ONBUILD instructions using ONBUILD ONBUILD isn’t allowed"
959 )),
960 Instruction::FROM(_) => Err(anyhow!(
961 "ONBUILD instruction may not trigger FROM instruction"
962 )),
963 ins => Ok(ins.to_string()),
964 }
965 }
966}
967
968/// Builder struct for [`STOPSIGNAL`] instruction
969///
970/// Format according to [Dockerfile
971/// reference](https://docs.docker.com/engine/reference/builder/#stopsignal):
972/// * `STOPSIGNAL <signal>`
973///
974/// [STOPSIGNAL]: dockerfile_builder::instruction::STOPSIGNAL
975#[derive(Debug, InstructionBuilder)]
976#[instruction_builder(
977 instruction_name = STOPSIGNAL,
978 value_method = value,
979)]
980pub struct StopsignalBuilder {
981 pub signal: String,
982}
983
984impl StopsignalBuilder {
985 fn value(&self) -> Result<String> {
986 Ok(self.signal.to_string())
987 }
988}
989
990/// Builder struct for [`HEALTHCHECK`] instruction
991///
992/// Format according to [Dockerfile
993/// reference](https://docs.docker.com/engine/reference/builder/#healthcheck):
994/// * `HEALTHCHECK [--interval=DURATION] [--timeout=DURATION]
995/// [--start-period=DURATION] [--start-interval=DURATION] [--retries=N] CMD <command>`
996///
997/// [HEALTHCHECK]: dockerfile_builder::instruction::HEALTHCHECK
998#[derive(Debug, InstructionBuilder)]
999#[instruction_builder(
1000 instruction_name = HEALTHCHECK,
1001 value_method = value,
1002)]
1003pub struct HealthcheckBuilder {
1004 pub cmd: Option<CMD>,
1005 pub interval: Option<Duration>,
1006 pub timeout: Option<Duration>,
1007 pub start_period: Option<Duration>,
1008 pub start_interval: Option<Duration>,
1009 pub retries: Option<i32>,
1010}
1011
1012impl HealthcheckBuilder {
1013 fn value(&self) -> Result<String> {
1014 match self.cmd.is_some() {
1015 true => Ok(format!(
1016 "{}{}{}{}{}{}",
1017 self.interval
1018 .as_ref()
1019 .map(|i| format!("--interal={} ", i.as_secs()))
1020 .unwrap_or_default(),
1021 self.timeout
1022 .as_ref()
1023 .map(|t| format!("--timeout={} ", t.as_secs()))
1024 .unwrap_or_default(),
1025 self.start_period
1026 .as_ref()
1027 .map(|s| format!("--start-period={} ", s.as_secs()))
1028 .unwrap_or_default(),
1029 self.start_interval
1030 .as_ref()
1031 .map(|s| format!("--start-interval={} ", s.as_secs()))
1032 .unwrap_or_default(),
1033 self.retries
1034 .as_ref()
1035 .map(|r| format!("--retries={} ", r))
1036 .unwrap_or_default(),
1037 self.cmd.clone().unwrap(),
1038 )),
1039 false => Ok("NONE".to_string()),
1040 }
1041 }
1042
1043 /// Convenience method for setting HEALTHCHECK NONE.
1044 /// ```Rust
1045 /// HealthcheckBuilder::set_none() == "HEALTHCHECK NONE".to_string()
1046 /// ```
1047 pub fn set_none() -> Result<String> {
1048 Ok("HEALTHCHECK NONE".to_string())
1049 }
1050}
1051
1052/// Builder struct for [`SHELL`] instruction
1053///
1054/// Format according to [Dockerfile
1055/// reference](https://docs.docker.com/engine/reference/builder/#shell):
1056/// * `SHELL ["executable", "params"]`
1057///
1058/// Example:
1059/// ```
1060/// # use dockerfile_builder::instruction_builder::ShellBuilder;
1061/// // build SHELL with params
1062/// let shell = ShellBuilder::builder()
1063/// .executable("cmd")
1064/// .param("/S")
1065/// .param("/C")
1066/// .build().unwrap();
1067/// assert_eq!(shell.to_string(), r#"SHELL ["cmd", "/S", "/C"]"#);
1068///
1069/// // build SHELL with a param vec
1070/// let shell = ShellBuilder::builder()
1071/// .executable("cmd")
1072/// .params(vec!["/S", "/C"])
1073/// .build().unwrap();
1074/// assert_eq!(shell.to_string(), r#"SHELL ["cmd", "/S", "/C"]"#);
1075/// ```
1076///
1077/// [SHELL]: dockerfile_builder::instruction::SHELL
1078#[derive(Debug, InstructionBuilder)]
1079#[instruction_builder(
1080 instruction_name = SHELL,
1081 value_method = value,
1082)]
1083pub struct ShellBuilder {
1084 pub executable: String,
1085 #[instruction_builder(each = param)]
1086 pub params: Option<Vec<String>>,
1087}
1088
1089impl ShellBuilder {
1090 fn value(&self) -> Result<String> {
1091 let params = match self.params.clone() {
1092 Some(param_vec) => {
1093 if param_vec.is_empty() {
1094 String::new()
1095 } else {
1096 format!(r#", "{}""#, param_vec.join(r#"", ""#))
1097 }
1098 }
1099 None => String::new(),
1100 };
1101 Ok(format!(r#"["{}"{}]"#, self.executable, params))
1102 }
1103}