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 if let Some(line) = crate::docker_resource_options::log_line(
74 &crate::docker_resource_options::specs(options),
75 &options.resolved_limits,
76 ) {
77 lines.push(line);
78 }
79 lines.extend(recovery_status_lines_with_delay(
80 options.on_kill_resume,
81 options.recovery_command.as_deref(),
82 options.on_kill_resume_delay.as_deref(),
83 ));
84 lines
85}
86
87pub fn recovery_status_lines(
90 on_kill_resume: Option<u32>,
91 recovery_command: Option<&str>,
92) -> Vec<String> {
93 recovery_status_lines_with_delay(on_kill_resume, recovery_command, None)
94}
95
96pub fn recovery_status_lines_with_delay(
99 on_kill_resume: Option<u32>,
100 recovery_command: Option<&str>,
101 on_kill_resume_delay: Option<&str>,
102) -> Vec<String> {
103 let Some(count) = effective_on_kill_resume(on_kill_resume, recovery_command) else {
104 return Vec::new();
105 };
106 let after = effective_on_kill_resume_delay(on_kill_resume_delay)
107 .map(|delay| format!(" after a random {}s delay", delay))
108 .unwrap_or_default();
109 vec![format!(
110 "[Isolation] On kill: resume up to {} time(s){} with {}",
111 count,
112 after,
113 recovery_command.unwrap_or("the original command")
114 )]
115}
116
117fn effective_on_kill_resume(
118 on_kill_resume: Option<u32>,
119 recovery_command: Option<&str>,
120) -> Option<u32> {
121 on_kill_resume
122 .filter(|count| *count > 0)
123 .or_else(|| recovery_command.map(|_| 1))
124}
125
126pub fn recovery_metadata(
128 on_kill_resume: Option<u32>,
129 recovery_command: Option<&str>,
130) -> Vec<(String, serde_json::Value)> {
131 recovery_metadata_with_delay(on_kill_resume, recovery_command, None)
132}
133
134pub fn recovery_metadata_with_delay(
137 on_kill_resume: Option<u32>,
138 recovery_command: Option<&str>,
139 on_kill_resume_delay: Option<&str>,
140) -> Vec<(String, serde_json::Value)> {
141 let mut entries = Vec::new();
142 let Some(count) = effective_on_kill_resume(on_kill_resume, recovery_command) else {
143 return entries;
144 };
145 entries.push(("onKillResume".to_string(), serde_json::json!(count)));
146 if let Some(command) = recovery_command {
147 entries.push(("recoveryCommand".to_string(), serde_json::json!(command)));
148 }
149 if let Some(delay) = effective_on_kill_resume_delay(on_kill_resume_delay) {
150 entries.push(("onKillResumeDelay".to_string(), serde_json::json!(delay)));
151 }
152 entries
153}
154
155pub fn docker_runtime_metadata(
159 volumes: &[String],
160 mounts: &[String],
161 env: &[String],
162 privileged: bool,
163 network: Option<&str>,
164 networks: &[String],
165 network_aliases: &[String],
166) -> Vec<(String, serde_json::Value)> {
167 let arr = |items: &[String]| {
168 serde_json::Value::Array(
169 items
170 .iter()
171 .map(|s| serde_json::Value::String(s.clone()))
172 .collect(),
173 )
174 };
175 let mut entries = Vec::new();
176 if !volumes.is_empty() {
177 entries.push(("volumes".to_string(), arr(volumes)));
178 }
179 if !mounts.is_empty() {
180 entries.push(("mounts".to_string(), arr(mounts)));
181 }
182 if !env.is_empty() {
183 entries.push(("env".to_string(), arr(env)));
184 }
185 if privileged {
186 entries.push(("privileged".to_string(), serde_json::Value::Bool(true)));
187 }
188 let resolved_networks = if networks.is_empty() {
189 network.into_iter().collect::<Vec<_>>()
190 } else {
191 networks.iter().map(String::as_str).collect::<Vec<_>>()
192 };
193 if let Some(network) = resolved_networks.first() {
194 entries.push((
195 "network".to_string(),
196 serde_json::Value::String((*network).to_string()),
197 ));
198 entries.push((
199 "networks".to_string(),
200 serde_json::Value::Array(
201 resolved_networks
202 .iter()
203 .map(|value| serde_json::Value::String((*value).to_string()))
204 .collect(),
205 ),
206 ));
207 }
208 if !network_aliases.is_empty() {
209 entries.push(("networkAliases".to_string(), arr(network_aliases)));
210 }
211 entries
212}
213
214pub fn build_isolation_options_map(
219 environment: Option<&str>,
220 mode: &str,
221 session_name: &str,
222 effective_image: Option<&str>,
223 options: &WrapperOptions,
224 created_user: Option<&str>,
225) -> HashMap<String, serde_json::Value> {
226 let str_val = |s: &str| serde_json::Value::String(s.to_string());
227 let mut opts_map = HashMap::new();
228 if let Some(env) = environment {
229 opts_map.insert("isolated".to_string(), str_val(env));
230 }
231 opts_map.insert("isolationMode".to_string(), str_val(mode));
232 opts_map.insert("sessionName".to_string(), str_val(session_name));
233 if let Some(v) = effective_image {
234 opts_map.insert("image".to_string(), str_val(v));
235 }
236 for (k, v) in docker_runtime_metadata(
237 &options.volumes,
238 &options.mounts,
239 &options.env,
240 options.privileged,
241 options.network.as_deref(),
242 &options.networks,
243 &options.network_aliases,
244 )
245 .into_iter()
246 .chain(recovery_metadata_with_delay(
247 options.on_kill_resume,
248 options.recovery_command.as_deref(),
249 options.on_kill_resume_delay.as_deref(),
250 )) {
251 opts_map.insert(k, v);
252 }
253 if let Some(config) = &options.cpu_penalty_config {
254 opts_map.insert("cpuPenaltyConfig".into(), serde_json::json!(config));
255 opts_map.insert(
256 "baseResourceLimits".into(),
257 serde_json::json!(options.resource_limits),
258 );
259 }
260 opts_map.insert(
261 "resourceLimits".into(),
262 serde_json::json!(options.resource_limits),
263 );
264 opts_map.insert("resolvedLimits".into(), options.resolved_limits.clone());
265 opts_map.insert(
266 "resourceLimitSpecs".into(),
267 crate::docker_resource_options::specs(options),
268 );
269 if let Some(value) = &options.on_kill_resume_memory {
270 opts_map.insert("onKillResumeMemory".into(), serde_json::json!(value));
271 }
272 if let Some(v) = &options.endpoint {
273 opts_map.insert("endpoint".to_string(), str_val(v));
274 }
275 if let Some(v) = created_user {
276 opts_map.insert("user".to_string(), str_val(v));
277 }
278 opts_map.insert(
279 "keepAlive".to_string(),
280 serde_json::Value::Bool(options.keep_alive),
281 );
282 opts_map.insert(
283 "autoRemoveDockerContainer".to_string(),
284 serde_json::Value::Bool(options.auto_remove_docker_container),
285 );
286 opts_map.insert(
287 "alwaysCleanupContainer".to_string(),
288 serde_json::Value::Bool(options.always_cleanup_container),
289 );
290 opts_map.insert(
291 "keepContainer".to_string(),
292 serde_json::Value::Bool(options.keep_container),
293 );
294 opts_map.insert(
295 "keepContainerOnFail".to_string(),
296 serde_json::Value::Bool(options.keep_container_on_fail),
297 );
298 opts_map
299}
300
301#[cfg(test)]
302#[path = "isolation_metadata_cases.rs"]
303mod tests;