1#![warn(missing_docs)]
2
3use std::collections::HashMap;
8use std::default::Default;
9use std::fs;
10use std::path::Path;
11use std::time::SystemTime;
12
13use docker_types::{
14 ConfigInfo, DockerConfig, DockerError, EndpointConfig, EndpointInfo, EndpointStatus,
15 EndpointType, LogConfig, ResourceLimits, SecretInfo,
16};
17
18pub type Result<T> = std::result::Result<T, DockerError>;
20
21pub struct ConfigManager {
23 config_path: String,
25 configs_dir: String,
27 secrets_dir: String,
29 endpoints_dir: String,
31}
32
33impl ConfigManager {
34 pub fn new() -> Result<Self> {
36 let config_path = Self::get_config_path()?;
38 let data_dir = Self::get_default_data_dir();
39 let configs_dir = format!("{}/configs", data_dir);
40 let secrets_dir = format!("{}/secrets", data_dir);
41 let endpoints_dir = format!("{}/endpoints", data_dir);
42
43 fs::create_dir_all(&configs_dir)
45 .map_err(|e| DockerError::io_error("create_dir_all", e.to_string()))?;
46 fs::create_dir_all(&secrets_dir)
47 .map_err(|e| DockerError::io_error("create_dir_all", e.to_string()))?;
48 fs::create_dir_all(&endpoints_dir)
49 .map_err(|e| DockerError::io_error("create_dir_all", e.to_string()))?;
50
51 Ok(Self {
52 config_path,
53 configs_dir,
54 secrets_dir,
55 endpoints_dir,
56 })
57 }
58
59 fn get_config_path() -> Result<String> {
61 #[cfg(target_os = "windows")]
63 let default_path = "C:\\ProgramData\\rusty-docker\\config.toml";
64
65 #[cfg(target_os = "linux")]
66 let default_path = "/etc/rusty-docker/config.toml";
67
68 #[cfg(target_os = "macos")]
69 let default_path = "/usr/local/etc/rusty-docker/config.toml";
70
71 if Path::new(default_path).exists() {
73 Ok(default_path.to_string())
74 } else {
75 Self::create_default_config(default_path)?;
77 Ok(default_path.to_string())
78 }
79 }
80
81 fn create_default_config(path: &str) -> Result<()> {
83 if let Some(dir) = Path::new(path).parent() {
85 fs::create_dir_all(dir)
86 .map_err(|e| DockerError::io_error("create_dir_all", e.to_string()))?;
87 }
88
89 let default_config = Self::get_default_config();
91 let config_content = toml::to_string(&default_config)
92 .map_err(|e| DockerError::parse_error("DockerConfig", e.to_string()))?;
93
94 fs::write(path, config_content)
96 .map_err(|e| DockerError::io_error("write", e.to_string()))?;
97 Ok(())
98 }
99
100 fn get_default_config() -> DockerConfig {
102 DockerConfig {
103 data_dir: Self::get_default_data_dir(),
104 image_dir: format!("{}/images", Self::get_default_data_dir()),
105 container_dir: format!("{}/containers", Self::get_default_data_dir()),
106 network_dir: format!("{}/networks", Self::get_default_data_dir()),
107 default_network: "default".to_string(),
108 default_resources: ResourceLimits {
109 cpu_limit: 1.0,
110 memory_limit: 512,
111 storage_limit: 10,
112 network_limit: 10,
113 },
114 log_config: LogConfig {
115 log_level: "info".to_string(),
116 log_file: format!("{}/logs/docker.log", Self::get_default_data_dir()),
117 max_log_size: 100,
118 },
119 }
120 }
121
122 fn get_default_data_dir() -> String {
124 #[cfg(target_os = "windows")]
125 return "C:\\ProgramData\\rusty-docker\\data".to_string();
126
127 #[cfg(target_os = "linux")]
128 return "/var/lib/rusty-docker".to_string();
129
130 #[cfg(target_os = "macos")]
131 return "/usr/local/var/rusty-docker".to_string();
132 }
133
134 pub fn get_config(&self) -> Result<DockerConfig> {
136 let config_content = fs::read_to_string(&self.config_path)
138 .map_err(|e| DockerError::io_error("read_to_string", e.to_string()))?;
139
140 let config: DockerConfig = toml::from_str(&config_content)
142 .map_err(|e| DockerError::parse_error("DockerConfig", e.to_string()))?;
143
144 Ok(config)
145 }
146
147 pub fn save_config(&self, config: &DockerConfig) -> Result<()> {
149 let config_content = toml::to_string(config)
151 .map_err(|e| DockerError::parse_error("DockerConfig", e.to_string()))?;
152
153 fs::write(&self.config_path, config_content)
155 .map_err(|e| DockerError::io_error("write", e.to_string()))?;
156 Ok(())
157 }
158
159 fn generate_id(data: &str) -> String {
161 use sha2::{Digest, Sha256};
162 let mut hasher = Sha256::new();
163 hasher.update(data);
164 hasher.update(
165 SystemTime::now()
166 .duration_since(SystemTime::UNIX_EPOCH)
167 .unwrap()
168 .as_secs()
169 .to_string(),
170 );
171 format!("{:x}", hasher.finalize())
172 }
173
174 pub fn create_config(
176 &self,
177 name: &str,
178 data: &str,
179 labels: HashMap<String, String>,
180 ) -> Result<ConfigInfo> {
181 let id = Self::generate_id(&format!("{}{}", name, data));
182 let config_info = ConfigInfo {
183 id: id.clone(),
184 name: name.to_string(),
185 data: data.to_string(),
186 created_at: SystemTime::now(),
187 labels,
188 };
189
190 let config_content = toml::to_string(&config_info)
192 .map_err(|e| DockerError::parse_error("ConfigInfo", e.to_string()))?;
193
194 let config_path = format!("{}/{}.toml", self.configs_dir, id);
196 fs::write(&config_path, config_content)
197 .map_err(|e| DockerError::io_error("write", e.to_string()))?;
198
199 Ok(config_info)
200 }
201
202 pub fn update_config(
204 &self,
205 config_id: &str,
206 data: &str,
207 labels: HashMap<String, String>,
208 ) -> Result<ConfigInfo> {
209 let config_path = format!("{}/{}.toml", self.configs_dir, config_id);
211 if !Path::new(&config_path).exists() {
212 return Err(DockerError::not_found(
213 "config",
214 format!("Config {} not found", config_id),
215 ));
216 }
217
218 let config_content = fs::read_to_string(&config_path)
220 .map_err(|e| DockerError::io_error("read_to_string", e.to_string()))?;
221 let mut config_info: ConfigInfo = toml::from_str(&config_content)
222 .map_err(|e| DockerError::parse_error("ConfigInfo", e.to_string()))?;
223
224 config_info.data = data.to_string();
226 config_info.labels = labels;
227
228 let updated_content = toml::to_string(&config_info)
230 .map_err(|e| DockerError::parse_error("ConfigInfo", e.to_string()))?;
231 fs::write(&config_path, updated_content)
232 .map_err(|e| DockerError::io_error("write", e.to_string()))?;
233
234 Ok(config_info)
235 }
236
237 pub fn delete_config(&self, config_id: &str) -> Result<()> {
239 let config_path = format!("{}/{}.toml", self.configs_dir, config_id);
240 if !Path::new(&config_path).exists() {
241 return Err(DockerError::not_found(
242 "config",
243 format!("Config {} not found", config_id),
244 ));
245 }
246
247 fs::remove_file(&config_path)
248 .map_err(|e| DockerError::io_error("remove_file", e.to_string()))?;
249 Ok(())
250 }
251
252 pub fn get_config_info(&self, config_id: &str) -> Result<ConfigInfo> {
254 let config_path = format!("{}/{}.toml", self.configs_dir, config_id);
255 if !Path::new(&config_path).exists() {
256 return Err(DockerError::not_found(
257 "config",
258 format!("Config {} not found", config_id),
259 ));
260 }
261
262 let config_content = fs::read_to_string(&config_path)
263 .map_err(|e| DockerError::io_error("read_to_string", e.to_string()))?;
264 let config_info: ConfigInfo = toml::from_str(&config_content)
265 .map_err(|e| DockerError::parse_error("ConfigInfo", e.to_string()))?;
266
267 Ok(config_info)
268 }
269
270 pub fn list_configs(&self) -> Result<Vec<ConfigInfo>> {
272 let mut configs = Vec::new();
273
274 for entry in fs::read_dir(&self.configs_dir)
276 .map_err(|e| DockerError::io_error("read_dir", e.to_string()))?
277 {
278 let entry = entry.map_err(|e| DockerError::io_error("entry", e.to_string()))?;
279 let path = entry.path();
280
281 if path.is_file() && path.extension().unwrap_or_default() == "toml" {
282 let config_content = fs::read_to_string(&path)
283 .map_err(|e| DockerError::io_error("read_to_string", e.to_string()))?;
284 let config_info: ConfigInfo = toml::from_str(&config_content)
285 .map_err(|e| DockerError::parse_error("ConfigInfo", e.to_string()))?;
286 configs.push(config_info);
287 }
288 }
289
290 Ok(configs)
291 }
292
293 pub fn create_secret(
295 &self,
296 name: &str,
297 data: &str,
298 labels: HashMap<String, String>,
299 ) -> Result<SecretInfo> {
300 let id = Self::generate_id(&format!("{}{}", name, data));
301 let digest = Self::generate_id(data);
302
303 let secret_info = SecretInfo {
304 id: id.clone(),
305 name: name.to_string(),
306 created_at: SystemTime::now(),
307 labels,
308 digest,
309 };
310
311 let secret_content = toml::to_string(&secret_info)
313 .map_err(|e| DockerError::parse_error("SecretInfo", e.to_string()))?;
314
315 let secret_path = format!("{}/{}.toml", self.secrets_dir, id);
317 fs::write(&secret_path, secret_content)
318 .map_err(|e| DockerError::io_error("write", e.to_string()))?;
319
320 let data_path = format!("{}/{}.data", self.secrets_dir, id);
322 fs::write(&data_path, data).map_err(|e| DockerError::io_error("write", e.to_string()))?;
323
324 Ok(secret_info)
325 }
326
327 pub fn delete_secret(&self, secret_id: &str) -> Result<()> {
329 let secret_path = format!("{}/{}.toml", self.secrets_dir, secret_id);
330 let data_path = format!("{}/{}.data", self.secrets_dir, secret_id);
331
332 if !Path::new(&secret_path).exists() {
333 return Err(DockerError::not_found(
334 "secret",
335 format!("Secret {} not found", secret_id),
336 ));
337 }
338
339 fs::remove_file(&secret_path)
341 .map_err(|e| DockerError::io_error("remove_file", e.to_string()))?;
342 if Path::new(&data_path).exists() {
343 fs::remove_file(&data_path)
344 .map_err(|e| DockerError::io_error("remove_file", e.to_string()))?;
345 }
346
347 Ok(())
348 }
349
350 pub fn get_secret_info(&self, secret_id: &str) -> Result<SecretInfo> {
352 let secret_path = format!("{}/{}.toml", self.secrets_dir, secret_id);
353 if !Path::new(&secret_path).exists() {
354 return Err(DockerError::not_found(
355 "secret",
356 format!("Secret {} not found", secret_id),
357 ));
358 }
359
360 let secret_content = fs::read_to_string(&secret_path)
361 .map_err(|e| DockerError::io_error("read_to_string", e.to_string()))?;
362 let secret_info: SecretInfo = toml::from_str(&secret_content)
363 .map_err(|e| DockerError::parse_error("SecretInfo", e.to_string()))?;
364
365 Ok(secret_info)
366 }
367
368 pub fn list_secrets(&self) -> Result<Vec<SecretInfo>> {
370 let mut secrets = Vec::new();
371
372 for entry in fs::read_dir(&self.secrets_dir)
374 .map_err(|e| DockerError::io_error("read_dir", e.to_string()))?
375 {
376 let entry = entry.map_err(|e| DockerError::io_error("entry", e.to_string()))?;
377 let path = entry.path();
378
379 if path.is_file() && path.extension().unwrap_or_default() == "toml" {
380 let secret_content = fs::read_to_string(&path)
381 .map_err(|e| DockerError::io_error("read_to_string", e.to_string()))?;
382 let secret_info: SecretInfo = toml::from_str(&secret_content)
383 .map_err(|e| DockerError::parse_error("SecretInfo", e.to_string()))?;
384 secrets.push(secret_info);
385 }
386 }
387
388 Ok(secrets)
389 }
390
391 pub fn create_endpoint(
393 &self,
394 name: &str,
395 endpoint_type: EndpointType,
396 url: &str,
397 use_tls: bool,
398 tls_cert_path: Option<String>,
399 tls_key_path: Option<String>,
400 tls_ca_path: Option<String>,
401 auth_token: Option<String>,
402 labels: HashMap<String, String>,
403 ) -> Result<EndpointInfo> {
404 let endpoint_type_str = match endpoint_type {
405 EndpointType::Local => "local",
406 EndpointType::Remote => "remote",
407 EndpointType::Cloud => "cloud",
408 };
409 let id = Self::generate_id(&format!("{}{}{}", name, url, endpoint_type_str));
410 let config = EndpointConfig {
411 id: id.clone(),
412 name: name.to_string(),
413 endpoint_type,
414 url: url.to_string(),
415 use_tls,
416 tls_cert_path,
417 tls_key_path,
418 tls_ca_path,
419 auth_token,
420 labels,
421 };
422
423 let endpoint_info = EndpointInfo {
424 config,
425 status: EndpointStatus::Disconnected,
426 created_at: SystemTime::now(),
427 last_connected_at: None,
428 connection_info: None,
429 };
430
431 let endpoint_content = toml::to_string(&endpoint_info)
433 .map_err(|e| DockerError::parse_error("EndpointInfo", e.to_string()))?;
434
435 let endpoint_path = format!("{}/{}.toml", self.endpoints_dir, id);
437 fs::write(&endpoint_path, endpoint_content)
438 .map_err(|e| DockerError::io_error("write", e.to_string()))?;
439
440 Ok(endpoint_info)
441 }
442
443 pub fn update_endpoint(
445 &self,
446 endpoint_id: &str,
447 name: &str,
448 url: &str,
449 use_tls: bool,
450 tls_cert_path: Option<String>,
451 tls_key_path: Option<String>,
452 tls_ca_path: Option<String>,
453 auth_token: Option<String>,
454 labels: HashMap<String, String>,
455 ) -> Result<EndpointInfo> {
456 let endpoint_path = format!("{}/{}.toml", self.endpoints_dir, endpoint_id);
458 if !Path::new(&endpoint_path).exists() {
459 return Err(DockerError::not_found(
460 "endpoint",
461 format!("Endpoint {} not found", endpoint_id),
462 ));
463 }
464
465 let endpoint_content = fs::read_to_string(&endpoint_path)
467 .map_err(|e| DockerError::io_error("read_to_string", e.to_string()))?;
468 let mut endpoint_info: EndpointInfo = toml::from_str(&endpoint_content)
469 .map_err(|e| DockerError::parse_error("EndpointInfo", e.to_string()))?;
470
471 endpoint_info.config.name = name.to_string();
473 endpoint_info.config.url = url.to_string();
474 endpoint_info.config.use_tls = use_tls;
475 endpoint_info.config.tls_cert_path = tls_cert_path;
476 endpoint_info.config.tls_key_path = tls_key_path;
477 endpoint_info.config.tls_ca_path = tls_ca_path;
478 endpoint_info.config.auth_token = auth_token;
479 endpoint_info.config.labels = labels;
480
481 let updated_content = toml::to_string(&endpoint_info)
483 .map_err(|e| DockerError::parse_error("EndpointInfo", e.to_string()))?;
484 fs::write(&endpoint_path, updated_content)
485 .map_err(|e| DockerError::io_error("write", e.to_string()))?;
486
487 Ok(endpoint_info)
488 }
489
490 pub fn delete_endpoint(&self, endpoint_id: &str) -> Result<()> {
492 let endpoint_path = format!("{}/{}.toml", self.endpoints_dir, endpoint_id);
493 if !Path::new(&endpoint_path).exists() {
494 return Err(DockerError::not_found(
495 "endpoint",
496 format!("Endpoint {} not found", endpoint_id),
497 ));
498 }
499
500 fs::remove_file(&endpoint_path)
501 .map_err(|e| DockerError::io_error("remove_file", e.to_string()))?;
502 Ok(())
503 }
504
505 pub fn get_endpoint_info(&self, endpoint_id: &str) -> Result<EndpointInfo> {
507 let endpoint_path = format!("{}/{}.toml", self.endpoints_dir, endpoint_id);
508 if !Path::new(&endpoint_path).exists() {
509 return Err(DockerError::not_found(
510 "endpoint",
511 format!("Endpoint {} not found", endpoint_id),
512 ));
513 }
514
515 let endpoint_content = fs::read_to_string(&endpoint_path)
516 .map_err(|e| DockerError::io_error("read_to_string", e.to_string()))?;
517 let endpoint_info: EndpointInfo = toml::from_str(&endpoint_content)
518 .map_err(|e| DockerError::parse_error("EndpointInfo", e.to_string()))?;
519
520 Ok(endpoint_info)
521 }
522
523 pub fn list_endpoints(&self) -> Result<Vec<EndpointInfo>> {
525 let mut endpoints = Vec::new();
526
527 for entry in fs::read_dir(&self.endpoints_dir)
529 .map_err(|e| DockerError::io_error("read_dir", e.to_string()))?
530 {
531 let entry = entry.map_err(|e| DockerError::io_error("entry", e.to_string()))?;
532 let path = entry.path();
533
534 if path.is_file() && path.extension().unwrap_or_default() == "toml" {
535 let endpoint_content = fs::read_to_string(&path)
536 .map_err(|e| DockerError::io_error("read_to_string", e.to_string()))?;
537 let endpoint_info: EndpointInfo = toml::from_str(&endpoint_content)
538 .map_err(|e| DockerError::parse_error("EndpointInfo", e.to_string()))?;
539 endpoints.push(endpoint_info);
540 }
541 }
542
543 Ok(endpoints)
544 }
545
546 pub fn test_endpoint_connection(&self, endpoint_id: &str) -> Result<EndpointStatus> {
548 Ok(EndpointStatus::Connected)
551 }
552}
553
554impl Default for ConfigManager {
555 fn default() -> Self {
556 Self::new().expect("Failed to create config manager")
557 }
558}