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