dockerfile_rs/
lib.rs

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/// Deprecated, use [`Label`] with `maintainer` key instead
162///
163/// [`Label`]: struct.Label.html
164#[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        // tag and no name
670        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        // tag and name
678        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        // digest and no name
686        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        // digest and name
694        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        // no tag or digest and no name
702        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        // no tag or digest and name
710        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        // without proto
786        let expose = Expose { port, proto: None };
787        assert_eq!(expose.to_string(), "EXPOSE 80");
788
789        // with proto
790        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        // with chown
812        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        // without chown
823        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        // with from and with chown
838        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        // with from
850        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        // with chown
862        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        // without from and without chown
874        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        // with group
902        let usr = User {
903            user: user.clone(),
904            group,
905        };
906        assert_eq!(usr.to_string(), "USER rustacean:root");
907
908        // without group
909        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        // with value
926        let arg = Arg {
927            name: name.clone(),
928            value,
929        };
930        assert_eq!(arg.to_string(), r#"ARG name="value""#);
931
932        // without value
933        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        // with params
947        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        // without params
959        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}