dockerfile_parser/instructions/
from.rs1use std::convert::TryFrom;
4
5use crate::dockerfile_parser::Instruction;
6use crate::image::ImageRef;
7use crate::parser::{Pair, Rule};
8use crate::parse_string;
9use crate::SpannedString;
10use crate::splicer::*;
11use crate::error::*;
12
13use lazy_static::lazy_static;
14use regex::Regex;
15
16#[derive(Debug, PartialEq, Eq, Clone)]
20pub struct FromFlag {
21 pub span: Span,
22 pub name: SpannedString,
23 pub value: SpannedString,
24}
25
26impl FromFlag {
27 fn from_record(record: Pair) -> Result<FromFlag> {
28 let span = Span::from_pair(&record);
29 let mut name = None;
30 let mut value = None;
31
32 for field in record.into_inner() {
33 match field.as_rule() {
34 Rule::from_flag_name => name = Some(parse_string(&field)?),
35 Rule::from_flag_value => value = Some(parse_string(&field)?),
36 _ => return Err(unexpected_token(field))
37 }
38 }
39
40 let name = name.ok_or_else(|| Error::GenericParseError {
41 message: "from flags require a key".into(),
42 })?;
43
44 let value = value.ok_or_else(|| Error::GenericParseError {
45 message: "from flags require a value".into()
46 })?;
47
48 Ok(FromFlag {
49 span, name, value
50 })
51 }
52}
53
54
55#[derive(Debug, PartialEq, Eq, Clone)]
62pub struct FromInstruction {
63 pub span: Span,
64 pub flags: Vec<FromFlag>,
65 pub image: SpannedString,
66 pub image_parsed: ImageRef,
67
68 pub index: usize,
69 pub alias: Option<SpannedString>,
70}
71
72impl FromInstruction {
73 pub(crate) fn from_record(record: Pair, index: usize) -> Result<FromInstruction> {
74 lazy_static! {
75 static ref HEX: Regex =
76 Regex::new(r"[0-9a-fA-F]+").unwrap();
77 }
78
79 let span = Span::from_pair(&record);
80 let mut image_field = None;
81 let mut alias_field = None;
82 let mut flags = Vec::new();
83
84 for field in record.into_inner() {
85 match field.as_rule() {
86 Rule::from_flag => flags.push(FromFlag::from_record(field)?),
87 Rule::from_image => image_field = Some(field),
88 Rule::from_alias => alias_field = Some(field),
89 Rule::comment => continue,
90 _ => return Err(unexpected_token(field))
91 };
92 }
93
94 let image = if let Some(image_field) = image_field {
95 parse_string(&image_field)?
96 } else {
97 return Err(Error::GenericParseError {
98 message: "missing from image".into()
99 });
100 };
101
102 let image_parsed = ImageRef::parse(&image.as_ref());
103
104 if let Some(hash) = &image_parsed.hash {
105 let parts: Vec<&str> = hash.split(":").collect();
106 if let ["sha256", hexdata] = parts[..] {
107 if !HEX.is_match(hexdata) || hexdata.len() != 64 {
108 return Err(Error::GenericParseError { message: "image reference digest is invalid".into() });
109 }
110 } else {
111 return Err(Error::GenericParseError { message: "image reference digest is invalid".into() });
112 }
113 }
114
115 let alias = if let Some(alias_field) = alias_field {
116 Some(parse_string(&alias_field)?)
117 } else {
118 None
119 };
120
121 Ok(FromInstruction {
122 span, index,
123 image, image_parsed,
124 flags, alias,
125 })
126 }
127
128 }
133
134impl<'a> TryFrom<&'a Instruction> for &'a FromInstruction {
135 type Error = Error;
136
137 fn try_from(instruction: &'a Instruction) -> std::result::Result<Self, Self::Error> {
138 if let Instruction::From(f) = instruction {
139 Ok(f)
140 } else {
141 Err(Error::ConversionError {
142 from: format!("{:?}", instruction),
143 to: "FromInstruction".into()
144 })
145 }
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use core::panic;
152
153use indoc::indoc;
154 use pretty_assertions::assert_eq;
155
156 use super::*;
157 use crate::test_util::*;
158
159 #[test]
160 fn from_bad_digest() {
161 let cases = vec![
162 "from alpine@sha256:ca5a2eb9b7917e542663152b04c0",
163 "from alpine@sha257:ca5a2eb9b7917e542663152b04c0ad0572e0522fcf80ff080156377fc08ea8f8",
164 "from alpine@ca5a2eb9b7917e542663152b04c0ad0572e0522fcf80ff080156377fc08ea8f8",
165 ];
166
167 for case in cases {
168 let result = parse_direct(
169 case,
170 Rule::from,
171 |p| FromInstruction::from_record(p, 0)
172 );
173
174 match result {
175 Ok(_) => panic!("Expected parse error."),
176 Err(Error::GenericParseError { message: _}) => {},
177 Err(_) => panic!("Expected GenericParseError"),
178 };
179 }
180 }
181
182 #[test]
183 fn from_no_alias() -> Result<()> {
184 let from = parse_direct(
187 "from alpine:3.10",
188 Rule::from,
189 |p| FromInstruction::from_record(p, 0)
190 )?;
191
192 assert_eq!(from, FromInstruction {
193 span: Span { start: 0, end: 16 },
194 index: 0,
195 image: SpannedString {
196 span: Span { start: 5, end: 16 },
197 content: "alpine:3.10".into(),
198 },
199 image_parsed: ImageRef {
200 registry: None,
201 image: "alpine".into(),
202 tag: Some("3.10".into()),
203 hash: None
204 },
205 alias: None,
206 flags: vec![],
207 });
208
209 Ok(())
210 }
211
212 #[test]
213 fn from_no_newline() -> Result<()> {
214 assert!(parse_single(
217 "from alpine:3.10 from example",
218 Rule::dockerfile,
219 ).is_err());
220
221 Ok(())
222 }
223
224 #[test]
225 fn from_missing_alias() -> Result<()> {
226 assert!(parse_single(
227 "from alpine:3.10 as",
228 Rule::dockerfile,
229 ).is_err());
230
231 Ok(())
232 }
233
234 #[test]
235 fn from_flags() -> Result<()> {
236 assert_eq!(
237 parse_single(
238 "FROM --platform=linux/amd64 alpine:3.10",
239 Rule::from
240 )?,
241 FromInstruction {
242 index: 0,
243 span: Span { start: 0, end: 39 },
244 flags: vec![
245 FromFlag {
246 span: Span { start: 5, end: 27 },
247 name: SpannedString {
248 content: "platform".into(),
249 span: Span { start: 7, end: 15 },
250 },
251 value: SpannedString {
252 content: "linux/amd64".into(),
253 span: Span { start: 16, end: 27 },
254 }
255 }
256 ],
257 image: SpannedString {
258 span: Span { start: 28, end: 39 },
259 content: "alpine:3.10".into(),
260 },
261 image_parsed: ImageRef {
262 registry: None,
263 image: "alpine".into(),
264 tag: Some("3.10".into()),
265 hash: None
266 },
267 alias: None,
268 }.into()
269 );
270
271 Ok(())
272 }
273
274
275 #[test]
276 fn from_multiline() -> Result<()> {
277 let from = parse_direct(
278 indoc!(r#"
279 from \
280 # foo
281 alpine:3.10 \
282
283 # test
284 # comment
285
286 as \
287
288 test
289 "#),
290 Rule::from,
291 |p| FromInstruction::from_record(p, 0)
292 )?;
293
294 assert_eq!(from, FromInstruction {
295 span: Span { start: 0, end: 68 },
296 index: 0,
297 image: SpannedString {
298 span: Span { start: 17, end: 28 },
299 content: "alpine:3.10".into(),
300 },
301 image_parsed: ImageRef {
302 registry: None,
303 image: "alpine".into(),
304 tag: Some("3.10".into()),
305 hash: None
306 },
307 alias: Some(SpannedString {
308 span: (64, 68).into(),
309 content: "test".into(),
310 }),
311 flags: vec![],
312 });
313
314 Ok(())
315 }
316}