clnrm_core/config/
services.rs1use crate::error::{CleanroomError, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7fn default_plugin() -> String {
9 "generic_container".to_string()
10}
11
12#[derive(Debug, Deserialize, Serialize, Clone)]
14pub struct ServiceConfig {
15 #[serde(default = "default_plugin")]
17 pub plugin: String,
18 pub image: Option<String>,
20 #[serde(default)]
23 pub args: Option<Vec<String>>,
24 pub env: Option<HashMap<String, String>>,
26 pub ports: Option<Vec<u16>>,
28 pub volumes: Option<Vec<VolumeConfig>>,
30 pub health_check: Option<HealthCheckConfig>,
32 pub username: Option<String>,
34 pub password: Option<String>,
36 pub strict: Option<bool>,
38 pub wait_for_span: Option<String>,
41 pub wait_for_span_timeout_secs: Option<u64>,
43}
44
45#[derive(Debug, Deserialize, Serialize, Clone)]
47pub struct VolumeConfig {
48 pub host_path: String,
50 pub container_path: String,
52 pub read_only: Option<bool>,
54}
55
56impl VolumeConfig {
57 pub fn validate(&self) -> Result<()> {
66 use std::path::Path;
67
68 if self.host_path.trim().is_empty() {
70 return Err(CleanroomError::validation_error(
71 "Volume host path cannot be empty",
72 ));
73 }
74
75 if self.container_path.trim().is_empty() {
77 return Err(CleanroomError::validation_error(
78 "Volume container path cannot be empty",
79 ));
80 }
81
82 let host_path = Path::new(&self.host_path);
84 if !host_path.is_absolute() {
85 return Err(CleanroomError::validation_error(format!(
86 "Volume host path must be absolute: {}",
87 self.host_path
88 )));
89 }
90
91 let container_path = Path::new(&self.container_path);
93 if !container_path.is_absolute() {
94 return Err(CleanroomError::validation_error(format!(
95 "Volume container path must be absolute: {}",
96 self.container_path
97 )));
98 }
99
100 Ok(())
101 }
102
103 pub fn to_volume_mount(&self) -> Result<crate::backend::volume::VolumeMount> {
108 use crate::backend::volume::VolumeMount;
109 VolumeMount::from_config(self)
110 }
111}
112
113#[derive(Debug, Deserialize, Serialize, Clone)]
115pub struct HealthCheckConfig {
116 pub cmd: Vec<String>,
118 pub interval: Option<u64>,
120 pub timeout: Option<u64>,
122 pub retries: Option<u32>,
124}
125
126impl ServiceConfig {
127 pub fn validate(&self) -> Result<()> {
129 if self.plugin.trim().is_empty() {
130 return Err(CleanroomError::validation_error(
131 "Service plugin cannot be empty",
132 ));
133 }
134
135 if let Some(ref image) = self.image {
136 if image.trim().is_empty() {
137 return Err(CleanroomError::validation_error(
138 "Service image cannot be empty",
139 ));
140 }
141 } else if self.plugin != "network_service" && self.plugin != "ollama" {
142 return Err(CleanroomError::validation_error(
144 "Service image is required for container-based services",
145 ));
146 }
147
148 if let Some(ref volumes) = self.volumes {
150 for (i, volume) in volumes.iter().enumerate() {
151 volume.validate().map_err(|e| {
152 CleanroomError::validation_error(format!("Volume {}: {}", i, e))
153 })?;
154 }
155 }
156
157 Ok(())
158 }
159}