Skip to main content

camel_component_exec/
lib.rs

1//! camel-component-exec — fail-closed system command execution component.
2
3pub mod audit;
4pub mod bundle;
5pub mod config;
6pub mod endpoint;
7pub mod error;
8pub mod headers;
9pub mod policy;
10pub mod process;
11pub mod producer;
12
13pub use audit::ExecAuditEvent;
14pub use bundle::ExecBundle;
15pub use config::{ArgPolicy, EnvPolicy, ExecGlobalConfig, ExecProfile, OutputMode, Sandbox};
16pub use endpoint::ExecEndpoint;
17pub use error::ExecError;
18pub use producer::{ExecProducer, ExecResult};
19
20use std::sync::Arc;
21
22use async_trait::async_trait;
23use camel_component_api::{CamelError, Component, ComponentContext, Endpoint};
24
25/// Factory for `exec:{profile-name}` endpoints.
26///
27/// Holds the validated global config (including startup-pinned canonical
28/// workspace root) and creates `ExecEndpoint` instances that resolve to
29/// the named profile.
30pub struct ExecComponent {
31    config: Arc<ExecGlobalConfig>,
32}
33
34impl ExecComponent {
35    /// Create a new ExecComponent from global config.
36    ///
37    /// The config should already be validated via
38    /// `ExecGlobalConfig::validate()` (the `ExecBundle` does this during
39    /// `from_toml`).
40    pub fn new(config: ExecGlobalConfig) -> Self {
41        Self {
42            config: Arc::new(config),
43        }
44    }
45}
46
47#[async_trait]
48impl Component for ExecComponent {
49    fn scheme(&self) -> &str {
50        "exec"
51    }
52
53    fn create_endpoint(
54        &self,
55        uri: &str,
56        _ctx: &dyn ComponentContext,
57    ) -> Result<Box<dyn Endpoint>, CamelError> {
58        // URI form: exec:{profile-name}
59        let profile_name = uri.strip_prefix("exec:").ok_or_else(|| {
60            CamelError::InvalidUri(format!("exec uri must start with 'exec:': {uri}"))
61        })?;
62
63        // Strip optional query string
64        let profile_name = profile_name
65            .split_once('?')
66            .map(|(p, _)| p)
67            .unwrap_or(profile_name)
68            .to_string();
69
70        if profile_name.is_empty() {
71            return Err(CamelError::InvalidUri("exec: profile name required".into()));
72        }
73
74        // Fail-fast: verify the profile exists at endpoint creation time,
75        // not deferred to producer call.
76        if self.config.profile(&profile_name).is_none() {
77            return Err(CamelError::EndpointCreationFailed(format!(
78                "exec profile {profile_name:?} not configured"
79            )));
80        }
81
82        Ok(Box::new(ExecEndpoint::new(
83            uri.to_string(),
84            profile_name,
85            Arc::clone(&self.config),
86        )))
87    }
88}