1use crate::args_parser::WrapperOptions;
11use crate::recovery_delay::effective_on_kill_resume_delay;
12use std::collections::HashMap;
13
14pub fn docker_runtime_status_lines(
18 volumes: &[String],
19 mounts: &[String],
20 env: &[String],
21 privileged: bool,
22 network: Option<&str>,
23 networks: &[String],
24 network_aliases: &[String],
25) -> Vec<String> {
26 let mut lines = Vec::new();
27 if !volumes.is_empty() {
28 lines.push(format!("[Isolation] Volumes: {}", volumes.join(", ")));
29 }
30 if !mounts.is_empty() {
31 lines.push(format!("[Isolation] Mounts: {}", mounts.join(", ")));
32 }
33 if !env.is_empty() {
34 lines.push(format!("[Isolation] Env: {}", env.join(", ")));
35 }
36 if privileged {
37 lines.push("[Isolation] Privileged: true".to_string());
38 }
39 let resolved_networks = if networks.is_empty() {
40 network.into_iter().collect::<Vec<_>>()
41 } else {
42 networks.iter().map(String::as_str).collect::<Vec<_>>()
43 };
44 if let Some(first_network) = resolved_networks.first() {
45 lines.push(format!("[Isolation] Network: {}", first_network));
46 }
47 if resolved_networks.len() > 1 {
48 lines.push(format!(
49 "[Isolation] Networks: {}",
50 resolved_networks.join(", ")
51 ));
52 }
53 if !network_aliases.is_empty() {
54 lines.push(format!(
55 "[Isolation] Network aliases: {}",
56 network_aliases.join(", ")
57 ));
58 }
59 lines
60}
61
62pub fn docker_runtime_status_lines_for_options(options: &WrapperOptions) -> Vec<String> {
64 let mut lines = docker_runtime_status_lines(
65 &options.volumes,
66 &options.mounts,
67 &options.env,
68 options.privileged,
69 options.network.as_deref(),
70 &options.networks,
71 &options.network_aliases,
72 );
73 lines.extend(recovery_status_lines_with_delay(
74 options.on_kill_resume,
75 options.recovery_command.as_deref(),
76 options.on_kill_resume_delay.as_deref(),
77 ));
78 lines
79}
80
81pub fn recovery_status_lines(
84 on_kill_resume: Option<u32>,
85 recovery_command: Option<&str>,
86) -> Vec<String> {
87 recovery_status_lines_with_delay(on_kill_resume, recovery_command, None)
88}
89
90pub fn recovery_status_lines_with_delay(
93 on_kill_resume: Option<u32>,
94 recovery_command: Option<&str>,
95 on_kill_resume_delay: Option<&str>,
96) -> Vec<String> {
97 let Some(count) = effective_on_kill_resume(on_kill_resume, recovery_command) else {
98 return Vec::new();
99 };
100 let after = effective_on_kill_resume_delay(on_kill_resume_delay)
101 .map(|delay| format!(" after a random {}s delay", delay))
102 .unwrap_or_default();
103 vec![format!(
104 "[Isolation] On kill: resume up to {} time(s){} with {}",
105 count,
106 after,
107 recovery_command.unwrap_or("the original command")
108 )]
109}
110
111fn effective_on_kill_resume(
112 on_kill_resume: Option<u32>,
113 recovery_command: Option<&str>,
114) -> Option<u32> {
115 on_kill_resume
116 .filter(|count| *count > 0)
117 .or_else(|| recovery_command.map(|_| 1))
118}
119
120pub fn recovery_metadata(
122 on_kill_resume: Option<u32>,
123 recovery_command: Option<&str>,
124) -> Vec<(String, serde_json::Value)> {
125 recovery_metadata_with_delay(on_kill_resume, recovery_command, None)
126}
127
128pub fn recovery_metadata_with_delay(
131 on_kill_resume: Option<u32>,
132 recovery_command: Option<&str>,
133 on_kill_resume_delay: Option<&str>,
134) -> Vec<(String, serde_json::Value)> {
135 let mut entries = Vec::new();
136 let Some(count) = effective_on_kill_resume(on_kill_resume, recovery_command) else {
137 return entries;
138 };
139 entries.push(("onKillResume".to_string(), serde_json::json!(count)));
140 if let Some(command) = recovery_command {
141 entries.push(("recoveryCommand".to_string(), serde_json::json!(command)));
142 }
143 if let Some(delay) = effective_on_kill_resume_delay(on_kill_resume_delay) {
144 entries.push(("onKillResumeDelay".to_string(), serde_json::json!(delay)));
145 }
146 entries
147}
148
149pub fn docker_runtime_metadata(
153 volumes: &[String],
154 mounts: &[String],
155 env: &[String],
156 privileged: bool,
157 network: Option<&str>,
158 networks: &[String],
159 network_aliases: &[String],
160) -> Vec<(String, serde_json::Value)> {
161 let arr = |items: &[String]| {
162 serde_json::Value::Array(
163 items
164 .iter()
165 .map(|s| serde_json::Value::String(s.clone()))
166 .collect(),
167 )
168 };
169 let mut entries = Vec::new();
170 if !volumes.is_empty() {
171 entries.push(("volumes".to_string(), arr(volumes)));
172 }
173 if !mounts.is_empty() {
174 entries.push(("mounts".to_string(), arr(mounts)));
175 }
176 if !env.is_empty() {
177 entries.push(("env".to_string(), arr(env)));
178 }
179 if privileged {
180 entries.push(("privileged".to_string(), serde_json::Value::Bool(true)));
181 }
182 let resolved_networks = if networks.is_empty() {
183 network.into_iter().collect::<Vec<_>>()
184 } else {
185 networks.iter().map(String::as_str).collect::<Vec<_>>()
186 };
187 if let Some(network) = resolved_networks.first() {
188 entries.push((
189 "network".to_string(),
190 serde_json::Value::String((*network).to_string()),
191 ));
192 entries.push((
193 "networks".to_string(),
194 serde_json::Value::Array(
195 resolved_networks
196 .iter()
197 .map(|value| serde_json::Value::String((*value).to_string()))
198 .collect(),
199 ),
200 ));
201 }
202 if !network_aliases.is_empty() {
203 entries.push(("networkAliases".to_string(), arr(network_aliases)));
204 }
205 entries
206}
207
208pub fn build_isolation_options_map(
213 environment: Option<&str>,
214 mode: &str,
215 session_name: &str,
216 effective_image: Option<&str>,
217 options: &WrapperOptions,
218 created_user: Option<&str>,
219) -> HashMap<String, serde_json::Value> {
220 let str_val = |s: &str| serde_json::Value::String(s.to_string());
221 let mut opts_map = HashMap::new();
222 if let Some(env) = environment {
223 opts_map.insert("isolated".to_string(), str_val(env));
224 }
225 opts_map.insert("isolationMode".to_string(), str_val(mode));
226 opts_map.insert("sessionName".to_string(), str_val(session_name));
227 if let Some(v) = effective_image {
228 opts_map.insert("image".to_string(), str_val(v));
229 }
230 for (k, v) in docker_runtime_metadata(
231 &options.volumes,
232 &options.mounts,
233 &options.env,
234 options.privileged,
235 options.network.as_deref(),
236 &options.networks,
237 &options.network_aliases,
238 )
239 .into_iter()
240 .chain(recovery_metadata_with_delay(
241 options.on_kill_resume,
242 options.recovery_command.as_deref(),
243 options.on_kill_resume_delay.as_deref(),
244 )) {
245 opts_map.insert(k, v);
246 }
247 if let Some(v) = &options.endpoint {
248 opts_map.insert("endpoint".to_string(), str_val(v));
249 }
250 if let Some(v) = created_user {
251 opts_map.insert("user".to_string(), str_val(v));
252 }
253 opts_map.insert(
254 "keepAlive".to_string(),
255 serde_json::Value::Bool(options.keep_alive),
256 );
257 opts_map.insert(
258 "autoRemoveDockerContainer".to_string(),
259 serde_json::Value::Bool(options.auto_remove_docker_container),
260 );
261 opts_map.insert(
262 "alwaysCleanupContainer".to_string(),
263 serde_json::Value::Bool(options.always_cleanup_container),
264 );
265 opts_map.insert(
266 "keepContainer".to_string(),
267 serde_json::Value::Bool(options.keep_container),
268 );
269 opts_map.insert(
270 "keepContainerOnFail".to_string(),
271 serde_json::Value::Bool(options.keep_container_on_fail),
272 );
273 opts_map
274}
275
276#[cfg(test)]
277#[path = "isolation_metadata_cases.rs"]
278mod tests;