documenting action spec
This commit is contained in:
@@ -106,10 +106,7 @@ impl ActionExecutor {
|
||||
let is_success = result.is_success();
|
||||
debug!(
|
||||
"Execution {} result: exit_code={}, error={:?}, is_success={}",
|
||||
execution_id,
|
||||
result.exit_code,
|
||||
result.error,
|
||||
is_success
|
||||
execution_id, result.exit_code, result.error, is_success
|
||||
);
|
||||
|
||||
if is_success {
|
||||
@@ -232,7 +229,11 @@ impl ActionExecutor {
|
||||
info!("No execution config present");
|
||||
}
|
||||
|
||||
info!("Extracted {} parameters: {:?}", parameters.len(), parameters);
|
||||
info!(
|
||||
"Extracted {} parameters: {:?}",
|
||||
parameters.len(),
|
||||
parameters
|
||||
);
|
||||
|
||||
// Prepare standard environment variables
|
||||
let mut env = HashMap::new();
|
||||
@@ -383,6 +384,7 @@ impl ActionExecutor {
|
||||
max_stderr_bytes: self.max_stderr_bytes,
|
||||
parameter_delivery: action.parameter_delivery,
|
||||
parameter_format: action.parameter_format,
|
||||
output_format: action.output_format,
|
||||
};
|
||||
|
||||
Ok(context)
|
||||
@@ -538,7 +540,8 @@ impl ActionExecutor {
|
||||
if stderr_path.exists() {
|
||||
if let Ok(contents) = tokio::fs::read_to_string(&stderr_path).await {
|
||||
if !contents.trim().is_empty() {
|
||||
result_data["stderr_log"] = serde_json::json!(stderr_path.to_string_lossy());
|
||||
result_data["stderr_log"] =
|
||||
serde_json::json!(stderr_path.to_string_lossy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
use super::native::NativeRuntime;
|
||||
use super::python::PythonRuntime;
|
||||
use super::shell::ShellRuntime;
|
||||
use super::{ExecutionContext, ExecutionResult, Runtime, RuntimeError, RuntimeResult};
|
||||
use super::{
|
||||
ExecutionContext, ExecutionResult, OutputFormat, Runtime, RuntimeError, RuntimeResult,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use tracing::{debug, info};
|
||||
|
||||
@@ -123,6 +125,7 @@ impl Runtime for LocalRuntime {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::runtime::{ParameterDelivery, ParameterFormat};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -149,6 +152,9 @@ def run():
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: ParameterDelivery::default(),
|
||||
parameter_format: ParameterFormat::default(),
|
||||
output_format: OutputFormat::default(),
|
||||
};
|
||||
|
||||
assert!(runtime.can_execute(&context));
|
||||
@@ -168,12 +174,15 @@ def run():
|
||||
secrets: HashMap::new(),
|
||||
timeout: Some(10),
|
||||
working_dir: None,
|
||||
entry_point: "shell".to_string(),
|
||||
code: Some("echo 'hello from shell'".to_string()),
|
||||
entry_point: "run.sh".to_string(),
|
||||
code: Some("#!/bin/bash\necho 'hello from shell'".to_string()),
|
||||
code_path: None,
|
||||
runtime_name: Some("shell".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: ParameterDelivery::default(),
|
||||
parameter_format: ParameterFormat::default(),
|
||||
output_format: OutputFormat::default(),
|
||||
};
|
||||
|
||||
assert!(runtime.can_execute(&context));
|
||||
@@ -194,12 +203,15 @@ def run():
|
||||
secrets: HashMap::new(),
|
||||
timeout: Some(10),
|
||||
working_dir: None,
|
||||
entry_point: "unknown".to_string(),
|
||||
entry_point: "run".to_string(),
|
||||
code: Some("some code".to_string()),
|
||||
code_path: None,
|
||||
runtime_name: None,
|
||||
runtime_name: Some("unknown".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: ParameterDelivery::default(),
|
||||
parameter_format: ParameterFormat::default(),
|
||||
output_format: OutputFormat::default(),
|
||||
};
|
||||
|
||||
assert!(!runtime.can_execute(&context));
|
||||
|
||||
@@ -19,7 +19,6 @@ pub use python::PythonRuntime;
|
||||
pub use shell::ShellRuntime;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use attune_common::models::{ParameterDelivery, ParameterFormat};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
@@ -34,6 +33,9 @@ pub use log_writer::{BoundedLogResult, BoundedLogWriter};
|
||||
pub use parameter_passing::{ParameterDeliveryConfig, PreparedParameters};
|
||||
pub use python_venv::PythonVenvManager;
|
||||
|
||||
// Re-export parameter types from common
|
||||
pub use attune_common::models::{OutputFormat, ParameterDelivery, ParameterFormat};
|
||||
|
||||
/// Runtime execution result
|
||||
pub type RuntimeResult<T> = std::result::Result<T, RuntimeError>;
|
||||
|
||||
@@ -119,6 +121,10 @@ pub struct ExecutionContext {
|
||||
/// Format for parameter serialization
|
||||
#[serde(default)]
|
||||
pub parameter_format: ParameterFormat,
|
||||
|
||||
/// Format for output parsing
|
||||
#[serde(default)]
|
||||
pub output_format: OutputFormat,
|
||||
}
|
||||
|
||||
fn default_max_log_bytes() -> usize {
|
||||
@@ -146,6 +152,7 @@ impl ExecutionContext {
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: ParameterDelivery::default(),
|
||||
parameter_format: ParameterFormat::default(),
|
||||
output_format: OutputFormat::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,20 +46,14 @@ fn format_dotenv(parameters: &HashMap<String, JsonValue>) -> Result<String, Runt
|
||||
/// Format parameters as JSON
|
||||
fn format_json(parameters: &HashMap<String, JsonValue>) -> Result<String, RuntimeError> {
|
||||
serde_json::to_string_pretty(parameters).map_err(|e| {
|
||||
RuntimeError::ExecutionFailed(format!(
|
||||
"Failed to serialize parameters to JSON: {}",
|
||||
e
|
||||
))
|
||||
RuntimeError::ExecutionFailed(format!("Failed to serialize parameters to JSON: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
/// Format parameters as YAML
|
||||
fn format_yaml(parameters: &HashMap<String, JsonValue>) -> Result<String, RuntimeError> {
|
||||
serde_yaml_ng::to_string(parameters).map_err(|e| {
|
||||
RuntimeError::ExecutionFailed(format!(
|
||||
"Failed to serialize parameters to YAML: {}",
|
||||
e
|
||||
))
|
||||
RuntimeError::ExecutionFailed(format!("Failed to serialize parameters to YAML: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -81,18 +75,21 @@ pub fn create_parameter_file(
|
||||
) -> Result<NamedTempFile, RuntimeError> {
|
||||
let formatted = format_parameters(parameters, format)?;
|
||||
|
||||
let mut temp_file = NamedTempFile::new()
|
||||
.map_err(|e| RuntimeError::IoError(e))?;
|
||||
let mut temp_file = NamedTempFile::new().map_err(|e| RuntimeError::IoError(e))?;
|
||||
|
||||
// Set restrictive permissions (owner read-only)
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = temp_file.as_file().metadata()
|
||||
let mut perms = temp_file
|
||||
.as_file()
|
||||
.metadata()
|
||||
.map_err(|e| RuntimeError::IoError(e))?
|
||||
.permissions();
|
||||
perms.set_mode(0o400); // Read-only for owner
|
||||
temp_file.as_file().set_permissions(perms)
|
||||
temp_file
|
||||
.as_file()
|
||||
.set_permissions(perms)
|
||||
.map_err(|e| RuntimeError::IoError(e))?;
|
||||
}
|
||||
|
||||
@@ -100,9 +97,7 @@ pub fn create_parameter_file(
|
||||
.write_all(formatted.as_bytes())
|
||||
.map_err(|e| RuntimeError::IoError(e))?;
|
||||
|
||||
temp_file
|
||||
.flush()
|
||||
.map_err(|e| RuntimeError::IoError(e))?;
|
||||
temp_file.flush().map_err(|e| RuntimeError::IoError(e))?;
|
||||
|
||||
debug!(
|
||||
"Created parameter file at {:?} with format {:?}",
|
||||
@@ -165,10 +160,7 @@ pub fn prepare_parameters(
|
||||
let formatted = format_parameters(parameters, config.format)?;
|
||||
|
||||
// Add environment variables to indicate delivery method
|
||||
env.insert(
|
||||
"ATTUNE_PARAMETER_DELIVERY".to_string(),
|
||||
"stdin".to_string(),
|
||||
);
|
||||
env.insert("ATTUNE_PARAMETER_DELIVERY".to_string(), "stdin".to_string());
|
||||
env.insert(
|
||||
"ATTUNE_PARAMETER_FORMAT".to_string(),
|
||||
config.format.to_string(),
|
||||
@@ -182,10 +174,7 @@ pub fn prepare_parameters(
|
||||
let path = temp_file.path().to_path_buf();
|
||||
|
||||
// Add environment variables to indicate delivery method and file location
|
||||
env.insert(
|
||||
"ATTUNE_PARAMETER_DELIVERY".to_string(),
|
||||
"file".to_string(),
|
||||
);
|
||||
env.insert("ATTUNE_PARAMETER_DELIVERY".to_string(), "file".to_string());
|
||||
env.insert(
|
||||
"ATTUNE_PARAMETER_FORMAT".to_string(),
|
||||
config.format.to_string(),
|
||||
@@ -256,7 +245,6 @@ mod tests {
|
||||
assert!(result.contains("42"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
fn test_create_parameter_file() {
|
||||
let mut params = HashMap::new();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use super::{
|
||||
BoundedLogWriter, DependencyManagerRegistry, DependencySpec, ExecutionContext, ExecutionResult,
|
||||
Runtime, RuntimeError, RuntimeResult,
|
||||
OutputFormat, Runtime, RuntimeError, RuntimeResult,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use std::path::PathBuf;
|
||||
@@ -214,6 +214,7 @@ if __name__ == '__main__':
|
||||
timeout_secs: Option<u64>,
|
||||
max_stdout_bytes: usize,
|
||||
max_stderr_bytes: usize,
|
||||
output_format: OutputFormat,
|
||||
) -> RuntimeResult<ExecutionResult> {
|
||||
let start = Instant::now();
|
||||
|
||||
@@ -330,13 +331,41 @@ if __name__ == '__main__':
|
||||
exit_code, duration_ms, stdout_result.truncated, stderr_result.truncated
|
||||
);
|
||||
|
||||
// Try to parse result from stdout
|
||||
let result = if exit_code == 0 {
|
||||
stdout_result
|
||||
.content
|
||||
.lines()
|
||||
.last()
|
||||
.and_then(|line| serde_json::from_str(line).ok())
|
||||
// Parse result from stdout based on output_format
|
||||
let result = if exit_code == 0 && !stdout_result.content.trim().is_empty() {
|
||||
match output_format {
|
||||
OutputFormat::Text => {
|
||||
// No parsing - text output is captured in stdout field
|
||||
None
|
||||
}
|
||||
OutputFormat::Json => {
|
||||
// Try to parse last line of stdout as JSON
|
||||
stdout_result
|
||||
.content
|
||||
.trim()
|
||||
.lines()
|
||||
.last()
|
||||
.and_then(|line| serde_json::from_str(line).ok())
|
||||
}
|
||||
OutputFormat::Yaml => {
|
||||
// Try to parse stdout as YAML
|
||||
serde_yaml_ng::from_str(stdout_result.content.trim()).ok()
|
||||
}
|
||||
OutputFormat::Jsonl => {
|
||||
// Parse each line as JSON and collect into array
|
||||
let mut items = Vec::new();
|
||||
for line in stdout_result.content.trim().lines() {
|
||||
if let Ok(value) = serde_json::from_str::<serde_json::Value>(line) {
|
||||
items.push(value);
|
||||
}
|
||||
}
|
||||
if items.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(serde_json::Value::Array(items))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -368,6 +397,7 @@ if __name__ == '__main__':
|
||||
python_path: PathBuf,
|
||||
max_stdout_bytes: usize,
|
||||
max_stderr_bytes: usize,
|
||||
output_format: OutputFormat,
|
||||
) -> RuntimeResult<ExecutionResult> {
|
||||
debug!(
|
||||
"Executing Python script with {} secrets (passed via stdin)",
|
||||
@@ -389,6 +419,7 @@ if __name__ == '__main__':
|
||||
timeout_secs,
|
||||
max_stdout_bytes,
|
||||
max_stderr_bytes,
|
||||
output_format,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -403,6 +434,7 @@ if __name__ == '__main__':
|
||||
python_path: PathBuf,
|
||||
max_stdout_bytes: usize,
|
||||
max_stderr_bytes: usize,
|
||||
output_format: OutputFormat,
|
||||
) -> RuntimeResult<ExecutionResult> {
|
||||
debug!(
|
||||
"Executing Python file: {:?} with {} secrets",
|
||||
@@ -425,6 +457,7 @@ if __name__ == '__main__':
|
||||
timeout_secs,
|
||||
max_stdout_bytes,
|
||||
max_stderr_bytes,
|
||||
output_format,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -515,6 +548,7 @@ impl Runtime for PythonRuntime {
|
||||
python_path,
|
||||
context.max_stdout_bytes,
|
||||
context.max_stderr_bytes,
|
||||
context.output_format,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -529,6 +563,7 @@ impl Runtime for PythonRuntime {
|
||||
python_path,
|
||||
context.max_stdout_bytes,
|
||||
context.max_stderr_bytes,
|
||||
context.output_format,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -625,6 +660,9 @@ def run(x, y):
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||
output_format: attune_common::models::OutputFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -658,6 +696,9 @@ def run():
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||
output_format: attune_common::models::OutputFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -691,6 +732,9 @@ def run():
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||
output_format: attune_common::models::OutputFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -736,6 +780,9 @@ def run():
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||
output_format: attune_common::models::OutputFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
use super::{
|
||||
parameter_passing::{self, ParameterDeliveryConfig},
|
||||
BoundedLogWriter, ExecutionContext, ExecutionResult, Runtime, RuntimeError, RuntimeResult,
|
||||
BoundedLogWriter, ExecutionContext, ExecutionResult, OutputFormat, Runtime, RuntimeError,
|
||||
RuntimeResult,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use std::path::PathBuf;
|
||||
@@ -58,6 +59,7 @@ impl ShellRuntime {
|
||||
timeout_secs: Option<u64>,
|
||||
max_stdout_bytes: usize,
|
||||
max_stderr_bytes: usize,
|
||||
output_format: OutputFormat,
|
||||
) -> RuntimeResult<ExecutionResult> {
|
||||
let start = Instant::now();
|
||||
|
||||
@@ -198,14 +200,41 @@ impl ShellRuntime {
|
||||
exit_code, duration_ms, stdout_result.truncated, stderr_result.truncated
|
||||
);
|
||||
|
||||
// Try to parse result from stdout as JSON
|
||||
// Parse result from stdout based on output_format
|
||||
let result = if exit_code == 0 && !stdout_result.content.trim().is_empty() {
|
||||
stdout_result
|
||||
.content
|
||||
.trim()
|
||||
.lines()
|
||||
.last()
|
||||
.and_then(|line| serde_json::from_str(line).ok())
|
||||
match output_format {
|
||||
OutputFormat::Text => {
|
||||
// No parsing - text output is captured in stdout field
|
||||
None
|
||||
}
|
||||
OutputFormat::Json => {
|
||||
// Try to parse last line of stdout as JSON
|
||||
stdout_result
|
||||
.content
|
||||
.trim()
|
||||
.lines()
|
||||
.last()
|
||||
.and_then(|line| serde_json::from_str(line).ok())
|
||||
}
|
||||
OutputFormat::Yaml => {
|
||||
// Try to parse stdout as YAML
|
||||
serde_yaml_ng::from_str(stdout_result.content.trim()).ok()
|
||||
}
|
||||
OutputFormat::Jsonl => {
|
||||
// Parse each line as JSON and collect into array
|
||||
let mut items = Vec::new();
|
||||
for line in stdout_result.content.trim().lines() {
|
||||
if let Ok(value) = serde_json::from_str::<serde_json::Value>(line) {
|
||||
items.push(value);
|
||||
}
|
||||
}
|
||||
if items.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(serde_json::Value::Array(items))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -338,6 +367,7 @@ impl ShellRuntime {
|
||||
timeout_secs: Option<u64>,
|
||||
max_stdout_bytes: usize,
|
||||
max_stderr_bytes: usize,
|
||||
output_format: OutputFormat,
|
||||
) -> RuntimeResult<ExecutionResult> {
|
||||
debug!(
|
||||
"Executing shell script with {} secrets (passed via stdin)",
|
||||
@@ -360,6 +390,7 @@ impl ShellRuntime {
|
||||
timeout_secs,
|
||||
max_stdout_bytes,
|
||||
max_stderr_bytes,
|
||||
output_format,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -374,6 +405,7 @@ impl ShellRuntime {
|
||||
timeout_secs: Option<u64>,
|
||||
max_stdout_bytes: usize,
|
||||
max_stderr_bytes: usize,
|
||||
output_format: OutputFormat,
|
||||
) -> RuntimeResult<ExecutionResult> {
|
||||
debug!(
|
||||
"Executing shell file: {:?} with {} secrets",
|
||||
@@ -397,6 +429,7 @@ impl ShellRuntime {
|
||||
timeout_secs,
|
||||
max_stdout_bytes,
|
||||
max_stderr_bytes,
|
||||
output_format,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -448,11 +481,8 @@ impl Runtime for ShellRuntime {
|
||||
format: context.parameter_format,
|
||||
};
|
||||
|
||||
let prepared_params = parameter_passing::prepare_parameters(
|
||||
&context.parameters,
|
||||
&mut env,
|
||||
config,
|
||||
)?;
|
||||
let prepared_params =
|
||||
parameter_passing::prepare_parameters(&context.parameters, &mut env, config)?;
|
||||
|
||||
// Get stdin content if parameters are delivered via stdin
|
||||
let parameters_stdin = prepared_params.stdin_content();
|
||||
@@ -478,6 +508,7 @@ impl Runtime for ShellRuntime {
|
||||
context.timeout,
|
||||
context.max_stdout_bytes,
|
||||
context.max_stderr_bytes,
|
||||
context.output_format,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -492,6 +523,7 @@ impl Runtime for ShellRuntime {
|
||||
context.timeout,
|
||||
context.max_stdout_bytes,
|
||||
context.max_stderr_bytes,
|
||||
context.output_format,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -577,6 +609,7 @@ mod tests {
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||
output_format: attune_common::models::OutputFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -609,6 +642,7 @@ mod tests {
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||
output_format: attune_common::models::OutputFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -636,6 +670,7 @@ mod tests {
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||
output_format: attune_common::models::OutputFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -665,6 +700,7 @@ mod tests {
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||
output_format: attune_common::models::OutputFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -709,6 +745,7 @@ echo "missing=$missing"
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||
output_format: attune_common::models::OutputFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -720,4 +757,58 @@ echo "missing=$missing"
|
||||
assert!(result.stdout.contains("db_pass=super_secret_pass"));
|
||||
assert!(result.stdout.contains("missing="));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_shell_runtime_jsonl_output() {
|
||||
let runtime = ShellRuntime::new();
|
||||
|
||||
let context = ExecutionContext {
|
||||
execution_id: 6,
|
||||
action_ref: "test.jsonl".to_string(),
|
||||
parameters: HashMap::new(),
|
||||
env: HashMap::new(),
|
||||
secrets: HashMap::new(),
|
||||
timeout: Some(10),
|
||||
working_dir: None,
|
||||
entry_point: "shell".to_string(),
|
||||
code: Some(
|
||||
r#"
|
||||
echo '{"id": 1, "name": "Alice"}'
|
||||
echo '{"id": 2, "name": "Bob"}'
|
||||
echo '{"id": 3, "name": "Charlie"}'
|
||||
"#
|
||||
.to_string(),
|
||||
),
|
||||
code_path: None,
|
||||
runtime_name: Some("shell".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_common::models::ParameterDelivery::default(),
|
||||
parameter_format: attune_common::models::ParameterFormat::default(),
|
||||
output_format: attune_common::models::OutputFormat::Jsonl,
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
assert!(result.is_success());
|
||||
assert_eq!(result.exit_code, 0);
|
||||
|
||||
// Verify result is parsed as an array of JSON objects
|
||||
let parsed_result = result.result.expect("Should have parsed result");
|
||||
assert!(parsed_result.is_array());
|
||||
|
||||
let items = parsed_result.as_array().unwrap();
|
||||
assert_eq!(items.len(), 3);
|
||||
|
||||
// Verify first item
|
||||
assert_eq!(items[0]["id"], 1);
|
||||
assert_eq!(items[0]["name"], "Alice");
|
||||
|
||||
// Verify second item
|
||||
assert_eq!(items[1]["id"], 2);
|
||||
assert_eq!(items[1]["name"], "Bob");
|
||||
|
||||
// Verify third item
|
||||
assert_eq!(items[2]["id"], 3);
|
||||
assert_eq!(items[2]["name"], "Charlie");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ for i in range(100):
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 500, // Small limit to trigger truncation
|
||||
max_stderr_bytes: 1024,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -70,6 +72,8 @@ for i in range(100):
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 300, // Small limit for stderr
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -109,6 +113,8 @@ done
|
||||
runtime_name: Some("shell".to_string()),
|
||||
max_stdout_bytes: 400, // Small limit
|
||||
max_stderr_bytes: 1024,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -144,6 +150,8 @@ print("Hello, World!")
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024, // Large limit
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -184,6 +192,8 @@ for i in range(50):
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 300, // Both limits are small
|
||||
max_stderr_bytes: 300,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -226,6 +236,8 @@ time.sleep(30) # Will timeout before this
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 500,
|
||||
max_stderr_bytes: 1024,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -262,6 +274,8 @@ sys.stdout.write("Small output")
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024, // Large limit to avoid truncation
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
|
||||
@@ -59,6 +59,8 @@ def run():
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -155,6 +157,8 @@ echo "SECURITY_PASS: Secrets not in environment but accessible via get_secret"
|
||||
runtime_name: Some("shell".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -203,6 +207,8 @@ def run():
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result1 = runtime.execute(context1).await.unwrap();
|
||||
@@ -239,6 +245,8 @@ def run():
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result2 = runtime.execute(context2).await.unwrap();
|
||||
@@ -286,6 +294,8 @@ def run():
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -329,6 +339,8 @@ fi
|
||||
runtime_name: Some("shell".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
@@ -380,6 +392,8 @@ def run():
|
||||
runtime_name: Some("python".to_string()),
|
||||
max_stdout_bytes: 10 * 1024 * 1024,
|
||||
max_stderr_bytes: 10 * 1024 * 1024,
|
||||
parameter_delivery: attune_worker::runtime::ParameterDelivery::default(),
|
||||
parameter_format: attune_worker::runtime::ParameterFormat::default(),
|
||||
};
|
||||
|
||||
let result = runtime.execute(context).await.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user