1use super::container_types::{Container, ContainerStatus, ResourceUsage};
4use crate::nerdctl::{execute_nerdctl_command, NerdctlError};
5use sal_process::CommandResult;
6use serde_json;
7
8impl Container {
9 pub fn start(&self) -> Result<CommandResult, NerdctlError> {
16 let container = if self.container_id.is_none() {
18 if self.image.is_none() {
20 return Err(NerdctlError::Other(
21 "No image specified for container creation".to_string(),
22 ));
23 }
24
25 println!("Container not created yet. Creating container from image...");
27
28 let image = self.image.as_ref().unwrap();
30 match execute_nerdctl_command(&["image", "inspect", image]) {
31 Err(_) => {
32 println!("Image '{}' not found locally. Pulling image...", image);
33 if let Err(e) = execute_nerdctl_command(&["pull", image]) {
34 return Err(NerdctlError::CommandFailed(format!(
35 "Failed to pull image '{}': {}",
36 image, e
37 )));
38 }
39 println!("Image '{}' pulled successfully.", image);
40 }
41 Ok(_) => {
42 println!("Image '{}' found locally.", image);
43 }
44 }
45
46 match self.clone().build() {
48 Ok(built) => built,
49 Err(e) => {
50 return Err(NerdctlError::CommandFailed(format!(
51 "Failed to create container from image '{}': {}",
52 image, e
53 )));
54 }
55 }
56 } else {
57 self.clone()
59 };
60
61 if let Some(container_id) = &container.container_id {
62 let start_result = execute_nerdctl_command(&["start", container_id]);
64
65 if let Err(err) = &start_result {
67 return Err(NerdctlError::CommandFailed(format!(
68 "Failed to start container {}: {}",
69 container_id, err
70 )));
71 }
72
73 match container.verify_running() {
75 Ok(true) => start_result,
76 Ok(false) => {
77 let mut error_message =
79 format!("Container {} started but is not running.", container_id);
80
81 if let Ok(status) = container.status() {
83 error_message.push_str(&format!(
84 "\nStatus: {}, State: {}, Health: {}",
85 status.status,
86 status.state,
87 status.health_status.unwrap_or_else(|| "N/A".to_string())
88 ));
89 }
90
91 if let Ok(logs) = execute_nerdctl_command(&["logs", container_id]) {
93 if !logs.stdout.trim().is_empty() {
94 error_message.push_str(&format!(
95 "\nContainer logs (stdout):\n{}",
96 logs.stdout.trim()
97 ));
98 }
99 if !logs.stderr.trim().is_empty() {
100 error_message.push_str(&format!(
101 "\nContainer logs (stderr):\n{}",
102 logs.stderr.trim()
103 ));
104 }
105 }
106
107 if let Ok(inspect_result) = execute_nerdctl_command(&[
109 "inspect",
110 "--format",
111 "{{.State.ExitCode}}",
112 container_id,
113 ]) {
114 let exit_code = inspect_result.stdout.trim();
115 if !exit_code.is_empty() && exit_code != "0" {
116 error_message
117 .push_str(&format!("\nContainer exit code: {}", exit_code));
118 }
119 }
120
121 Err(NerdctlError::CommandFailed(error_message))
122 }
123 Err(err) => {
124 Err(NerdctlError::CommandFailed(format!(
126 "Container {} may have started, but verification failed: {}",
127 container_id, err
128 )))
129 }
130 }
131 } else {
132 Err(NerdctlError::Other(
133 "Failed to create container. No container ID available.".to_string(),
134 ))
135 }
136 }
137
138 fn verify_running(&self) -> Result<bool, NerdctlError> {
144 if let Some(container_id) = &self.container_id {
145 let inspect_result = execute_nerdctl_command(&[
147 "inspect",
148 "--format",
149 "{{.State.Running}}",
150 container_id,
151 ]);
152
153 match inspect_result {
154 Ok(result) => {
155 let running = result.stdout.trim().to_lowercase() == "true";
156 Ok(running)
157 }
158 Err(err) => Err(err),
159 }
160 } else {
161 Err(NerdctlError::Other("No container ID available".to_string()))
162 }
163 }
164
165 pub fn stop(&self) -> Result<CommandResult, NerdctlError> {
171 if let Some(container_id) = &self.container_id {
172 execute_nerdctl_command(&["stop", container_id])
173 } else {
174 Err(NerdctlError::Other("No container ID available".to_string()))
175 }
176 }
177
178 pub fn remove(&self) -> Result<CommandResult, NerdctlError> {
184 if let Some(container_id) = &self.container_id {
185 execute_nerdctl_command(&["rm", container_id])
186 } else {
187 Err(NerdctlError::Other("No container ID available".to_string()))
188 }
189 }
190
191 pub fn exec(&self, command: &str) -> Result<CommandResult, NerdctlError> {
201 if let Some(container_id) = &self.container_id {
202 execute_nerdctl_command(&["exec", container_id, "sh", "-c", command])
203 } else {
204 Err(NerdctlError::Other("No container ID available".to_string()))
205 }
206 }
207
208 pub fn copy(&self, source: &str, dest: &str) -> Result<CommandResult, NerdctlError> {
219 if self.container_id.is_some() {
220 execute_nerdctl_command(&["cp", source, dest])
221 } else {
222 Err(NerdctlError::Other("No container ID available".to_string()))
223 }
224 }
225
226 pub fn export(&self, path: &str) -> Result<CommandResult, NerdctlError> {
236 if let Some(container_id) = &self.container_id {
237 execute_nerdctl_command(&["export", "-o", path, container_id])
238 } else {
239 Err(NerdctlError::Other("No container ID available".to_string()))
240 }
241 }
242
243 pub fn commit(&self, image_name: &str) -> Result<CommandResult, NerdctlError> {
253 if let Some(container_id) = &self.container_id {
254 execute_nerdctl_command(&["commit", container_id, image_name])
255 } else {
256 Err(NerdctlError::Other("No container ID available".to_string()))
257 }
258 }
259
260 pub fn status(&self) -> Result<ContainerStatus, NerdctlError> {
266 if let Some(container_id) = &self.container_id {
267 let result = execute_nerdctl_command(&["inspect", container_id])?;
268
269 match serde_json::from_str::<serde_json::Value>(&result.stdout) {
271 Ok(json) => {
272 if let Some(container_json) = json.as_array().and_then(|arr| arr.first()) {
273 let state = container_json
274 .get("State")
275 .and_then(|state| state.get("Status"))
276 .and_then(|status| status.as_str())
277 .unwrap_or("unknown")
278 .to_string();
279
280 let status = container_json
281 .get("State")
282 .and_then(|state| state.get("Running"))
283 .and_then(|running| {
284 if running.as_bool().unwrap_or(false) {
285 Some("running")
286 } else {
287 Some("stopped")
288 }
289 })
290 .unwrap_or("unknown")
291 .to_string();
292
293 let created = container_json
294 .get("Created")
295 .and_then(|created| created.as_str())
296 .unwrap_or("unknown")
297 .to_string();
298
299 let started = container_json
300 .get("State")
301 .and_then(|state| state.get("StartedAt"))
302 .and_then(|started| started.as_str())
303 .unwrap_or("unknown")
304 .to_string();
305
306 let health_status = container_json
308 .get("State")
309 .and_then(|state| state.get("Health"))
310 .and_then(|health| health.get("Status"))
311 .and_then(|status| status.as_str())
312 .map(|s| s.to_string());
313
314 let health_output = container_json
316 .get("State")
317 .and_then(|state| state.get("Health"))
318 .and_then(|health| health.get("Log"))
319 .and_then(|log| log.as_array())
320 .and_then(|log_array| log_array.last())
321 .and_then(|last_log| last_log.get("Output"))
322 .and_then(|output| output.as_str())
323 .map(|s| s.to_string());
324
325 Ok(ContainerStatus {
326 state,
327 status,
328 created,
329 started,
330 health_status,
331 health_output,
332 })
333 } else {
334 Err(NerdctlError::JsonParseError(
335 "Invalid container inspect JSON".to_string(),
336 ))
337 }
338 }
339 Err(e) => Err(NerdctlError::JsonParseError(format!(
340 "Failed to parse container inspect JSON: {}",
341 e
342 ))),
343 }
344 } else {
345 Err(NerdctlError::Other("No container ID available".to_string()))
346 }
347 }
348
349 pub fn health_status(&self) -> Result<String, NerdctlError> {
355 if let Some(container_id) = &self.container_id {
356 let result = execute_nerdctl_command(&[
357 "inspect",
358 "--format",
359 "{{.State.Health.Status}}",
360 container_id,
361 ])?;
362 Ok(result.stdout.trim().to_string())
363 } else {
364 Err(NerdctlError::Other("No container ID available".to_string()))
365 }
366 }
367
368 pub fn logs(&self) -> Result<CommandResult, NerdctlError> {
374 if let Some(container_id) = &self.container_id {
375 execute_nerdctl_command(&["logs", container_id])
376 } else {
377 Err(NerdctlError::Other("No container ID available".to_string()))
378 }
379 }
380
381 pub fn resources(&self) -> Result<ResourceUsage, NerdctlError> {
387 if let Some(container_id) = &self.container_id {
388 let result = execute_nerdctl_command(&["stats", "--no-stream", container_id])?;
389
390 let lines: Vec<&str> = result.stdout.lines().collect();
392 if lines.len() >= 2 {
393 let headers = lines[0];
394 let values = lines[1];
395
396 let headers_vec: Vec<&str> = headers.split_whitespace().collect();
397 let values_vec: Vec<&str> = values.split_whitespace().collect();
398
399 let cpu_index = headers_vec
401 .iter()
402 .position(|&h| h.contains("CPU"))
403 .unwrap_or(0);
404 let mem_index = headers_vec
405 .iter()
406 .position(|&h| h.contains("MEM"))
407 .unwrap_or(0);
408 let mem_perc_index = headers_vec
409 .iter()
410 .position(|&h| h.contains("MEM%"))
411 .unwrap_or(0);
412 let net_in_index = headers_vec
413 .iter()
414 .position(|&h| h.contains("NET"))
415 .unwrap_or(0);
416 let net_out_index = if net_in_index > 0 {
417 net_in_index + 1
418 } else {
419 0
420 };
421 let block_in_index = headers_vec
422 .iter()
423 .position(|&h| h.contains("BLOCK"))
424 .unwrap_or(0);
425 let block_out_index = if block_in_index > 0 {
426 block_in_index + 1
427 } else {
428 0
429 };
430 let pids_index = headers_vec
431 .iter()
432 .position(|&h| h.contains("PIDS"))
433 .unwrap_or(0);
434
435 let cpu_usage = if cpu_index < values_vec.len() {
436 values_vec[cpu_index].to_string()
437 } else {
438 "unknown".to_string()
439 };
440
441 let memory_usage = if mem_index < values_vec.len() {
442 values_vec[mem_index].to_string()
443 } else {
444 "unknown".to_string()
445 };
446
447 let memory_limit = if mem_index + 1 < values_vec.len() {
448 values_vec[mem_index + 1].to_string()
449 } else {
450 "unknown".to_string()
451 };
452
453 let memory_percentage = if mem_perc_index < values_vec.len() {
454 values_vec[mem_perc_index].to_string()
455 } else {
456 "unknown".to_string()
457 };
458
459 let network_input = if net_in_index < values_vec.len() {
460 values_vec[net_in_index].to_string()
461 } else {
462 "unknown".to_string()
463 };
464
465 let network_output = if net_out_index < values_vec.len() {
466 values_vec[net_out_index].to_string()
467 } else {
468 "unknown".to_string()
469 };
470
471 let block_input = if block_in_index < values_vec.len() {
472 values_vec[block_in_index].to_string()
473 } else {
474 "unknown".to_string()
475 };
476
477 let block_output = if block_out_index < values_vec.len() {
478 values_vec[block_out_index].to_string()
479 } else {
480 "unknown".to_string()
481 };
482
483 let pids = if pids_index < values_vec.len() {
484 values_vec[pids_index].to_string()
485 } else {
486 "unknown".to_string()
487 };
488
489 Ok(ResourceUsage {
490 cpu_usage,
491 memory_usage,
492 memory_limit,
493 memory_percentage,
494 network_input,
495 network_output,
496 block_input,
497 block_output,
498 pids,
499 })
500 } else {
501 Err(NerdctlError::ConversionError(
502 "Failed to parse stats output".to_string(),
503 ))
504 }
505 } else {
506 Err(NerdctlError::Other("No container ID available".to_string()))
507 }
508 }
509}