1use crate::nerdctl::{self, Container, Image, NerdctlError};
6use rhai::{Array, Dynamic, Engine, EvalAltResult, Map};
7use sal_process::CommandResult;
8
9fn nerdctl_error_to_rhai_error<T>(
11 result: Result<T, NerdctlError>,
12) -> Result<T, Box<EvalAltResult>> {
13 result.map_err(|e| {
14 let error_message = match &e {
16 NerdctlError::CommandExecutionFailed(io_err) => {
17 format!("Failed to execute nerdctl command: {}. This may indicate nerdctl is not installed or not in PATH.", io_err)
18 },
19 NerdctlError::CommandFailed(msg) => {
20 format!("Nerdctl command failed: {}. Check container status and logs for more details.", msg)
21 },
22 NerdctlError::JsonParseError(msg) => {
23 format!("Failed to parse nerdctl JSON output: {}. This may indicate an incompatible nerdctl version.", msg)
24 },
25 NerdctlError::ConversionError(msg) => {
26 format!("Data conversion error: {}. This may indicate unexpected output format from nerdctl.", msg)
27 },
28 NerdctlError::Other(msg) => {
29 format!("Nerdctl error: {}. This is an unexpected error.", msg)
30 },
31 };
32 Box::new(EvalAltResult::ErrorRuntime(
33 error_message.into(),
34 rhai::Position::NONE
35 ))
36 })
37}
38
39pub fn container_new(name: &str) -> Result<Container, Box<EvalAltResult>> {
45 nerdctl_error_to_rhai_error(Container::new(name))
46}
47
48pub fn container_from_image(name: &str, image: &str) -> Result<Container, Box<EvalAltResult>> {
50 nerdctl_error_to_rhai_error(Container::from_image(name, image))
51}
52
53pub fn container_reset(container: Container) -> Container {
55 container.reset()
56}
57
58pub fn container_with_port(container: Container, port: &str) -> Container {
60 container.with_port(port)
61}
62
63pub fn container_with_volume(container: Container, volume: &str) -> Container {
65 container.with_volume(volume)
66}
67
68pub fn container_with_env(container: Container, key: &str, value: &str) -> Container {
70 container.with_env(key, value)
71}
72
73pub fn container_with_network(container: Container, network: &str) -> Container {
75 container.with_network(network)
76}
77
78pub fn container_with_network_alias(container: Container, alias: &str) -> Container {
80 container.with_network_alias(alias)
81}
82
83pub fn container_with_cpu_limit(container: Container, cpus: &str) -> Container {
85 container.with_cpu_limit(cpus)
86}
87
88pub fn container_with_memory_limit(container: Container, memory: &str) -> Container {
90 container.with_memory_limit(memory)
91}
92
93pub fn container_with_restart_policy(container: Container, policy: &str) -> Container {
95 container.with_restart_policy(policy)
96}
97
98pub fn container_with_health_check(container: Container, cmd: &str) -> Container {
100 container.with_health_check(cmd)
101}
102
103pub fn container_with_ports(mut container: Container, ports: Array) -> Container {
105 for port in ports.iter() {
106 if port.is_string() {
107 let port_str = port.clone().cast::<String>();
108 container = container.with_port(&port_str);
109 }
110 }
111 container
112}
113
114pub fn container_with_volumes(mut container: Container, volumes: Array) -> Container {
116 for volume in volumes.iter() {
117 if volume.is_string() {
118 let volume_str = volume.clone().cast::<String>();
119 container = container.with_volume(&volume_str);
120 }
121 }
122 container
123}
124
125pub fn container_with_envs(mut container: Container, env_map: Map) -> Container {
127 for (key, value) in env_map.iter() {
128 if value.is_string() {
129 let value_str = value.clone().cast::<String>();
130 container = container.with_env(&key, &value_str);
131 }
132 }
133 container
134}
135
136pub fn container_with_network_aliases(mut container: Container, aliases: Array) -> Container {
138 for alias in aliases.iter() {
139 if alias.is_string() {
140 let alias_str = alias.clone().cast::<String>();
141 container = container.with_network_alias(&alias_str);
142 }
143 }
144 container
145}
146
147pub fn container_with_memory_swap_limit(container: Container, memory_swap: &str) -> Container {
149 container.with_memory_swap_limit(memory_swap)
150}
151
152pub fn container_with_cpu_shares(container: Container, shares: &str) -> Container {
154 container.with_cpu_shares(shares)
155}
156
157pub fn container_with_health_check_options(
159 container: Container,
160 cmd: &str,
161 interval: Option<&str>,
162 timeout: Option<&str>,
163 retries: Option<i64>,
164 start_period: Option<&str>,
165) -> Container {
166 let retries_u32 = retries.map(|r| r as u32);
168 container.with_health_check_options(cmd, interval, timeout, retries_u32, start_period)
169}
170
171pub fn container_with_snapshotter(container: Container, snapshotter: &str) -> Container {
173 container.with_snapshotter(snapshotter)
174}
175
176pub fn container_with_detach(container: Container, detach: bool) -> Container {
178 container.with_detach(detach)
179}
180
181pub fn container_build(container: Container) -> Result<Container, Box<EvalAltResult>> {
186 let container_name = container.name.clone();
188 let image = container
189 .image
190 .clone()
191 .unwrap_or_else(|| "none".to_string());
192 let ports = container.ports.clone();
193 let volumes = container.volumes.clone();
194 let env_vars = container.env_vars.clone();
195
196 let build_result = container.build();
198
199 match build_result {
201 Ok(built_container) => {
202 Ok(built_container)
204 }
205 Err(err) => {
206 let enhanced_error = match err {
208 NerdctlError::CommandFailed(msg) => {
209 let mut enhanced_msg = format!(
211 "Failed to build container '{}' from image '{}': {}",
212 container_name, image, msg
213 );
214
215 if !ports.is_empty() {
217 enhanced_msg.push_str(&format!("\nConfigured ports: {:?}", ports));
218 }
219
220 if !volumes.is_empty() {
221 enhanced_msg.push_str(&format!("\nConfigured volumes: {:?}", volumes));
222 }
223
224 if !env_vars.is_empty() {
225 enhanced_msg.push_str(&format!(
226 "\nConfigured environment variables: {:?}",
227 env_vars
228 ));
229 }
230
231 if msg.contains("not found") || msg.contains("no such image") {
233 enhanced_msg.push_str("\nSuggestion: The specified image may not exist or may not be pulled yet. Try pulling the image first with nerdctl_image_pull().");
234 } else if msg.contains("port is already allocated") {
235 enhanced_msg.push_str("\nSuggestion: One of the specified ports is already in use. Try using a different port or stopping the container using that port.");
236 } else if msg.contains("permission denied") {
237 enhanced_msg.push_str("\nSuggestion: Permission issues detected. Check if you have the necessary permissions to create containers or access the specified volumes.");
238 }
239
240 NerdctlError::CommandFailed(enhanced_msg)
241 }
242 _ => err,
243 };
244
245 nerdctl_error_to_rhai_error(Err(enhanced_error))
246 }
247 }
248}
249
250pub fn container_start(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
256 let container_name = container.name.clone();
258 let container_id = container
259 .container_id
260 .clone()
261 .unwrap_or_else(|| "unknown".to_string());
262
263 let start_result = container.start();
265
266 match start_result {
268 Ok(result) => {
269 Ok(result)
271 }
272 Err(err) => {
273 let enhanced_error = match err {
275 NerdctlError::CommandFailed(msg) => {
276 if msg.contains("already running") {
278 return Ok(CommandResult {
279 stdout: format!("Container {} is already running", container_name),
280 stderr: "".to_string(),
281 success: true,
282 code: 0,
283 });
284 }
285
286 let mut enhanced_msg = format!(
288 "Failed to start container '{}' (ID: {}): {}",
289 container_name, container_id, msg
290 );
291
292 if let Some(image) = &container.image {
294 enhanced_msg.push_str(&format!("\nContainer was using image: {}", image));
295 }
296
297 NerdctlError::CommandFailed(enhanced_msg)
298 }
299 _ => err,
300 };
301
302 nerdctl_error_to_rhai_error(Err(enhanced_error))
303 }
304 }
305}
306
307pub fn container_stop(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
309 nerdctl_error_to_rhai_error(container.stop())
310}
311
312pub fn container_remove(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
314 nerdctl_error_to_rhai_error(container.remove())
315}
316
317pub fn container_exec(
319 container: &mut Container,
320 command: &str,
321) -> Result<CommandResult, Box<EvalAltResult>> {
322 nerdctl_error_to_rhai_error(container.exec(command))
323}
324
325pub fn container_logs(container: &mut Container) -> Result<CommandResult, Box<EvalAltResult>> {
327 let container_name = container.name.clone();
329 let container_id = container
330 .container_id
331 .clone()
332 .unwrap_or_else(|| "unknown".to_string());
333
334 let logs_result = nerdctl::logs(&container_id);
336
337 match logs_result {
338 Ok(result) => Ok(result),
339 Err(err) => {
340 let enhanced_error = NerdctlError::CommandFailed(format!(
342 "Failed to get logs for container '{}' (ID: {}): {}",
343 container_name, container_id, err
344 ));
345
346 nerdctl_error_to_rhai_error(Err(enhanced_error))
347 }
348 }
349}
350
351pub fn container_copy(
353 container: &mut Container,
354 source: &str,
355 dest: &str,
356) -> Result<CommandResult, Box<EvalAltResult>> {
357 nerdctl_error_to_rhai_error(container.copy(source, dest))
358}
359
360pub fn new_run_options() -> Map {
362 let mut map = Map::new();
363 map.insert("name".into(), Dynamic::UNIT);
364 map.insert("detach".into(), Dynamic::from(true));
365 map.insert("ports".into(), Dynamic::from(Array::new()));
366 map.insert("snapshotter".into(), Dynamic::from("native"));
367 map
368}
369
370pub fn nerdctl_run(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
378 nerdctl_error_to_rhai_error(nerdctl::run(image, None, true, None, None))
379}
380
381pub fn nerdctl_run_with_name(image: &str, name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
383 nerdctl_error_to_rhai_error(nerdctl::run(image, Some(name), true, None, None))
384}
385
386pub fn nerdctl_run_with_port(
388 image: &str,
389 name: &str,
390 port: &str,
391) -> Result<CommandResult, Box<EvalAltResult>> {
392 let ports = vec![port];
393 nerdctl_error_to_rhai_error(nerdctl::run(image, Some(name), true, Some(&ports), None))
394}
395
396pub fn nerdctl_exec(container: &str, command: &str) -> Result<CommandResult, Box<EvalAltResult>> {
400 nerdctl_error_to_rhai_error(nerdctl::exec(container, command))
401}
402
403pub fn nerdctl_copy(source: &str, dest: &str) -> Result<CommandResult, Box<EvalAltResult>> {
407 nerdctl_error_to_rhai_error(nerdctl::copy(source, dest))
408}
409
410pub fn nerdctl_stop(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
414 nerdctl_error_to_rhai_error(nerdctl::stop(container))
415}
416
417pub fn nerdctl_remove(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
421 nerdctl_error_to_rhai_error(nerdctl::remove(container))
422}
423
424pub fn nerdctl_list(all: bool) -> Result<CommandResult, Box<EvalAltResult>> {
428 nerdctl_error_to_rhai_error(nerdctl::list(all))
429}
430
431pub fn nerdctl_logs(container: &str) -> Result<CommandResult, Box<EvalAltResult>> {
435 nerdctl_error_to_rhai_error(nerdctl::logs(container))
436}
437
438pub fn nerdctl_images() -> Result<CommandResult, Box<EvalAltResult>> {
446 nerdctl_error_to_rhai_error(nerdctl::images())
447}
448
449pub fn nerdctl_image_remove(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
453 nerdctl_error_to_rhai_error(nerdctl::image_remove(image))
454}
455
456pub fn nerdctl_image_push(
460 image: &str,
461 destination: &str,
462) -> Result<CommandResult, Box<EvalAltResult>> {
463 nerdctl_error_to_rhai_error(nerdctl::image_push(image, destination))
464}
465
466pub fn nerdctl_image_tag(image: &str, new_name: &str) -> Result<CommandResult, Box<EvalAltResult>> {
470 nerdctl_error_to_rhai_error(nerdctl::image_tag(image, new_name))
471}
472
473pub fn nerdctl_image_pull(image: &str) -> Result<CommandResult, Box<EvalAltResult>> {
477 nerdctl_error_to_rhai_error(nerdctl::image_pull(image))
478}
479
480pub fn nerdctl_image_commit(
484 container: &str,
485 image_name: &str,
486) -> Result<CommandResult, Box<EvalAltResult>> {
487 nerdctl_error_to_rhai_error(nerdctl::image_commit(container, image_name))
488}
489
490pub fn nerdctl_image_build(
494 tag: &str,
495 context_path: &str,
496) -> Result<CommandResult, Box<EvalAltResult>> {
497 nerdctl_error_to_rhai_error(nerdctl::image_build(tag, context_path))
498}
499
500pub fn register_nerdctl_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
510 register_nerdctl_types(engine)?;
512
513 engine.register_fn("nerdctl_container_new", container_new);
515 engine.register_fn("nerdctl_container_from_image", container_from_image);
516
517 engine.register_fn("reset", container_reset);
519 engine.register_fn("with_port", container_with_port);
520 engine.register_fn("with_volume", container_with_volume);
521 engine.register_fn("with_env", container_with_env);
522 engine.register_fn("with_network", container_with_network);
523 engine.register_fn("with_network_alias", container_with_network_alias);
524 engine.register_fn("with_cpu_limit", container_with_cpu_limit);
525 engine.register_fn("with_memory_limit", container_with_memory_limit);
526 engine.register_fn("with_restart_policy", container_with_restart_policy);
527 engine.register_fn("with_health_check", container_with_health_check);
528 engine.register_fn("with_ports", container_with_ports);
529 engine.register_fn("with_volumes", container_with_volumes);
530 engine.register_fn("with_envs", container_with_envs);
531 engine.register_fn("with_network_aliases", container_with_network_aliases);
532 engine.register_fn("with_memory_swap_limit", container_with_memory_swap_limit);
533 engine.register_fn("with_cpu_shares", container_with_cpu_shares);
534 engine.register_fn(
535 "with_health_check_options",
536 container_with_health_check_options,
537 );
538 engine.register_fn("with_snapshotter", container_with_snapshotter);
539 engine.register_fn("with_detach", container_with_detach);
540 engine.register_fn("build", container_build);
541 engine.register_fn("start", container_start);
542 engine.register_fn("stop", container_stop);
543 engine.register_fn("remove", container_remove);
544 engine.register_fn("exec", container_exec);
545 engine.register_fn("logs", container_logs);
546 engine.register_fn("copy", container_copy);
547
548 engine.register_fn("nerdctl_run", nerdctl_run);
550 engine.register_fn("nerdctl_run_with_name", nerdctl_run_with_name);
551 engine.register_fn("nerdctl_run_with_port", nerdctl_run_with_port);
552 engine.register_fn("new_run_options", new_run_options);
553 engine.register_fn("nerdctl_exec", nerdctl_exec);
554 engine.register_fn("nerdctl_copy", nerdctl_copy);
555 engine.register_fn("nerdctl_stop", nerdctl_stop);
556 engine.register_fn("nerdctl_remove", nerdctl_remove);
557 engine.register_fn("nerdctl_list", nerdctl_list);
558 engine.register_fn("nerdctl_logs", nerdctl_logs);
559
560 engine.register_fn("nerdctl_images", nerdctl_images);
562 engine.register_fn("nerdctl_image_remove", nerdctl_image_remove);
563 engine.register_fn("nerdctl_image_push", nerdctl_image_push);
564 engine.register_fn("nerdctl_image_tag", nerdctl_image_tag);
565 engine.register_fn("nerdctl_image_pull", nerdctl_image_pull);
566 engine.register_fn("nerdctl_image_commit", nerdctl_image_commit);
567 engine.register_fn("nerdctl_image_build", nerdctl_image_build);
568
569 Ok(())
570}
571
572fn register_nerdctl_types(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
574 engine.register_type_with_name::<Container>("NerdctlContainer");
576
577 engine.register_get("name", |container: &mut Container| container.name.clone());
579 engine.register_get(
580 "container_id",
581 |container: &mut Container| match &container.container_id {
582 Some(id) => id.clone(),
583 None => "".to_string(),
584 },
585 );
586 engine.register_get("image", |container: &mut Container| {
587 match &container.image {
588 Some(img) => img.clone(),
589 None => "".to_string(),
590 }
591 });
592 engine.register_get("ports", |container: &mut Container| {
593 let mut array = Array::new();
594 for port in &container.ports {
595 array.push(Dynamic::from(port.clone()));
596 }
597 array
598 });
599 engine.register_get("volumes", |container: &mut Container| {
600 let mut array = Array::new();
601 for volume in &container.volumes {
602 array.push(Dynamic::from(volume.clone()));
603 }
604 array
605 });
606 engine.register_get("detach", |container: &mut Container| container.detach);
607
608 engine.register_type_with_name::<Image>("NerdctlImage");
610
611 engine.register_get("id", |img: &mut Image| img.id.clone());
613 engine.register_get("repository", |img: &mut Image| img.repository.clone());
614 engine.register_get("tag", |img: &mut Image| img.tag.clone());
615 engine.register_get("size", |img: &mut Image| img.size.clone());
616 engine.register_get("created", |img: &mut Image| img.created.clone());
617
618 Ok(())
619}