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