earl_protocol_bash/
builder.rs1use anyhow::{Result, bail};
2use serde_json::Value;
3
4use crate::schema::BashOperationTemplate;
5use crate::{PreparedBashScript, ResolvedBashSandbox};
6use earl_core::render::{TemplateRenderer, render_key_value_map};
7
8#[derive(Debug, Clone, Default)]
10pub struct GlobalBashLimits {
11 pub allow_network: bool,
12 pub max_time_ms: Option<u64>,
13 pub max_output_bytes: Option<u64>,
14}
15
16pub fn build_bash_request(
18 bash_op: &BashOperationTemplate,
19 context: &Value,
20 renderer: &dyn TemplateRenderer,
21 global_limits: &GlobalBashLimits,
22) -> Result<PreparedBashScript> {
23 let script = renderer.render_str(&bash_op.bash.script, context)?;
24 if script.trim().is_empty() {
25 bail!("operation.bash.script rendered empty");
26 }
27
28 let env = render_key_value_map(bash_op.bash.env.as_ref(), context, renderer)?;
29
30 let cwd = bash_op
31 .bash
32 .cwd
33 .as_ref()
34 .map(|value| renderer.render_str(value, context))
35 .transpose()?
36 .filter(|value| !value.trim().is_empty());
37
38 let template_sandbox = &bash_op.bash.sandbox;
41
42 let network = template_sandbox
43 .as_ref()
44 .and_then(|s| s.network)
45 .unwrap_or(false)
46 && global_limits.allow_network;
47
48 let writable_paths = template_sandbox
49 .as_ref()
50 .and_then(|s| s.writable_paths.clone())
51 .unwrap_or_default();
52
53 let max_time_ms = most_restrictive_option(
54 template_sandbox.as_ref().and_then(|s| s.max_time_ms),
55 global_limits.max_time_ms,
56 );
57
58 let max_output_bytes = most_restrictive_option(
59 template_sandbox.as_ref().and_then(|s| s.max_output_bytes),
60 global_limits.max_output_bytes,
61 )
62 .map(|v| v as usize);
63
64 Ok(PreparedBashScript {
65 script,
66 env,
67 cwd,
68 stdin: None,
69 sandbox: ResolvedBashSandbox {
70 network,
71 writable_paths,
72 max_time_ms,
73 max_output_bytes,
74 },
75 })
76}
77
78fn most_restrictive_option(a: Option<u64>, b: Option<u64>) -> Option<u64> {
80 match (a, b) {
81 (Some(a), Some(b)) => Some(a.min(b)),
82 (Some(a), None) => Some(a),
83 (None, Some(b)) => Some(b),
84 (None, None) => None,
85 }
86}