Skip to main content

agent_orchestrator/resource/
execution_profile.rs

1use crate::cli_types::{ExecutionProfileSpec, OrchestratorResource, ResourceKind, ResourceSpec};
2use crate::config::{ExecutionProfileConfig, OrchestratorConfig};
3use crate::sandbox_network::validate_network_allowlist;
4use anyhow::{Result, anyhow};
5
6use super::{ApplyResult, RegisteredResource, Resource, ResourceMetadata};
7
8#[derive(Debug, Clone)]
9/// Builtin manifest adapter for `ExecutionProfile` resources.
10pub struct ExecutionProfileResource {
11    /// Resource metadata from the manifest.
12    pub metadata: ResourceMetadata,
13    /// Manifest spec payload for the execution profile.
14    pub spec: ExecutionProfileSpec,
15}
16
17impl Resource for ExecutionProfileResource {
18    fn kind(&self) -> ResourceKind {
19        ResourceKind::ExecutionProfile
20    }
21
22    fn name(&self) -> &str {
23        &self.metadata.name
24    }
25
26    fn validate(&self) -> Result<()> {
27        super::validate_resource_name(self.name())?;
28        if self.spec.mode == "host" && self.spec.fs_mode != "inherit" {
29            return Err(anyhow!(
30                "executionprofile.spec.fs_mode is only valid when mode=sandbox"
31            ));
32        }
33        if self.spec.network_mode == "allowlist" && self.spec.network_allowlist.is_empty() {
34            return Err(anyhow!(
35                "executionprofile.spec.network_allowlist cannot be empty when network_mode=allowlist"
36            ));
37        }
38        if self.spec.network_mode == "allowlist" {
39            validate_network_allowlist(&self.spec.network_allowlist)?;
40        }
41        Ok(())
42    }
43
44    fn apply(&self, config: &mut OrchestratorConfig) -> Result<ApplyResult> {
45        let mut metadata = self.metadata.clone();
46        metadata.project = Some(
47            config
48                .effective_project_id(metadata.project.as_deref())
49                .to_string(),
50        );
51        Ok(super::apply_to_store(
52            config,
53            "ExecutionProfile",
54            self.name(),
55            &metadata,
56            serde_json::to_value(&self.spec)?,
57        ))
58    }
59
60    fn to_yaml(&self) -> Result<String> {
61        super::manifest_yaml(
62            ResourceKind::ExecutionProfile,
63            &self.metadata,
64            ResourceSpec::ExecutionProfile(self.spec.clone()),
65        )
66    }
67
68    fn get_from_project(
69        config: &OrchestratorConfig,
70        name: &str,
71        project_id: Option<&str>,
72    ) -> Option<Self> {
73        config
74            .project(project_id)?
75            .execution_profiles
76            .get(name)
77            .map(|profile| Self {
78                metadata: super::metadata_from_store(config, "ExecutionProfile", name, project_id),
79                spec: execution_profile_config_to_spec(profile),
80            })
81    }
82
83    fn delete_from_project(
84        config: &mut OrchestratorConfig,
85        name: &str,
86        project_id: Option<&str>,
87    ) -> bool {
88        super::helpers::delete_from_store_project(config, "ExecutionProfile", name, project_id)
89    }
90}
91
92/// Builds a typed `ExecutionProfileResource` from a generic manifest wrapper.
93pub(super) fn build_execution_profile(
94    resource: OrchestratorResource,
95) -> Result<RegisteredResource> {
96    let OrchestratorResource {
97        kind,
98        metadata,
99        spec,
100        ..
101    } = resource;
102    if kind != ResourceKind::ExecutionProfile {
103        return Err(anyhow!("resource kind/spec mismatch for ExecutionProfile"));
104    }
105    match spec {
106        ResourceSpec::ExecutionProfile(spec) => Ok(RegisteredResource::ExecutionProfile(
107            ExecutionProfileResource { metadata, spec },
108        )),
109        _ => Err(anyhow!("resource kind/spec mismatch for ExecutionProfile")),
110    }
111}
112
113/// Converts an execution-profile manifest spec into runtime config.
114pub(crate) fn execution_profile_spec_to_config(
115    spec: &ExecutionProfileSpec,
116) -> ExecutionProfileConfig {
117    ExecutionProfileConfig {
118        mode: match spec.mode.as_str() {
119            "sandbox" => crate::config::ExecutionProfileMode::Sandbox,
120            _ => crate::config::ExecutionProfileMode::Host,
121        },
122        fs_mode: match spec.fs_mode.as_str() {
123            "workspace_readonly" => crate::config::ExecutionFsMode::WorkspaceReadonly,
124            "workspace_rw_scoped" => crate::config::ExecutionFsMode::WorkspaceRwScoped,
125            _ => crate::config::ExecutionFsMode::Inherit,
126        },
127        writable_paths: spec.writable_paths.clone(),
128        network_mode: match spec.network_mode.as_str() {
129            "deny" => crate::config::ExecutionNetworkMode::Deny,
130            "allowlist" => crate::config::ExecutionNetworkMode::Allowlist,
131            _ => crate::config::ExecutionNetworkMode::Inherit,
132        },
133        network_allowlist: spec.network_allowlist.clone(),
134        max_memory_mb: spec.max_memory_mb,
135        max_cpu_seconds: spec.max_cpu_seconds,
136        max_processes: spec.max_processes,
137        max_open_files: spec.max_open_files,
138    }
139}
140
141/// Converts runtime execution-profile config into its manifest spec representation.
142pub(crate) fn execution_profile_config_to_spec(
143    config: &ExecutionProfileConfig,
144) -> ExecutionProfileSpec {
145    ExecutionProfileSpec {
146        mode: match config.mode {
147            crate::config::ExecutionProfileMode::Host => "host".to_string(),
148            crate::config::ExecutionProfileMode::Sandbox => "sandbox".to_string(),
149        },
150        fs_mode: match config.fs_mode {
151            crate::config::ExecutionFsMode::Inherit => "inherit".to_string(),
152            crate::config::ExecutionFsMode::WorkspaceReadonly => "workspace_readonly".to_string(),
153            crate::config::ExecutionFsMode::WorkspaceRwScoped => "workspace_rw_scoped".to_string(),
154        },
155        writable_paths: config.writable_paths.clone(),
156        network_mode: match config.network_mode {
157            crate::config::ExecutionNetworkMode::Inherit => "inherit".to_string(),
158            crate::config::ExecutionNetworkMode::Deny => "deny".to_string(),
159            crate::config::ExecutionNetworkMode::Allowlist => "allowlist".to_string(),
160        },
161        network_allowlist: config.network_allowlist.clone(),
162        max_memory_mb: config.max_memory_mb,
163        max_cpu_seconds: config.max_cpu_seconds,
164        max_processes: config.max_processes,
165        max_open_files: config.max_open_files,
166    }
167}