Skip to main content

start_command/
isolation_metadata.rs

1//! Metadata helpers for isolated executions.
2//!
3//! Builds the human-readable `[Isolation]` status lines and the execution
4//! record options map that describe how an isolated command was launched,
5//! including the configurable Docker runtime options (volumes, mounts,
6//! environment variables, privileged mode). Kept separate from `isolation`
7//! so the runtime backends and the metadata representation can evolve
8//! independently.
9
10use crate::args_parser::WrapperOptions;
11use std::collections::HashMap;
12
13/// Build the human-readable `[Isolation]` status lines for docker runtime
14/// options (volumes, mounts, env, privileged). Used for the start block and
15/// log header; empty collections contribute no lines.
16pub fn docker_runtime_status_lines(
17    volumes: &[String],
18    mounts: &[String],
19    env: &[String],
20    privileged: bool,
21    network: Option<&str>,
22    network_aliases: &[String],
23) -> Vec<String> {
24    let mut lines = Vec::new();
25    if !volumes.is_empty() {
26        lines.push(format!("[Isolation] Volumes: {}", volumes.join(", ")));
27    }
28    if !mounts.is_empty() {
29        lines.push(format!("[Isolation] Mounts: {}", mounts.join(", ")));
30    }
31    if !env.is_empty() {
32        lines.push(format!("[Isolation] Env: {}", env.join(", ")));
33    }
34    if privileged {
35        lines.push("[Isolation] Privileged: true".to_string());
36    }
37    if let Some(network) = network {
38        lines.push(format!("[Isolation] Network: {}", network));
39    }
40    if !network_aliases.is_empty() {
41        lines.push(format!(
42            "[Isolation] Network aliases: {}",
43            network_aliases.join(", ")
44        ));
45    }
46    lines
47}
48
49/// Build Docker runtime status lines directly from parsed wrapper options.
50pub fn docker_runtime_status_lines_for_options(options: &WrapperOptions) -> Vec<String> {
51    docker_runtime_status_lines(
52        &options.volumes,
53        &options.mounts,
54        &options.env,
55        options.privileged,
56        options.network.as_deref(),
57        &options.network_aliases,
58    )
59}
60
61/// Build the execution-record metadata entries for docker runtime options.
62/// Returns `(key, value)` pairs to merge into the options map; empty
63/// collections and a false `privileged` flag contribute no entries.
64pub fn docker_runtime_metadata(
65    volumes: &[String],
66    mounts: &[String],
67    env: &[String],
68    privileged: bool,
69    network: Option<&str>,
70    network_aliases: &[String],
71) -> Vec<(String, serde_json::Value)> {
72    let arr = |items: &[String]| {
73        serde_json::Value::Array(
74            items
75                .iter()
76                .map(|s| serde_json::Value::String(s.clone()))
77                .collect(),
78        )
79    };
80    let mut entries = Vec::new();
81    if !volumes.is_empty() {
82        entries.push(("volumes".to_string(), arr(volumes)));
83    }
84    if !mounts.is_empty() {
85        entries.push(("mounts".to_string(), arr(mounts)));
86    }
87    if !env.is_empty() {
88        entries.push(("env".to_string(), arr(env)));
89    }
90    if privileged {
91        entries.push(("privileged".to_string(), serde_json::Value::Bool(true)));
92    }
93    if let Some(network) = network {
94        entries.push((
95            "network".to_string(),
96            serde_json::Value::String(network.to_string()),
97        ));
98    }
99    if !network_aliases.is_empty() {
100        entries.push(("networkAliases".to_string(), arr(network_aliases)));
101    }
102    entries
103}
104
105/// Build the execution-record options map describing how an isolated command
106/// was launched (environment, mode, session, image, docker runtime options,
107/// endpoint, user, keep-alive). Used to persist the execution record so it can
108/// be surfaced via `--status`/`--list`.
109pub fn build_isolation_options_map(
110    environment: Option<&str>,
111    mode: &str,
112    session_name: &str,
113    effective_image: Option<&str>,
114    options: &WrapperOptions,
115    created_user: Option<&str>,
116) -> HashMap<String, serde_json::Value> {
117    let str_val = |s: &str| serde_json::Value::String(s.to_string());
118    let mut opts_map = HashMap::new();
119    if let Some(env) = environment {
120        opts_map.insert("isolated".to_string(), str_val(env));
121    }
122    opts_map.insert("isolationMode".to_string(), str_val(mode));
123    opts_map.insert("sessionName".to_string(), str_val(session_name));
124    if let Some(v) = effective_image {
125        opts_map.insert("image".to_string(), str_val(v));
126    }
127    for (k, v) in docker_runtime_metadata(
128        &options.volumes,
129        &options.mounts,
130        &options.env,
131        options.privileged,
132        options.network.as_deref(),
133        &options.network_aliases,
134    ) {
135        opts_map.insert(k, v);
136    }
137    if let Some(v) = &options.endpoint {
138        opts_map.insert("endpoint".to_string(), str_val(v));
139    }
140    if let Some(v) = created_user {
141        opts_map.insert("user".to_string(), str_val(v));
142    }
143    opts_map.insert(
144        "keepAlive".to_string(),
145        serde_json::Value::Bool(options.keep_alive),
146    );
147    opts_map.insert(
148        "autoRemoveDockerContainer".to_string(),
149        serde_json::Value::Bool(options.auto_remove_docker_container),
150    );
151    opts_map.insert(
152        "alwaysCleanupContainer".to_string(),
153        serde_json::Value::Bool(options.always_cleanup_container),
154    );
155    opts_map.insert(
156        "keepContainer".to_string(),
157        serde_json::Value::Bool(options.keep_container),
158    );
159    opts_map.insert(
160        "keepContainerOnFail".to_string(),
161        serde_json::Value::Bool(options.keep_container_on_fail),
162    );
163    opts_map
164}
165
166#[cfg(test)]
167#[path = "isolation_metadata_cases.rs"]
168mod tests;