Some checks failed
CI / Rustfmt (push) Successful in 19s
CI / Cargo Audit & Deny (push) Successful in 33s
CI / Security Blocking Checks (push) Successful in 5s
CI / Web Advisory Checks (push) Successful in 28s
CI / Web Blocking Checks (push) Successful in 52s
Publish Images / Resolve Publish Metadata (push) Successful in 0s
CI / Security Advisory Checks (push) Successful in 23s
CI / Clippy (push) Successful in 2m4s
Publish Images / Publish Docker Dist Bundle (push) Successful in 4s
Publish Images / Publish web (amd64) (push) Successful in 45s
Publish Images / Publish web (arm64) (push) Successful in 3m32s
CI / Tests (push) Failing after 8m25s
Publish Images / Build Rust Bundles (arm64) (push) Successful in 12m12s
Publish Images / Build Rust Bundles (amd64) (push) Successful in 12m39s
Publish Images / Publish agent (amd64) (push) Successful in 26s
Publish Images / Publish executor (amd64) (push) Successful in 40s
Publish Images / Publish api (amd64) (push) Successful in 30s
Publish Images / Publish notifier (amd64) (push) Successful in 41s
Publish Images / Publish agent (arm64) (push) Successful in 52s
Publish Images / Publish api (arm64) (push) Successful in 1m56s
Publish Images / Publish executor (arm64) (push) Successful in 1m57s
Publish Images / Publish notifier (arm64) (push) Successful in 1m50s
Publish Images / Publish manifest attune/agent (push) Successful in 15s
Publish Images / Publish manifest attune/api (push) Failing after 30s
Publish Images / Publish manifest attune/executor (push) Successful in 42s
Publish Images / Publish manifest attune/web (push) Failing after 17s
Publish Images / Publish manifest attune/notifier (push) Failing after 14m44s
249 lines
7.9 KiB
Rust
249 lines
7.9 KiB
Rust
//! Local Runtime Module
|
|
//!
|
|
//! Provides local execution capabilities by combining Process and Native runtimes.
|
|
//! This module serves as a facade for all local process-based execution.
|
|
//!
|
|
//! The `ProcessRuntime` is used for Python (and other interpreted languages),
|
|
//! driven by `RuntimeExecutionConfig` rather than language-specific Rust code.
|
|
|
|
use super::native::NativeRuntime;
|
|
use super::process::ProcessRuntime;
|
|
use super::{ExecutionContext, ExecutionResult, Runtime, RuntimeError, RuntimeResult};
|
|
use async_trait::async_trait;
|
|
use attune_common::models::runtime::{
|
|
InlineExecutionConfig, InlineExecutionStrategy, InterpreterConfig, RuntimeExecutionConfig,
|
|
};
|
|
use std::path::PathBuf;
|
|
use tracing::{debug, info};
|
|
|
|
/// Local runtime that delegates to Process, Shell, or Native based on action type
|
|
pub struct LocalRuntime {
|
|
native: NativeRuntime,
|
|
python: ProcessRuntime,
|
|
shell: ProcessRuntime,
|
|
}
|
|
|
|
impl LocalRuntime {
|
|
/// Create a new local runtime with default settings.
|
|
///
|
|
/// Uses a default Python `RuntimeExecutionConfig` for the process runtime,
|
|
/// since this is a fallback when runtimes haven't been loaded from the database.
|
|
pub fn new() -> Self {
|
|
let python_config = RuntimeExecutionConfig {
|
|
interpreter: InterpreterConfig {
|
|
binary: "python3".to_string(),
|
|
args: vec![],
|
|
file_extension: Some(".py".to_string()),
|
|
},
|
|
inline_execution: InlineExecutionConfig::default(),
|
|
environment: None,
|
|
dependencies: None,
|
|
env_vars: std::collections::HashMap::new(),
|
|
};
|
|
|
|
let shell_config = RuntimeExecutionConfig {
|
|
interpreter: InterpreterConfig {
|
|
binary: "/bin/bash".to_string(),
|
|
args: vec![],
|
|
file_extension: Some(".sh".to_string()),
|
|
},
|
|
inline_execution: InlineExecutionConfig {
|
|
strategy: InlineExecutionStrategy::TempFile,
|
|
extension: Some(".sh".to_string()),
|
|
inject_shell_helpers: true,
|
|
},
|
|
environment: None,
|
|
dependencies: None,
|
|
env_vars: std::collections::HashMap::new(),
|
|
};
|
|
|
|
Self {
|
|
native: NativeRuntime::new(),
|
|
python: ProcessRuntime::new(
|
|
"python".to_string(),
|
|
python_config,
|
|
PathBuf::from("/opt/attune/packs"),
|
|
PathBuf::from("/opt/attune/runtime_envs"),
|
|
),
|
|
shell: ProcessRuntime::new(
|
|
"shell".to_string(),
|
|
shell_config,
|
|
PathBuf::from("/opt/attune/packs"),
|
|
PathBuf::from("/opt/attune/runtime_envs"),
|
|
),
|
|
}
|
|
}
|
|
|
|
/// Create a local runtime with custom runtimes
|
|
pub fn with_runtimes(
|
|
native: NativeRuntime,
|
|
python: ProcessRuntime,
|
|
shell: ProcessRuntime,
|
|
) -> Self {
|
|
Self {
|
|
native,
|
|
python,
|
|
shell,
|
|
}
|
|
}
|
|
|
|
/// Get the appropriate runtime for the given context
|
|
fn select_runtime(&self, context: &ExecutionContext) -> RuntimeResult<&dyn Runtime> {
|
|
if self.native.can_execute(context) {
|
|
debug!("Selected Native runtime for action: {}", context.action_ref);
|
|
Ok(&self.native)
|
|
} else if self.python.can_execute(context) {
|
|
debug!(
|
|
"Selected Python (ProcessRuntime) for action: {}",
|
|
context.action_ref
|
|
);
|
|
Ok(&self.python)
|
|
} else if self.shell.can_execute(context) {
|
|
debug!(
|
|
"Selected Shell (ProcessRuntime) for action: {}",
|
|
context.action_ref
|
|
);
|
|
Ok(&self.shell)
|
|
} else {
|
|
Err(RuntimeError::RuntimeNotFound(format!(
|
|
"No suitable local runtime found for action: {}",
|
|
context.action_ref
|
|
)))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for LocalRuntime {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Runtime for LocalRuntime {
|
|
fn name(&self) -> &str {
|
|
"local"
|
|
}
|
|
|
|
fn can_execute(&self, context: &ExecutionContext) -> bool {
|
|
self.native.can_execute(context)
|
|
|| self.python.can_execute(context)
|
|
|| self.shell.can_execute(context)
|
|
}
|
|
|
|
async fn execute(&self, context: ExecutionContext) -> RuntimeResult<ExecutionResult> {
|
|
info!(
|
|
"Executing local action: {} (execution_id: {})",
|
|
context.action_ref, context.execution_id
|
|
);
|
|
|
|
let runtime = self.select_runtime(&context)?;
|
|
runtime.execute(context).await
|
|
}
|
|
|
|
async fn setup(&self) -> RuntimeResult<()> {
|
|
info!("Setting up Local runtime");
|
|
|
|
self.native.setup().await?;
|
|
self.python.setup().await?;
|
|
self.shell.setup().await?;
|
|
|
|
info!("Local runtime setup complete");
|
|
Ok(())
|
|
}
|
|
|
|
async fn cleanup(&self) -> RuntimeResult<()> {
|
|
info!("Cleaning up Local runtime");
|
|
|
|
self.native.cleanup().await?;
|
|
self.python.cleanup().await?;
|
|
self.shell.cleanup().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn validate(&self) -> RuntimeResult<()> {
|
|
debug!("Validating Local runtime");
|
|
|
|
self.native.validate().await?;
|
|
self.python.validate().await?;
|
|
self.shell.validate().await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::runtime::{OutputFormat, ParameterDelivery, ParameterFormat};
|
|
use std::collections::HashMap;
|
|
|
|
#[tokio::test]
|
|
async fn test_local_runtime_shell() {
|
|
let runtime = LocalRuntime::new();
|
|
|
|
let context = ExecutionContext {
|
|
execution_id: 2,
|
|
action_ref: "test.shell_action".to_string(),
|
|
parameters: HashMap::new(),
|
|
env: HashMap::new(),
|
|
secrets: HashMap::new(),
|
|
timeout: Some(10),
|
|
working_dir: None,
|
|
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()),
|
|
runtime_config_override: None,
|
|
runtime_env_dir_suffix: None,
|
|
selected_runtime_version: None,
|
|
max_stdout_bytes: 10 * 1024 * 1024,
|
|
max_stderr_bytes: 10 * 1024 * 1024,
|
|
stdout_log_path: None,
|
|
stderr_log_path: None,
|
|
parameter_delivery: ParameterDelivery::default(),
|
|
parameter_format: ParameterFormat::default(),
|
|
output_format: OutputFormat::default(),
|
|
cancel_token: None,
|
|
};
|
|
|
|
assert!(runtime.can_execute(&context));
|
|
let result = runtime.execute(context).await.unwrap();
|
|
assert!(result.is_success());
|
|
assert!(result.stdout.contains("hello from shell"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_local_runtime_unknown() {
|
|
let runtime = LocalRuntime::new();
|
|
|
|
let context = ExecutionContext {
|
|
execution_id: 3,
|
|
action_ref: "test.unknown_action".to_string(),
|
|
parameters: HashMap::new(),
|
|
env: HashMap::new(),
|
|
secrets: HashMap::new(),
|
|
timeout: Some(10),
|
|
working_dir: None,
|
|
entry_point: "run".to_string(),
|
|
code: Some("some code".to_string()),
|
|
code_path: None,
|
|
runtime_name: Some("unknown".to_string()),
|
|
runtime_config_override: None,
|
|
runtime_env_dir_suffix: None,
|
|
selected_runtime_version: None,
|
|
max_stdout_bytes: 10 * 1024 * 1024,
|
|
max_stderr_bytes: 10 * 1024 * 1024,
|
|
stdout_log_path: None,
|
|
stderr_log_path: None,
|
|
parameter_delivery: ParameterDelivery::default(),
|
|
parameter_format: ParameterFormat::default(),
|
|
output_format: OutputFormat::default(),
|
|
cancel_token: None,
|
|
};
|
|
|
|
assert!(!runtime.can_execute(&context));
|
|
}
|
|
}
|