dockerfile_parser_rs/
ast.rs

1// https://docs.docker.com/reference/dockerfile/#overview
2
3use std::collections::HashMap;
4use std::fmt;
5
6use strum_macros::EnumString;
7
8#[derive(Debug)]
9pub enum Instruction {
10    Add {
11        checksum: Option<String>,
12        chown: Option<String>,
13        chmod: Option<String>,
14        link: Option<String>,
15        sources: Vec<String>,
16        destination: String,
17    },
18    Arg {
19        name: String,
20        default: Option<String>,
21    },
22    Cmd(Vec<String>),
23    Copy {
24        from: Option<String>,
25        chown: Option<String>,
26        chmod: Option<String>,
27        link: Option<String>,
28        sources: Vec<String>,
29        destination: String,
30    },
31    Entrypoint(Vec<String>),
32    Env(HashMap<String, String>),
33    Expose {
34        port: String,
35        protocol: Option<Protocol>,
36    },
37    From {
38        platform: Option<String>,
39        image: String,
40        alias: Option<String>,
41    },
42    Label(HashMap<String, String>),
43    Run {
44        mount: Option<String>,
45        network: Option<String>,
46        security: Option<String>,
47        command: Vec<String>,
48    },
49    Shell(Vec<String>),
50    User {
51        user: String,
52        group: Option<String>,
53    },
54    Volume {
55        mounts: Vec<String>,
56    },
57    Workdir {
58        path: String,
59    },
60    //-------------//
61    //    Extra    //
62    //-------------//
63    Comment(String),
64    Empty,
65}
66
67impl fmt::Display for Instruction {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        match self {
70            Instruction::Add {
71                checksum,
72                chown,
73                chmod,
74                link,
75                sources,
76                destination,
77            } => {
78                let options = vec![
79                    format_option("checksum", checksum.clone()),
80                    format_option("chown", chown.clone()),
81                    format_option("chmod", chmod.clone()),
82                    format_option("link", link.clone()),
83                ];
84                let options_string = options
85                    .into_iter()
86                    .filter(|s| !s.is_empty())
87                    .collect::<Vec<String>>()
88                    .join(" ");
89                write!(
90                    f,
91                    "ADD {}{} {}",
92                    options_string,
93                    sources.join(" "),
94                    destination
95                )
96            }
97            Instruction::Arg { name, default } => {
98                if let Some(default) = default {
99                    write!(f, "ARG {}={}", name, default)
100                } else {
101                    write!(f, "ARG {}", name)
102                }
103            }
104            Instruction::Cmd(cmd) => write!(f, "CMD {:?}", cmd),
105            Instruction::Copy {
106                from,
107                chown,
108                chmod,
109                link,
110                sources,
111                destination,
112            } => {
113                let options = vec![
114                    format_option("from", from.clone()),
115                    format_option("chown", chown.clone()),
116                    format_option("chmod", chmod.clone()),
117                    format_option("link", link.clone()),
118                ];
119                let options_string = options
120                    .into_iter()
121                    .filter(|s| !s.is_empty())
122                    .collect::<Vec<String>>()
123                    .join(" ");
124                write!(
125                    f,
126                    "COPY {}{} {}",
127                    options_string,
128                    sources.join(" "),
129                    destination
130                )
131            }
132            Instruction::Entrypoint(entrypoint) => write!(f, "ENTRYPOINT {:?}", entrypoint),
133            Instruction::Env(env) => {
134                let mut env_vars = Vec::new();
135                for (key, value) in env {
136                    env_vars.push(format!("{}=\"{}\"", key, value));
137                }
138                write!(f, "ENV {}", env_vars.join(" "))
139            }
140            Instruction::Expose { port, protocol } => {
141                if let Some(protocol) = protocol {
142                    write!(f, "EXPOSE {}/{:?}", port, protocol)
143                } else {
144                    write!(f, "EXPOSE {}", port)
145                }
146            }
147            Instruction::From {
148                platform,
149                image,
150                alias,
151            } => {
152                let mut line = String::new();
153                if let Some(platform) = platform {
154                    line.push_str(&format!("--platform={} ", platform));
155                }
156                line.push_str(image);
157                if let Some(alias) = alias {
158                    line.push_str(&format!(" AS {}", alias));
159                }
160                write!(f, "FROM {}", line)
161            }
162            Instruction::Label(labels) => {
163                let mut label_pairs = Vec::new();
164                for (key, value) in labels {
165                    label_pairs.push(format!("{}=\"{}\"", key, value));
166                }
167                write!(f, "LABEL {}", label_pairs.join(" "))
168            }
169            Instruction::Run {
170                mount,
171                network,
172                security,
173                command,
174            } => {
175                let options = vec![
176                    format_option("mount", mount.clone()),
177                    format_option("network", network.clone()),
178                    format_option("security", security.clone()),
179                ];
180                let options_string = options
181                    .into_iter()
182                    .filter(|s| !s.is_empty())
183                    .collect::<Vec<String>>()
184                    .join(" ");
185                write!(f, "RUN {}{:?}", options_string, command)
186            }
187            Instruction::Shell(shell) => write!(f, "SHELL {:?}", shell),
188            Instruction::User { user, group } => {
189                if let Some(group) = group {
190                    write!(f, "USER {}:{}", user, group)
191                } else {
192                    write!(f, "USER {}", user)
193                }
194            }
195            Instruction::Volume { mounts } => write!(f, "VOLUME {:?}", mounts),
196            Instruction::Workdir { path } => write!(f, "WORKDIR {}", path),
197            //-------------//
198            //    Extra    //
199            //-------------//
200            Instruction::Comment(comment) => write!(f, "{}", comment),
201            Instruction::Empty => write!(f, ""),
202        }
203    }
204}
205
206fn format_option(key: &str, value: Option<String>) -> String {
207    value
208        .map(|v| format!("--{}={}", key, v))
209        .unwrap_or_default()
210}
211
212#[derive(Debug, EnumString)]
213#[strum(serialize_all = "lowercase")]
214pub enum Protocol {
215    Tcp,
216    Udp,
217}