1use std::net::IpAddr;
4use std::net::SocketAddr;
5use std::str::FromStr;
6
7use bollard::Docker;
8use color_eyre::Result;
9
10use crate::models::DockerPortMap;
11use crate::models::DockerPortMapRequest;
12
13pub fn connect() -> Result<Docker> {
15 lab_ops_lab_lib::docker::connect()
16}
17
18pub async fn get_port_mappings(docker: &Docker, c_id: &str) -> Result<Vec<DockerPortMap>> {
24 let inspect = docker.inspect_container(c_id, None).await?;
25 let c_name = inspect
26 .name
27 .as_deref()
28 .map(lab_ops_lab_lib::docker::trim_container_name)
29 .unwrap_or("unknown")
30 .to_string();
31
32 let Some(network_settings) = inspect.network_settings else {
33 return Ok(vec![]);
34 };
35
36 let Some(c_ip) = network_settings.networks.as_ref().and_then(|networks| {
38 networks.values().find_map(|net| {
39 net.ip_address
40 .as_ref()
41 .filter(|ip| !ip.is_empty())
42 .and_then(|ip| IpAddr::from_str(ip).ok())
43 })
44 }) else {
45 tracing::debug!(container.id = %c_id, "container has no IP address, skipping ports");
46 return Ok(vec![]);
47 };
48
49 let Some(ports) = network_settings.ports else {
50 return Ok(vec![]);
51 };
52
53 let mut mappings = vec![];
54 for (port_proto, bindings) in ports {
55 let Some(bindings) = bindings else { continue };
56
57 let parts: Vec<&str> = port_proto.split('/').collect();
59 if parts.len() != 2 {
60 continue;
62 }
63
64 let Ok(c_port) = u16::from_str(parts[0]) else {
65 continue;
66 };
67
68 let Ok(proto) = parts[1].to_lowercase().parse() else {
69 continue;
70 };
71
72 let container_addr = SocketAddr::new(c_ip, c_port);
73
74 for bind in bindings {
75 let Some(host_port) = bind
76 .host_port
77 .as_deref()
78 .and_then(|s| s.split('-').next())
80 .and_then(|s| s.parse::<u16>().ok())
81 else {
82 continue;
83 };
84
85 let host_ip_str = bind.host_ip.as_deref().unwrap_or_default();
86 let ips: &[&str] = if host_ip_str.is_empty() || host_ip_str == "0.0.0.0" {
87 &["0.0.0.0", "::"]
88 } else {
89 &[host_ip_str]
90 };
91 mappings.extend(ips.iter().filter_map(|ip| {
92 let host_ip = IpAddr::from_str(ip).ok()?;
93 let req = DockerPortMapRequest {
94 host_addr: SocketAddr::new(host_ip, host_port),
95 container_addr,
96 proto,
97 };
98 Some(DockerPortMap::new(0, req, c_id.to_string(), c_name.clone()))
99 }));
100 }
101 }
102
103 Ok(mappings)
104}