Skip to content

Commit

Permalink
Add support for EXPOSE instruction
Browse files Browse the repository at this point in the history
  • Loading branch information
Bond-009 committed Dec 11, 2024
1 parent bc52c98 commit 3d9038d
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 1 deletion.
8 changes: 7 additions & 1 deletion src/dockerfile_parser.pest
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ step = _{
entrypoint |
cmd |
env |
expose |

// todos:
// add | workdir | user

// things that we probably won't bother supporting
// expose | volume | onbuild | stopsignal | healthcheck | shell
// volume | onbuild | stopsignal | healthcheck | shell

// deprecated: maintainer

Expand Down Expand Up @@ -154,6 +155,11 @@ env_single_value = @{ any_breakable }
env_single = { arg_ws ~ env_name ~ arg_ws ~ (env_single_quoted_value | env_single_value) }
env = { ^"env" ~ (env_single | env_pairs) }

expose_protocol = ${ "tcp" | "udp" }
expose_port_number = ${ ASCII_DIGIT+ }
expose_port = @{ expose_port_number ~ ("/" ~ expose_protocol)?}
expose = { ^"expose" ~ (arg_ws ~ expose_port?)+}

misc_instruction = @{ ASCII_ALPHA+ }
misc_arguments = @{ any_breakable }
misc = { misc_instruction ~ misc_arguments }
23 changes: 23 additions & 0 deletions src/dockerfile_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub enum Instruction {
Cmd(CmdInstruction),
Copy(CopyInstruction),
Env(EnvInstruction),
Expose(ExposeInstruction),
Misc(MiscInstruction)
}

Expand Down Expand Up @@ -190,6 +191,24 @@ impl Instruction {
}
}

/// Attempts to convert this instruction into an ExposeInstruction, returning
/// None if impossible.
pub fn into_expose(self) -> Option<ExposeInstruction> {
match self {
Instruction::Expose(e) => Some(e),
_ => None,
}
}

/// Attempts to convert this instruction into an ExposeInstruction, returning
/// None if impossible.
pub fn as_expose(&self) -> Option<&ExposeInstruction> {
match self {
Instruction::Expose(e) => Some(e),
_ => None,
}
}

/// Attempts to convert this instruction into a MiscInstruction, returning
/// None if impossible.
pub fn into_misc(self) -> Option<MiscInstruction> {
Expand Down Expand Up @@ -219,6 +238,7 @@ impl Instruction {
Instruction::Cmd(instruction) => instruction.span,
Instruction::Copy(instruction) => instruction.span,
Instruction::Env(instruction) => instruction.span,
Instruction::Expose(instruction) => instruction.span,
Instruction::Misc(instruction) => instruction.span,
}
}
Expand All @@ -244,6 +264,7 @@ impl_from_instruction!(EntrypointInstruction, Instruction::Entrypoint);
impl_from_instruction!(CmdInstruction, Instruction::Cmd);
impl_from_instruction!(CopyInstruction, Instruction::Copy);
impl_from_instruction!(EnvInstruction, Instruction::Env);
impl_from_instruction!(ExposeInstruction, Instruction::Expose);
impl_from_instruction!(MiscInstruction, Instruction::Misc);

impl TryFrom<Pair<'_>> for Instruction {
Expand All @@ -265,6 +286,8 @@ impl TryFrom<Pair<'_>> for Instruction {

Rule::env => EnvInstruction::from_record(record)?.into(),

Rule::expose => ExposeInstruction::from_record(record)?.into(),

Rule::misc => MiscInstruction::from_record(record)?.into(),

// TODO: consider exposing comments
Expand Down
200 changes: 200 additions & 0 deletions src/instructions/expose.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
use std::convert::TryFrom;

use crate::error::*;
use crate::{parse_short, Instruction, Pair, Rule, Span, SpannedShort, SpannedString};

use snafu::ResultExt;

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ExposePort {
pub span: Span,
pub port: SpannedShort,
pub protocol: Option<SpannedString>,
}

impl ExposePort {
pub fn new(span: Span, port: SpannedShort, protocol: Option<SpannedString>) -> Self {
ExposePort {
span,
port,
protocol,
}
}
}

/// https://docs.docker.com/reference/dockerfile/#expose
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ExposeInstruction {
pub span: Span,
pub vars: Vec<ExposePort>,
}

