1mod builder;
2
3pub mod macros;
4
5pub use builder::DockerFile;
6
7use std::{
8 collections::HashMap,
9 convert::From as StdFrom,
10 fmt::{self, Display},
11 hash::Hash,
12};
13
14pub trait Instruction: Display {}
15
16trait StorageInstruction: Instruction {}
17
18#[derive(Debug, Clone, Eq, PartialEq)]
19pub enum TagOrDigest {
20 Tag(String),
21 Digest(String),
22}
23
24pub use TagOrDigest::*;
25
26#[derive(Debug, Clone, Eq, PartialEq)]
27pub struct From {
28 pub image: String,
29 pub tag_or_digest: Option<TagOrDigest>,
30 pub name: Option<String>,
31}
32
33impl Display for From {
34 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35 match (&self.tag_or_digest, &self.name) {
36 (Some(Tag(tag)), None) => write!(f, "FROM {}:{}", self.image, tag),
37 (Some(Tag(tag)), Some(name)) => write!(f, "FROM {}:{} AS {}", self.image, tag, name),
38 (Some(Digest(digest)), None) => write!(f, "FROM {}@{}", self.image, digest),
39 (Some(Digest(digest)), Some(name)) => {
40 write!(f, "FROM {}@{} AS {}", self.image, digest, name)
41 }
42 (None, None) => write!(f, "FROM {}", self.image),
43 (None, Some(name)) => write!(f, "FROM {} AS {}", self.image, name),
44 }
45 }
46}
47
48#[derive(Debug, Clone, Eq, PartialEq)]
49pub struct Run {
50 pub params: Vec<String>,
51}
52
53impl<I, S> StdFrom<I> for Run
54where
55 I: IntoIterator<Item = S>,
56 S: Into<String>,
57{
58 fn from(iter: I) -> Self {
59 let params = iter.into_iter().map(Into::into).collect();
60 Run { params }
61 }
62}
63
64impl Display for Run {
65 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66 write!(
67 f,
68 "RUN [{}]",
69 self.params
70 .iter()
71 .map(|i| format!(r#""{}""#, i))
72 .collect::<Vec<String>>()
73 .join(", ")
74 )
75 }
76}
77
78impl Instruction for Run {}
79impl StorageInstruction for Run {}
80
81#[derive(Debug, Clone, Eq, PartialEq)]
82pub struct Cmd {
83 pub params: Vec<String>,
84}
85
86impl<I, S> StdFrom<I> for Cmd
87where
88 I: IntoIterator<Item = S>,
89 S: Into<String>,
90{
91 fn from(iter: I) -> Self {
92 let params = iter.into_iter().map(Into::into).collect();
93 Cmd { params }
94 }
95}
96
97impl Display for Cmd {
98 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99 write!(
100 f,
101 "CMD [{}]",
102 self.params
103 .iter()
104 .map(|i| format!(r#""{}""#, i))
105 .collect::<Vec<String>>()
106 .join(", ")
107 )
108 }
109}
110
111impl Instruction for Cmd {}
112
113#[derive(Debug, Clone, Eq, PartialEq)]
114pub struct Label {
115 inner: HashMap<String, String>,
116}
117
118impl<K, V> StdFrom<HashMap<K, V>> for Label
119where
120 K: Into<String> + Eq + Hash,
121 V: AsRef<str>,
122{
123 fn from(map: HashMap<K, V>) -> Self {
124 let inner = map
125 .into_iter()
126 .map(|(k, v)| (k.into(), v.as_ref().replace('\n', "\\\n")))
127 .collect();
128 Label { inner }
129 }
130}
131
132impl<K, V> StdFrom<(K, V)> for Label
133where
134 K: Into<String> + Eq + Hash,
135 V: Into<String>,
136{
137 fn from((k, v): (K, V)) -> Self {
138 let mut inner = HashMap::new();
139 inner.insert(k.into(), v.into());
140 Label { inner }
141 }
142}
143
144impl Display for Label {
145 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146 write!(
147 f,
148 "LABEL {}",
149 self.inner
150 .iter()
151 .map(|(k, v)| format!(r#"{}="{}""#, k, v))
152 .collect::<Vec<String>>()
153 .join(" \\\n ")
154 )
155 }
156}
157
158impl Instruction for Label {}
159impl StorageInstruction for Label {}
160
161#[derive(Debug, Clone, Eq, PartialEq)]
165pub struct Maintainer {
166 pub name: String,
167}
168
169impl<T> StdFrom<T> for Maintainer
170where
171 T: Into<String>,
172{
173 fn from(name: T) -> Self {
174 Maintainer { name: name.into() }
175 }
176}
177
178impl PartialEq<Label> for Maintainer {
179 fn eq(&self, other: &Label) -> bool {
180 if let Some(name) = other.inner.get("maintainer") {
181 self.name == *name
182 } else {
183 false
184 }
185 }
186}
187
188impl Display for Maintainer {
189 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
190 write!(f, "MAINTAINER {}", self.name)
191 }
192}
193
194#[derive(Debug, Clone, Eq, PartialEq)]
195pub struct Expose {
196 pub port: u16,
197 pub proto: Option<String>,
198}
199
200impl StdFrom<u16> for Expose {
201 fn from(port: u16) -> Self {
202 Expose { port, proto: None }
203 }
204}
205
206impl Display for Expose {
207 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
208 write!(
209 f,
210 "EXPOSE {}{}",
211 self.port,
212 self.proto
213 .clone()
214 .map(|s| format!("/{}", s))
215 .unwrap_or_default()
216 )
217 }
218}
219
220impl Instruction for Expose {}
221impl StorageInstruction for Expose {}
222
223#[derive(Debug, Clone, Eq, PartialEq)]
224pub struct Env {
225 inner: HashMap<String, String>,
226}
227
228impl<K, V> StdFrom<HashMap<K, V>> for Env
229where
230 K: Into<String> + Eq + Hash,
231 V: Into<String>,
232{
233 fn from(map: HashMap<K, V>) -> Self {
234 let inner = map.into_iter().map(|(k, v)| (k.into(), v.into())).collect();
235 Env { inner }
236 }
237}
238
239impl<K, V> StdFrom<(K, V)> for Env
240where
241 K: Into<String> + Eq + Hash,
242 V: Into<String>,
243{
244 fn from((k, v): (K, V)) -> Self {
245 let mut inner = HashMap::new();
246 inner.insert(k.into(), v.into());
247 Env { inner }
248 }
249}
250
251impl Display for Env {
252 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
253 write!(
254 f,
255 "ENV {}",
256 self.inner
257 .iter()
258 .map(|(k, v)| format!(r#"{}="{}""#, k, v))
259 .collect::<Vec<String>>()
260 .join(" ")
261 )
262 }
263}
264
265impl Instruction for Env {}
266impl StorageInstruction for Env {}
267
268#[derive(Debug, Clone, Eq, PartialEq)]
269pub struct Add {
270 pub src: String,
271 pub dst: String,
272 pub chown: Option<User>,
273}
274
275impl<K, V> StdFrom<(K, V)> for Add
276where
277 K: Into<String>,
278 V: Into<String>,
279{
280 fn from((src, dst): (K, V)) -> Self {
281 Add {
282 src: src.into(),
283 dst: dst.into(),
284 chown: None,
285 }
286 }
287}
288
289impl Display for Add {
290 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
291 match &self.chown {
292 Some(chown) => write!(
293 f,
294 r#"ADD --chown={}{} "{}" "{}""#,
295 chown.user,
296 chown
297 .group
298 .clone()
299 .map(|s| format!(":{}", s))
300 .unwrap_or_default(),
301 self.src,
302 self.dst
303 ),
304 None => write!(f, r#"ADD "{}" "{}""#, self.src, self.dst),
305 }
306 }
307}
308
309impl Instruction for Add {}
310impl StorageInstruction for Add {}
311
312#[derive(Debug, Clone, Eq, PartialEq)]
313pub struct Copy {
314 pub src: String,
315 pub dst: String,
316 pub from: Option<String>,
317 pub chown: Option<User>,
318}
319
320impl<K, V> StdFrom<(K, V)> for Copy
321where
322 K: Into<String>,
323 V: Into<String>,
324{
325 fn from((src, dst): (K, V)) -> Self {
326 Copy {
327 src: src.into(),
328 dst: dst.into(),
329 from: None,
330 chown: None,
331 }
332 }
333}
334
335impl Display for Copy {
336 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
337 match (&self.from, &self.chown) {
338 (Some(from), Some(chown)) => write!(
339 f,
340 r#"COPY --from={} --chown={}{} "{}" "{}""#,
341 from,
342 chown.user,
343 chown
344 .group
345 .clone()
346 .map(|s| format!(":{}", s))
347 .unwrap_or_default(),
348 self.src,
349 self.dst
350 ),
351 (Some(from), None) => {
352 write!(f, r#"COPY --from={} "{}" "{}""#, from, self.src, self.dst)
353 }
354 (None, Some(chown)) => write!(
355 f,
356 r#"COPY --chown={}{} "{}" "{}""#,
357 chown.user,
358 chown
359 .group
360 .clone()
361 .map(|group| format!(":{}", group))
362 .unwrap_or_default(),
363 self.src,
364 self.dst
365 ),
366 (None, None) => write!(f, r#"COPY "{}" "{}""#, self.src, self.dst),
367 }
368 }
369}
370
371impl Instruction for Copy {}
372impl StorageInstruction for Copy {}
373
374#[derive(Debug, Clone, Eq, PartialEq)]
375pub struct EntryPoint {
376 params: Vec<String>,
377}
378
379impl<I, S> StdFrom<I> for EntryPoint
380where
381 I: IntoIterator<Item = S>,
382 S: Into<String>,
383{
384 fn from(iter: I) -> Self {
385 let params = iter.into_iter().map(Into::into).collect();
386 EntryPoint { params }
387 }
388}
389
390impl Display for EntryPoint {
391 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
392 write!(
393 f,
394 "ENTRYPOINT [{}]",
395 self.params
396 .iter()
397 .map(|i| format!(r#""{}""#, i))
398 .collect::<Vec<String>>()
399 .join(", ")
400 )
401 }
402}
403
404impl Instruction for EntryPoint {}
405
406#[derive(Debug, Clone, Eq, PartialEq)]
407pub struct Volume {
408 pub paths: Vec<String>,
409}
410
411impl<I, S> StdFrom<I> for Volume
412where
413 I: IntoIterator<Item = S>,
414 S: Into<String>,
415{
416 fn from(iter: I) -> Self {
417 let paths = iter.into_iter().map(Into::into).collect();
418 Volume { paths }
419 }
420}
421
422impl Display for Volume {
423 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
424 write!(
425 f,
426 "VOLUME [{}]",
427 self.paths
428 .iter()
429 .map(|i| format!(r#""{}""#, i))
430 .collect::<Vec<String>>()
431 .join(", ")
432 )
433 }
434}
435
436impl Instruction for Volume {}
437impl StorageInstruction for Volume {}
438
439#[derive(Debug, Clone, Eq, PartialEq)]
440pub struct User {
441 pub user: String,
442 pub group: Option<String>,
443}
444
445impl Display for User {
446 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
447 match &self.group {
448 Some(group) => write!(f, "USER {}:{}", self.user, group),
449 None => write!(f, "USER {}", self.user),
450 }
451 }
452}
453
454impl Instruction for User {}
455impl StorageInstruction for User {}
456
457#[derive(Debug, Clone, Eq, PartialEq)]
458pub struct WorkDir {
459 pub path: String,
460}
461
462impl<T> StdFrom<T> for WorkDir
463where
464 T: Into<String>,
465{
466 fn from(path: T) -> Self {
467 WorkDir { path: path.into() }
468 }
469}
470
471impl Display for WorkDir {
472 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
473 write!(f, r#"WORKDIR "{}""#, self.path)
474 }
475}
476
477impl Instruction for WorkDir {}
478impl StorageInstruction for WorkDir {}
479
480#[derive(Debug, Clone, Eq, PartialEq)]
481pub struct Arg {
482 pub name: String,
483 pub value: Option<String>,
484}
485
486impl<K, V> StdFrom<(K, V)> for Arg
487where
488 K: Into<String>,
489 V: Into<String>,
490{
491 fn from((name, value): (K, V)) -> Self {
492 Arg {
493 name: name.into(),
494 value: Some(value.into()),
495 }
496 }
497}
498
499impl Display for Arg {
500 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
501 match &self.value {
502 Some(value) => write!(f, r#"ARG {}="{}""#, self.name, value),
503 None => write!(f, "ARG {}", self.name),
504 }
505 }
506}
507
508impl Instruction for Arg {}
509impl StorageInstruction for Arg {}
510
511#[derive(Debug, Clone, Eq, PartialEq)]
512pub struct StopSignal {
513 pub signal: String,
514}
515
516impl<T> StdFrom<T> for StopSignal
517where
518 T: Into<String>,
519{
520 fn from(signal: T) -> Self {
521 StopSignal {
522 signal: signal.into(),
523 }
524 }
525}
526
527impl Display for StopSignal {
528 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
529 write!(f, "STOPSIGNAL {}", self.signal)
530 }
531}
532
533impl Instruction for StopSignal {}
534impl StorageInstruction for StopSignal {}
535
536#[derive(Debug, Clone, Eq, PartialEq)]
537pub enum HealthCheck {
538 Check {
539 cmd: Cmd,
540 interval: Option<i32>,
541 timeout: Option<i32>,
542 start_period: Option<i32>,
543 retries: Option<i32>,
544 },
545 None,
546}
547
548impl Display for HealthCheck {
549 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
550 match self {
551 HealthCheck::Check {
552 cmd,
553 interval,
554 timeout,
555 start_period,
556 retries,
557 } => {
558 write!(f, "HEALTHCHECK ")?;
559 if let Some(interval) = interval {
560 write!(f, "--interval={} ", interval)?;
561 }
562 if let Some(timeout) = timeout {
563 write!(f, "--timeout={} ", timeout)?;
564 }
565 if let Some(period) = start_period {
566 write!(f, "--start-period={} ", period)?;
567 }
568 if let Some(retries) = retries {
569 write!(f, "--retries={} ", retries)?;
570 }
571 write!(f, "{}", cmd)
572 }
573 HealthCheck::None => write!(f, "HEALTHCHECK NONE"),
574 }
575 }
576}
577
578impl Instruction for HealthCheck {}
579impl StorageInstruction for HealthCheck {}
580
581#[derive(Debug, Clone, Eq, PartialEq)]
582pub struct Shell {
583 pub params: Vec<String>,
584}
585
586impl<I, S> StdFrom<I> for Shell
587where
588 I: IntoIterator<Item = S>,
589 S: Into<String>,
590{
591 fn from(iter: I) -> Self {
592 let params = iter.into_iter().map(Into::into).collect();
593 Shell { params }
594 }
595}
596
597impl Display for Shell {
598 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
599 write!(
600 f,
601 "SHELL [{}]",
602 self.params
603 .iter()
604 .map(|i| format!(r#""{}""#, i))
605 .collect::<Vec<String>>()
606 .join(", ")
607 )
608 }
609}
610
611impl Instruction for Shell {}
612impl StorageInstruction for Shell {}
613
614pub struct OnBuild {
615 inner: Box<Instruction>,
616}
617
618impl<I> StdFrom<I> for OnBuild
619where
620 I: Instruction + 'static,
621{
622 fn from(i: I) -> Self {
623 let inner = Box::new(i);
624 OnBuild { inner }
625 }
626}
627
628impl Display for OnBuild {
629 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
630 write!(f, "ONBUILD {}", self.inner)
631 }
632}
633
634pub struct Comment {
635 pub comment: String,
636}
637
638impl<T> StdFrom<T> for Comment
639where
640 T: Into<String>,
641{
642 fn from(comment: T) -> Self {
643 Comment {
644 comment: comment.into(),
645 }
646 }
647}
648
649impl Display for Comment {
650 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
651 write!(f, "# {}", self.comment)
652 }
653}
654
655impl Instruction for Comment {}
656impl StorageInstruction for Comment {}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661
662 #[test]
663 fn from() {
664 let image = String::from("rust");
665 let tag = Some(Tag("latest".into()));
666 let digest = Some(Digest("digest".into()));
667 let name = Some(String::from("crab"));
668
669 let from = From {
671 image: image.clone(),
672 tag_or_digest: tag.clone(),
673 name: None,
674 };
675 assert_eq!(from.to_string(), "FROM rust:latest");
676
677 let from = From {
679 image: image.clone(),
680 tag_or_digest: tag.clone(),
681 name: name.clone(),
682 };
683 assert_eq!(from.to_string(), "FROM rust:latest AS crab");
684
685 let from = From {
687 image: image.clone(),
688 tag_or_digest: digest.clone(),
689 name: None,
690 };
691 assert_eq!(from.to_string(), "FROM rust@digest");
692
693 let from = From {
695 image: image.clone(),
696 tag_or_digest: digest.clone(),
697 name: name.clone(),
698 };
699 assert_eq!(from.to_string(), "FROM rust@digest AS crab");
700
701 let from = From {
703 image: image.clone(),
704 tag_or_digest: None,
705 name: None,
706 };
707 assert_eq!(from.to_string(), "FROM rust");
708
709 let from = From {
711 image: image.clone(),
712 tag_or_digest: None,
713 name: name.clone(),
714 };
715 assert_eq!(from.to_string(), "FROM rust AS crab");
716 }
717
718 #[test]
719 fn run() {
720 let curl = vec!["curl", "-v", "https://rust-lang.org"];
721 let run = Run::from(curl);
722 assert_eq!(run.params, ["curl", "-v", "https://rust-lang.org"]);
723 assert_eq!(
724 run.to_string(),
725 r#"RUN ["curl", "-v", "https://rust-lang.org"]"#
726 )
727 }
728
729 #[test]
730 fn cmd() {
731 let curl = vec!["curl", "-v", "https://rust-lang.org"];
732 let cmd = Cmd::from(curl);
733 assert_eq!(cmd.params, ["curl", "-v", "https://rust-lang.org"]);
734 assert_eq!(
735 cmd.to_string(),
736 r#"CMD ["curl", "-v", "https://rust-lang.org"]"#
737 )
738 }
739
740 #[test]
741 fn label() {
742 let mut map = HashMap::new();
743 map.insert("key", "value");
744 let label = Label::from(map);
745 assert_eq!(label.to_string(), r#"LABEL key="value""#);
746
747 let mut map = HashMap::new();
748 map.insert("key", "1\n2\n3");
749 let label = Label::from(map);
750 assert_eq!(
751 label.to_string(),
752 r#"LABEL key="1\
7532\
7543""#
755 );
756
757 let mut map = HashMap::new();
758 map.insert("key", "value");
759 map.insert("hello", "world");
760 let label = Label::from(map);
761 let label = label.to_string();
762 assert!(
763 label
764 == r#"LABEL hello="world" \
765 key="value""#
766 || label
767 == r#"LABEL key="value" \
768 hello="world""#
769 );
770 }
771
772 #[test]
773 fn maintainer() {
774 let name = String::from("Someone Rustacean");
775 let maintainer = Maintainer::from(name.clone());
776 assert_eq!(maintainer.to_string(), "MAINTAINER Someone Rustacean");
777 assert_eq!(maintainer, Label::from(("maintainer", name)))
778 }
779
780 #[test]
781 fn expose() {
782 let port = 80;
783 let proto = Some(String::from("tcp"));
784
785 let expose = Expose { port, proto: None };
787 assert_eq!(expose.to_string(), "EXPOSE 80");
788
789 let expose = Expose { port, proto };
791 assert_eq!(expose.to_string(), "EXPOSE 80/tcp")
792 }
793
794 #[test]
795 fn env() {
796 let mut map = HashMap::new();
797 map.insert("key", "value");
798 let label = Env::from(map.clone());
799 assert_eq!(label.to_string(), r#"ENV key="value""#);
800 }
801
802 #[test]
803 fn add() {
804 let chown = User {
805 user: "rustacean".to_string(),
806 group: None,
807 };
808 let src = "/home/container001".to_string();
809 let dst = "/".to_string();
810
811 let add = Add {
813 src: src.clone(),
814 dst: dst.clone(),
815 chown: Some(chown),
816 };
817 assert_eq!(
818 add.to_string(),
819 r#"ADD --chown=rustacean "/home/container001" "/""#
820 );
821
822 let add = Add::from((src.clone(), dst.clone()));
824 assert_eq!(add.to_string(), r#"ADD "/home/container001" "/""#);
825 }
826
827 #[test]
828 fn copy() {
829 let from = Some("crab".to_string());
830 let chown = Some(User {
831 user: "rustacean".to_string(),
832 group: Some("root".to_string()),
833 });
834 let src = "/home/container001".to_string();
835 let dst = "/".to_string();
836
837 let copy = Copy {
839 src: src.clone(),
840 dst: dst.clone(),
841 from: from.clone(),
842 chown: chown.clone(),
843 };
844 assert_eq!(
845 copy.to_string(),
846 r#"COPY --from=crab --chown=rustacean:root "/home/container001" "/""#
847 );
848
849 let copy = Copy {
851 src: src.clone(),
852 dst: dst.clone(),
853 from: from.clone(),
854 chown: None,
855 };
856 assert_eq!(
857 copy.to_string(),
858 r#"COPY --from=crab "/home/container001" "/""#
859 );
860
861 let copy = Copy {
863 src: src.clone(),
864 dst: dst.clone(),
865 from: None,
866 chown: chown.clone(),
867 };
868 assert_eq!(
869 copy.to_string(),
870 r#"COPY --chown=rustacean:root "/home/container001" "/""#
871 );
872
873 let copy = Copy::from((src.clone(), dst.clone()));
875 assert_eq!(copy.to_string(), r#"COPY "/home/container001" "/""#);
876 }
877
878 #[test]
879 fn entrypoint() {
880 let curl = vec!["curl", "-v", "https://rust-lang.org"];
881 let point = EntryPoint::from(curl);
882 assert_eq!(point.params, ["curl", "-v", "https://rust-lang.org"]);
883 assert_eq!(
884 point.to_string(),
885 r#"ENTRYPOINT ["curl", "-v", "https://rust-lang.org"]"#
886 )
887 }
888
889 #[test]
890 fn volume() {
891 let paths = vec!["/var/run"];
892 let volume = Volume::from(paths);
893 assert_eq!(volume.to_string(), r#"VOLUME ["/var/run"]"#);
894 }
895
896 #[test]
897 fn user() {
898 let user = "rustacean".to_string();
899 let group = Some("root".to_string());
900
901 let usr = User {
903 user: user.clone(),
904 group,
905 };
906 assert_eq!(usr.to_string(), "USER rustacean:root");
907
908 let usr = User { user, group: None };
910 assert_eq!(usr.to_string(), "USER rustacean");
911 }
912
913 #[test]
914 fn workdir() {
915 let path = "/var/run";
916 let dir = WorkDir::from(path);
917 assert_eq!(dir.to_string(), r#"WORKDIR "/var/run""#)
918 }
919
920 #[test]
921 fn arg() {
922 let name = "name".to_string();
923 let value = Some("value".to_string());
924
925 let arg = Arg {
927 name: name.clone(),
928 value,
929 };
930 assert_eq!(arg.to_string(), r#"ARG name="value""#);
931
932 let arg = Arg { name, value: None };
934 assert_eq!(arg.to_string(), r#"ARG name"#);
935 }
936
937 #[test]
938 fn stopsignal() {
939 let signal = "SIGKILL".to_string();
940 let signal = StopSignal::from(signal);
941 assert_eq!(signal.to_string(), "STOPSIGNAL SIGKILL");
942 }
943
944 #[test]
945 fn healthcheck() {
946 let curl = vec!["curl", "-v", "https://rust-lang.org"];
948 let cmd = Cmd::from(curl);
949 let check = HealthCheck::Check {
950 cmd,
951 interval: Some(0),
952 timeout: Some(3600),
953 start_period: Some(123),
954 retries: Some(2),
955 };
956 assert_eq!(check.to_string(), r#"HEALTHCHECK --interval=0 --timeout=3600 --start-period=123 --retries=2 CMD ["curl", "-v", "https://rust-lang.org"]"#);
957
958 let check = HealthCheck::None;
960 assert_eq!(check.to_string(), "HEALTHCHECK NONE");
961 }
962
963 #[test]
964 fn shell() {
965 let bash = vec!["bash", "-c"];
966 let shell = Shell::from(bash);
967 assert_eq!(shell.params, ["bash", "-c"]);
968 assert_eq!(shell.to_string(), r#"SHELL ["bash", "-c"]"#)
969 }
970
971 #[test]
972 fn onbuild() {
973 let curl = vec!["curl", "-v", "https://rust-lang.org"];
974 let cmd = Cmd::from(curl);
975 let onbuild = OnBuild::from(cmd);
976 assert_eq!(
977 onbuild.to_string(),
978 r#"ONBUILD CMD ["curl", "-v", "https://rust-lang.org"]"#
979 );
980 }
981
982 #[test]
983 fn comment() {
984 let comment = "This is an example comment";
985 let comment = Comment::from(comment);
986 assert_eq!(comment.to_string(), "# This is an example comment");
987 }
988}