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 User {
50 user: String,
51 group: Option<String>,
52 },
53 Volume {
54 mounts: Vec<String>,
55 },
56 Workdir {
57 path: String,
58 },
59 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 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}