agent_orchestrator/resource/
execution_profile.rs1use 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)]
9pub struct ExecutionProfileResource {
11 pub metadata: ResourceMetadata,
13 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
92pub(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
113pub(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
141pub(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}