fn parse_expose_port(record: Pair) -> Result<ExposePort> {
let span = Span::from_pair(&record);
let mut port = None;
let mut protocol = None;

for field in record.into_inner() {
match field.as_rule() {
Rule::expose_port_number => port = Some(parse_short(&field)?),
Rule::expose_protocol => {
protocol = Some(SpannedString {
span: Span::from_pair(&field),
content: field.as_str().to_owned(),
});
}
_ => return Err(unexpected_token(field)),
}
}

let port = port.ok_or_else(|| Error::GenericParseError {
message: "env pair requires a key".into(),
})?;

Ok(ExposePort {
span,
port,
protocol,
})
}

impl ExposeInstruction {
pub(crate) fn from_record(record: Pair) -> Result<ExposeInstruction> {
let span = Span::from_pair(&record);
let mut vars = Vec::new();

for field in record.into_inner() {
match field.as_rule() {
Rule::expose_port => vars.push(parse_expose_port(field)?),
_ => return Err(unexpected_token(field)),
}
}

Ok(ExposeInstruction { span, vars })
}
}

impl<'a> TryFrom<&'a Instruction> for &'a ExposeInstruction {
type Error = Error;

fn try_from(instruction: &'a Instruction) -> std::result::Result<Self, Self::Error> {
if let Instruction::Expose(e) = instruction {
Ok(e)
} else {
Err(Error::ConversionError {
from: format!("{:?}", instruction),
to: "ExposeInstruction".into(),
})
}
}
}

#[cfg(test)]
mod tests {
use indoc::indoc;
use pretty_assertions::assert_eq;

use super::*;
use crate::test_util::*;
use crate::Dockerfile;

#[test]
fn expose() -> Result<()> {
assert_eq!(
parse_single(r#"expose 8000"#, Rule::expose)?
.into_expose()
.unwrap(),
ExposeInstruction {
span: Span::new(0, 11),
vars: vec![ExposePort::new(
Span::new(7, 11),
SpannedShort {
span: Span::new(7, 11),
content: 8000,
},
None,
)],
}
);

assert_eq!(
parse_single(r#"expose 8000/udp"#, Rule::expose)?
.into_expose()
.unwrap(),
ExposeInstruction {
span: Span::new(0, 15),
vars: vec![ExposePort::new(
Span::new(7, 15),
SpannedShort {
span: Span::new(7, 11),
content: 8000,
},
Some(SpannedString {
span: Span::new(12, 15),
content: "udp".to_owned()
}),
)],
}
);

Ok(())
}

#[test]
fn test_multiline_single_env() -> Result<()> {
assert_eq!(
parse_single(
r#"expose 80 8000/udp \
8080/tcp 8096"#,
Rule::expose
)?
.into_expose()
.unwrap(),
ExposeInstruction {
span: Span::new(0, 46),
vars: vec![
ExposePort::new(
Span::new(7, 9),
SpannedShort {
span: Span::new(7, 9),
content: 80,
},
None
),
ExposePort::new(
Span::new(10, 18),
SpannedShort {
span: Span::new(10, 14),
content: 8000,
},
Some(SpannedString {
span: Span::new(15, 18),
content: "udp".to_owned()
})
),
ExposePort::new(
Span::new(33, 41),
SpannedShort {
span: Span::new(33, 37),
content: 8080,
},
Some(SpannedString {
span: Span::new(38, 41),
content: "tcp".to_owned()
})
),
ExposePort::new(
Span::new(42, 46),
SpannedShort {
span: Span::new(42, 46),
content: 8096,
},
None
),
],
}
);

Ok(())
}
}
3 changes: 3 additions & 0 deletions src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ pub use entrypoint::*;
mod cmd;
pub use cmd::*;

mod expose;
pub use expose::*;

mod misc;
pub use misc::*;

29 changes: 29 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ pub(crate) fn parse_string(field: &Pair) -> Result<SpannedString> {
})
}

pub(crate) fn parse_short(field: &Pair) -> Result<SpannedShort> {
let str_span = Span::from_pair(field);
let field_str = field.as_str();

Ok(SpannedShort {
span: str_span,
content: u16::from_str_radix(&field_str, 10).unwrap(),
})
}

/// Removes escaped line breaks (\\\n) from a string
///
/// This should be used to clean any input from the any_breakable rule
Expand Down Expand Up @@ -141,6 +151,25 @@ impl fmt::Display for SpannedString {
}
}

/// A short with a character span.
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone)]
pub struct SpannedShort {
pub span: Span,
pub content: u16,
}

impl AsRef<u16> for SpannedShort {
fn as_ref(&self) -> &u16 {
&self.content
}
}

impl fmt::Display for SpannedShort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.content.fmt(f)
}
}

/// A component of a breakable string.
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone)]
pub enum BreakableStringComponent {
Expand Down

0 comments on commit 3d9038d

Please sign in to comment.