dockerfile_parser/instructions/
cmd.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 [`CMD` instruction][cmd].
12///
13/// An command 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/// [cmd]: https://docs.docker.com/engine/reference/builder/#cmd
17#[derive(Debug, PartialEq, Eq, Clone)]
18pub struct CmdInstruction {
19  pub span: Span,
20  pub expr: ShellOrExecExpr,
21}
22
23impl CmdInstruction {
24  pub(crate) fn from_record(record: Pair) -> Result<CmdInstruction> {
25    let span = Span::from_pair(&record);
26    let field = record.into_inner().next().unwrap();
27
28    match field.as_rule() {
29      Rule::cmd_exec => Ok(CmdInstruction {
30        span,
31        expr: ShellOrExecExpr::Exec(parse_string_array(field)?),
32      }),
33      Rule::cmd_shell => Ok(CmdInstruction {
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<'a> TryFrom<&'a Instruction> for &'a CmdInstruction {
67  type Error = Error;
68
69  fn try_from(instruction: &'a Instruction) -> std::result::Result<Self, Self::Error> {
70    if let Instruction::Cmd(c) = instruction {
71      Ok(c)
72    } else {
73      Err(Error::ConversionError {
74        from: format!("{:?}", instruction),
75        to: "CmdInstruction".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 cmd_basic() -> Result<()> {
92    assert_eq!(
93      parse_single(r#"CMD echo "hello world""#, Rule::cmd)?
94        .as_cmd().unwrap()
95        .as_shell().unwrap(),
96      &BreakableString::new((4, 22))
97        .add_string((4, 22), "echo \"hello world\"")
98    );
99
100    assert_eq!(
101      parse_single(r#"CMD echo "hello world""#, Rule::cmd)?
102        .as_cmd().unwrap()
103        .as_shell().unwrap()
104        .to_string(),
105      "echo \"hello world\""
106    );
107
108    assert_eq!(
109      parse_single(r#"cmd ["echo", "hello world"]"#, Rule::cmd)?,
110      CmdInstruction {
111        span: Span::new(0, 27),
112        expr: ShellOrExecExpr::Exec(StringArray {
113          span: Span::new(4, 27),
114          elements: vec![SpannedString {
115            span: Span::new(5, 11),
116            content: "echo".to_string(),
117          }, SpannedString {
118            span: Span::new(13, 26),
119            content: "hello world".to_string(),
120          }]
121        }),
122      }.into()
123    );
124
125    Ok(())
126  }
127
128  #[test]
129  fn cmd_multiline_exec() -> Result<()> {
130    assert_eq!(
131      parse_single(r#"cmd\
132        [\
133        "echo", \
134        "hello world"\
135        ]"#, Rule::cmd)?,
136      CmdInstruction {
137        span: Span::new(0, 66),
138        expr: ShellOrExecExpr::Exec(StringArray {
139          span: Span::new(13, 66),
140          elements: vec![SpannedString {
141            span: Span::new(24, 30),
142            content: "echo".to_string(),
143          }, SpannedString {
144            span: Span::new(42, 55),
145            content: "hello world".to_string(),
146          }]
147        }),
148      }.into()
149    );
150
151    Ok(())
152  }
153
154  #[test]
155  fn cmd_multiline_shell() -> Result<()> {
156    assert_eq!(
157      parse_single(indoc!(r#"
158        cmd echo \
159          "hello world"
160      "#), Rule::cmd)?
161        .as_cmd().unwrap()
162        .as_shell().unwrap(),
163      &BreakableString::new((4, 26))
164        .add_string((4, 9), "echo ")
165        .add_string((11, 26), "  \"hello world\"")
166    );
167
168    assert_eq!(
169      parse_single(indoc!(r#"
170        cmd echo \
171          "hello world"
172      "#), Rule::cmd)?
173        .as_cmd().unwrap()
174        .as_shell().unwrap()
175        .to_string(),
176      "echo   \"hello world\""
177    );
178
179    Ok(())
180  }
181
182  #[test]
183  fn cmd_multiline_shell_large() -> Result<()> {
184    // note: the trailing `\` at the end is _almost_ nonsense and generates a
185    // warning from docker
186    let ins = parse_single(
187      indoc!(r#"
188        cmd set -x && \
189            # lorem ipsum
190            echo "hello world" && \
191            # dolor sit amet,
192            # consectetur \
193            # adipiscing elit, \
194            # sed do eiusmod
195            # tempor incididunt ut labore
196            echo foo && \
197            echo 'bar' \
198            && echo baz \
199            # et dolore magna aliqua."#),
200      Rule::cmd
201    )?.into_cmd().unwrap().into_shell().unwrap();
202
203    assert_eq!(
204      ins,
205      BreakableString::new((4, 266))
206        .add_string((4, 14), "set -x && ")
207        .add_comment((20, 33), "# lorem ipsum")
208        .add_string((34, 60), "    echo \"hello world\" && ")
209        .add_comment((66, 83), "# dolor sit amet,")
210        .add_comment((88, 103), "# consectetur \\")
211        .add_comment((108, 128), "# adipiscing elit, \\")
212        .add_comment((133, 149), "# sed do eiusmod")
213        .add_comment((154, 183), "# tempor incididunt ut labore")
214        .add_string((184, 200), "    echo foo && ")
215        .add_string((202, 217), "    echo 'bar' ")
216        .add_string((219, 235), "    && echo baz ")
217        .add_comment((241, 266), "# et dolore magna aliqua.")
218    );
219
220    assert_eq!(
221      ins.to_string(),
222      r#"set -x &&     echo "hello world" &&     echo foo &&     echo 'bar'     && echo baz "#
223    );
224
225    Ok(())
226  }
227}