1use 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 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 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}