dockerfile_parser/instructions/
entrypoint.rs

1// (C) Copyright 2019-2020 Hewlett Packard Enterprise Development LP
2
3use std::convert::TryFrom;
4
5use crate::Span;
6use crate::dockerfile_parser::Instruction;
7use crate::error::*;
8use crate::util::*;
9use crate::parser::*;
10
11/// A Dockerfile [`ENTRYPOINT` instruction][entrypoint].
12///
13/// An entrypoint may be defined as either a single string (to be run in the
14/// default shell), or a list of strings (to be run directly).
15///
16/// [entrypoint]: https://docs.docker.com/engine/reference/builder/#entrypoint
17#[derive(Debug, PartialEq, Eq, Clone)]
18pub struct EntrypointInstruction {
19  pub span: Span,
20  pub expr: ShellOrExecExpr,
21}
22
23impl EntrypointInstruction {
24  pub(crate) fn from_record(record: Pair) -> Result<EntrypointInstruction> {
25    let span = Span::from_pair(&record);
26    let field = record.into_inner().next().unwrap();
27
28    match field.as_rule() {
29      Rule::entrypoint_exec => Ok(EntrypointInstruction {
30        span,
31        expr: ShellOrExecExpr::Exec(parse_string_array(field)?),
32      }),
33      Rule::entrypoint_shell => Ok(EntrypointInstruction {
34        span,
35        expr: ShellOrExecExpr::Shell(parse_any_breakable(field)?),
36      }),
37      _ => Err(unexpected_token(field)),
38    }
39  }
40
41  /// Unpacks this instruction into its inner value if it is a Shell-form
42  /// instruction, otherwise returns None.
43  pub fn into_shell(self) -> Option<BreakableString> {
44    self.expr.into_shell()
45  }
46
47  /// Unpacks this instruction into its inner value if it is a Shell-form
48  /// instruction, otherwise returns None.
49  pub fn as_shell(&self) -> Option<&BreakableString> {
50    self.expr.as_shell()
51  }
52
53  /// Unpacks this instruction into its inner value if it is an Exec-form
54  /// instruction, otherwise returns None.
55  pub fn into_exec(self) -> Option<StringArray> {
56    self.expr.into_exec()
57  }
58
59  /// Unpacks this instruction into its inner value if it is an Exec-form
60  /// instruction, otherwise returns None.
61  pub fn as_exec(&self) -> Option<&StringArray> {
62    self.expr.as_exec()
63  }
64}
65
66impl TryFrom<Instruction> for EntrypointInstruction {
67  type Error = Error;
68
69  fn try_from(instruction: Instruction) -> std::result::Result<Self, Self::Error> {
70    if let Instruction::Entrypoint(e) = instruction {
71      Ok(e)
72    } else {
73      Err(Error::ConversionError {
74        from: format!("{:?}", instruction),
75        to: "EntrypointInstruction".into()
76      })
77    }
78  }
79}
80
81#[cfg(test)]
82mod tests {
83  use indoc::indoc;
84  use pretty_assertions::assert_eq;
85
86  use super::*;
87  use crate::Span;
88  use crate::test_util::*;
89
90  #[test]
91  fn entrypoint_basic() -> Result<()> {
92    assert_eq!(
93      parse_single(r#"entrypoint echo "hello world""#, Rule::entrypoint)?
94        .as_entrypoint().unwrap()
95        .as_shell().unwrap(),
96      &BreakableString::new((11, 29))
97        .add_string((11, 29), "echo \"hello world\"")
98    );
99
100    assert_eq!(
101      parse_single(r#"entrypoint ["echo", "hello world"]"#, Rule::entrypoint)?,
102      EntrypointInstruction {
103        span: Span::new(0, 34),
104        expr: ShellOrExecExpr::Exec(StringArray {
105          span: Span::new(11, 34),
106          elements: vec![SpannedString {
107            span: Span::new(12, 18),
108            content: "echo".to_string(),
109          }, SpannedString {
110            span: Span::new(20, 33),
111            content: "hello world".to_string(),
112          }]
113        })
114      }.into()
115    );
116
117    Ok(())
118  }
119
120  #[test]
121  fn entrypoint_multiline_exec() -> Result<()> {
122    assert_eq!(
123      parse_single(r#"entrypoint\
124        [\
125        "echo", \
126        "hello world"\
127        ]"#, Rule::entrypoint)?,
128      EntrypointInstruction {
129        span: Span::new(0, 73),
130        expr: ShellOrExecExpr::Exec(StringArray {
131          span: Span::new(20, 73),
132          elements: vec![SpannedString {
133            span: Span::new(31, 37),
134            content: "echo".to_string(),
135          }, SpannedString {
136            span: Span::new(49, 62),
137            content: "hello world".to_string(),
138          }]
139        }),
140      }.into()
141    );
142
143    Ok(())
144  }
145
146  #[test]
147  fn entrypoint_multiline_shell() -> Result<()> {
148    assert_eq!(
149      parse_single(indoc!(r#"
150        entrypoint echo \
151          "hello world"
152      "#), Rule::entrypoint)?
153        .as_entrypoint().unwrap()
154        .as_shell().unwrap(),
155      &BreakableString::new((11, 33))
156        .add_string((11, 16), "echo ")
157        .add_string((18, 33), "  \"hello world\"")
158    );
159
160    Ok(())
161  }
162
163  #[test]
164  fn entrypoint_multiline_large() -> Result<()> {
165    // note: the trailing `\` at the end is _almost_ nonsense and generates a
166    // warning from docker
167    let ins = parse_single(
168      indoc!(r#"
169        entrypoint set -x && \
170            # lorem ipsum
171            echo "hello world" && \
172            # dolor sit amet,
173            # consectetur \
174            # adipiscing elit, \
175            # sed do eiusmod
176            # tempor incididunt ut labore
177            echo foo && \
178            echo 'bar' \
179            && echo baz \
180            # et dolore magna aliqua."#),
181      Rule::entrypoint
182    )?.into_entrypoint().unwrap().into_shell().unwrap();
183
184    assert_eq!(
185      ins,
186      BreakableString::new((11, 273))
187        .add_string((11, 21), "set -x && ")
188        .add_comment((27, 40), "# lorem ipsum")
189        .add_string((41, 67), "    echo \"hello world\" && ")
190        .add_comment((73, 90), "# dolor sit amet,")
191        .add_comment((95, 110), "# consectetur \\")
192        .add_comment((115, 135), "# adipiscing elit, \\")
193        .add_comment((140, 156), "# sed do eiusmod")
194        .add_comment((161, 190), "# tempor incididunt ut labore")
195        .add_string((191, 207), "    echo foo && ")
196        .add_string((209, 224), "    echo 'bar' ")
197        .add_string((226, 242), "    && echo baz ")
198        .add_comment((248, 273), "# et dolore magna aliqua.")
199    );
200
201    assert_eq!(
202      ins.to_string(),
203      r#"set -x &&     echo "hello world" &&     echo foo &&     echo 'bar'     && echo baz "#
204    );
205
206    Ok(())
207  }
208}