diff --git a/src/dockerfile_parser.pest b/src/dockerfile_parser.pest index 6752bcc..0e19d94 100644 --- a/src/dockerfile_parser.pest +++ b/src/dockerfile_parser.pest @@ -101,10 +101,13 @@ string_array = _{ ) | "[" ~ arg_ws_maybe ~ "]" } +from_flag_name = @{ ASCII_ALPHA+ } +from_flag_value = @{ any_whitespace } +from_flag = { "--" ~ from_flag_name ~ "=" ~ from_flag_value } from_image = @{ (ASCII_ALPHANUMERIC | "_" | "-" | "." | ":" | "/" | "$" | "{" | "}")+ } from_alias = { identifier_whitespace } from_alias_outer = _{ arg_ws ~ ^"as" ~ arg_ws ~ from_alias } -from = { ^"from" ~ arg_ws ~ from_image ~ from_alias_outer? } +from = { ^"from" ~ (arg_ws ~ from_flag)* ~ arg_ws ~ from_image ~ from_alias_outer? } arg_name = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } arg_value = ${ any_whitespace } diff --git a/src/instructions/from.rs b/src/instructions/from.rs index 0f76394..6694ead 100644 --- a/src/instructions/from.rs +++ b/src/instructions/from.rs @@ -10,6 +10,45 @@ use crate::SpannedString; use crate::splicer::*; use crate::error::*; +/// A key/value pair passed to a `FROM` instruction as a flag. +/// +/// Examples include: `FROM --platform=linux/amd64 node:lts-alpine` +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct FromFlag { + pub span: Span, + pub name: SpannedString, + pub value: SpannedString, +} + +impl FromFlag { + fn from_record(record: Pair) -> Result { + let span = Span::from_pair(&record); + let mut name = None; + let mut value = None; + + for field in record.into_inner() { + match field.as_rule() { + Rule::from_flag_name => name = Some(parse_string(&field)?), + Rule::from_flag_value => value = Some(parse_string(&field)?), + _ => return Err(unexpected_token(field)) + } + } + + let name = name.ok_or_else(|| Error::GenericParseError { + message: "from flags require a key".into(), + })?; + + let value = value.ok_or_else(|| Error::GenericParseError { + message: "from flags require a value".into() + })?; + + Ok(FromFlag { + span, name, value + }) + } +} + + /// A Dockerfile [`FROM` instruction][from]. /// /// Contains spans for the entire instruction, the image, and the alias (if @@ -19,6 +58,7 @@ use crate::error::*; #[derive(Debug, PartialEq, Eq, Clone)] pub struct FromInstruction { pub span: Span, + pub flags: Vec, pub image: SpannedString, pub image_parsed: ImageRef, @@ -31,9 +71,11 @@ impl FromInstruction { let span = Span::from_pair(&record); let mut image_field = None; let mut alias_field = None; + let mut flags = Vec::new(); for field in record.into_inner() { match field.as_rule() { + Rule::from_flag => flags.push(FromFlag::from_record(field)?), Rule::from_image => image_field = Some(field), Rule::from_alias => alias_field = Some(field), Rule::comment => continue, @@ -60,7 +102,7 @@ impl FromInstruction { Ok(FromInstruction { span, index, image, image_parsed, - alias, + flags, alias, }) } @@ -117,6 +159,7 @@ mod tests { hash: None }, alias: None, + flags: vec![], }); Ok(()) @@ -144,6 +187,47 @@ mod tests { Ok(()) } + #[test] + fn from_flags() -> Result<()> { + assert_eq!( + parse_single( + "FROM --platform=linux/amd64 alpine:3.10", + Rule::from + )?, + FromInstruction { + index: 0, + span: Span { start: 0, end: 39 }, + flags: vec![ + FromFlag { + span: Span { start: 5, end: 27 }, + name: SpannedString { + content: "platform".into(), + span: Span { start: 7, end: 15 }, + }, + value: SpannedString { + content: "linux/amd64".into(), + span: Span { start: 16, end: 27 }, + } + } + ], + image: SpannedString { + span: Span { start: 28, end: 39 }, + content: "alpine:3.10".into(), + }, + image_parsed: ImageRef { + registry: None, + image: "alpine".into(), + tag: Some("3.10".into()), + hash: None + }, + alias: None, + }.into() + ); + + Ok(()) + } + + #[test] fn from_multiline() -> Result<()> { let from = parse_direct( @@ -180,6 +264,7 @@ mod tests { span: (64, 68).into(), content: "test".into(), }), + flags: vec![], }); Ok(()) diff --git a/tests/parsing.rs b/tests/parsing.rs index 9523b8e..f7e2626 100644 --- a/tests/parsing.rs +++ b/tests/parsing.rs @@ -32,6 +32,7 @@ fn parse_basic() -> Result<(), dockerfile_parser::Error> { }, index: 0, alias: None, + flags: vec![], }) );