Skip to main content

client_core/container/
volumes.rs

1use super::types::DockerManager;
2use crate::DuckError;
3use crate::container::path_utils::{PathProcessor, PathUtilsError};
4use anyhow::Result;
5use docker_compose_types as dct;
6use std::path::Path;
7use tracing::{debug, info, warn};
8
9/// 挂载信息结构体
10#[derive(Debug, Clone)]
11pub struct MountInfo {
12    pub service_name: String,
13    pub container_path: String,
14    pub host_path: Option<String>,
15    pub is_bind_mount: bool,
16}
17
18impl DockerManager {
19    /// 确保所有宿主机挂载目录存在
20    pub async fn ensure_host_volumes_exist(&self) -> Result<()> {
21        info!("🔍 Checking and creating host mount directories...");
22
23        let compose_config = self.load_compose_config()?;
24        let mount_directories = self.extract_mount_directories(&compose_config)?;
25
26        if mount_directories.is_empty() {
27            info!("✅ No host mount directories need to be created");
28            return Ok(());
29        }
30
31        info!("📁 Found {} mount directories to verify", mount_directories.len());
32
33        for mount_info in mount_directories {
34            if let Some(host_path) = &mount_info.host_path {
35                if mount_info.is_bind_mount {
36                    self.create_host_directory_if_not_exists(host_path)?;
37                }
38            }
39        }
40
41        info!("✅ Host mount directory verification completed");
42        Ok(())
43    }
44
45    /// 从compose配置中提取挂载目录信息
46    pub fn extract_mount_directories(&self, compose: &dct::Compose) -> Result<Vec<MountInfo>> {
47        let mut mount_infos = Vec::new();
48
49        for (service_name, service_opt) in &compose.services.0 {
50            if let Some(service) = service_opt {
51                let volumes = &service.volumes;
52                for volume in volumes {
53                    if let Some(mount_info) = self.parse_volume_spec(service_name, volume) {
54                        mount_infos.push(mount_info);
55                    }
56                }
57            }
58        }
59
60        Ok(mount_infos)
61    }
62
63    /// 解析单个volume规范
64    fn parse_volume_spec(&self, service_name: &str, volume: &dct::Volumes) -> Option<MountInfo> {
65        match volume {
66            dct::Volumes::Simple(volume_str) => {
67                let parts: Vec<&str> = volume_str.split(':').collect();
68
69                match parts.len() {
70                    2 | 3 => {
71                        // 格式: host_path:container_path 或 host_path:container_path:mode
72                        let host_path = parts[0];
73                        let container_path = parts[1];
74
75                        let is_bind = self.is_bind_mount_path(host_path);
76
77                        if is_bind {
78                            // 规范化路径(返回 Result)
79                            let normalized_host_path = match self.normalize_path(host_path) {
80                                Ok(path) => path,
81                                Err(e) => {
82                                    warn!("Path normalization failed: {}", e);
83                                    return None;
84                                }
85                            };
86
87                            // 将相对路径转换为相对于compose文件所在目录的绝对路径
88                            let host_path_buf = std::path::PathBuf::from(&normalized_host_path);
89                            let absolute_host_path = if host_path_buf.is_absolute() {
90                                normalized_host_path
91                            } else {
92                                match self.get_working_directory() {
93                                    Some(compose_dir) => compose_dir
94                                        .join(&normalized_host_path)
95                                        .to_string_lossy()
96                                        .to_string(),
97                                    None => {
98                                        return None;
99                                    }
100                                }
101                            };
102                            Some(MountInfo {
103                                service_name: service_name.to_string(),
104                                container_path: container_path.to_string(),
105                                host_path: Some(absolute_host_path),
106                                is_bind_mount: true,
107                            })
108                        } else {
109                            None
110                        }
111                    }
112                    _ => None,
113                }
114            }
115            dct::Volumes::Advanced(volume_def) => {
116                // 处理高级volume定义
117                if let Some(source) = &volume_def.source {
118                    let is_bind = self.is_bind_mount_path(source);
119
120                    if is_bind {
121                        let container_path = &volume_def.target;
122                        // 规范化路径(返回 Result)
123                        let normalized_source = match self.normalize_path(source) {
124                            Ok(path) => path,
125                            Err(e) => {
126                                warn!("Path normalization failed: {}", e);
127                                return None;
128                            }
129                        };
130
131                        // 将相对路径转换为相对于compose文件所在目录的绝对路径
132                        let source_path_buf = std::path::PathBuf::from(&normalized_source);
133                        let absolute_host_path = if source_path_buf.is_absolute() {
134                            normalized_source
135                        } else {
136                            match self.get_working_directory() {
137                                Some(compose_dir) => compose_dir
138                                    .join(&normalized_source)
139                                    .to_string_lossy()
140                                    .to_string(),
141                                None => {
142                                    return None;
143                                }
144                            }
145                        };
146                        return Some(MountInfo {
147                            service_name: service_name.to_string(),
148                            container_path: container_path.to_string(),
149                            host_path: Some(absolute_host_path),
150                            is_bind_mount: true,
151                        });
152                    }
153                }
154                None
155            }
156        }
157    }
158
159    /// 规范化路径(使用新的路径处理器)
160    fn normalize_path(&self, path: &str) -> Result<String, PathUtilsError> {
161        let path_processor = PathProcessor::new(
162            self.runtime_env.host_os.clone(),
163            self.runtime_env.path_format.clone(),
164        );
165        path_processor.normalize_path(path)
166    }
167
168    /// 判断是否为bind mount路径(使用新的路径处理器)
169    fn is_bind_mount_path(&self, path: &str) -> bool {
170        let path_processor = PathProcessor::new(
171            self.runtime_env.host_os.clone(),
172            self.runtime_env.path_format.clone(),
173        );
174        path_processor.is_bind_mount_path(path)
175    }
176
177    /// 创建宿主机目录(如果不存在)
178    fn create_host_directory_if_not_exists(&self, path_str: &str) -> Result<()> {
179        let path = Path::new(path_str);
180
181        if path.exists() {
182            if path.is_dir() {
183                debug!("✅ Directory already exists: {}", path.display());
184                return Ok(());
185            } else {
186                debug!("✅ File already exists: {}", path.display());
187                return Ok(());
188            }
189        }
190
191        // 检查路径是否有扩展名,可能是文件路径
192        let is_likely_file = path.extension().is_some();
193
194        let dir_to_create = if is_likely_file {
195            // 如果是文件路径,创建父目录
196            path.parent()
197        } else {
198            // 如果是目录路径,创建该目录
199            Some(path)
200        };
201
202        if let Some(dir_path) = dir_to_create {
203            match std::fs::create_dir_all(dir_path) {
204                Ok(_) => {
205                    info!("📂 Creating directory: {}", dir_path.display());
206                    Ok(())
207                }
208                Err(e) => {
209                    let error_msg = format!("Failed to create directory {}: {}", dir_path.display(), e);
210                    warn!("❌ {}", error_msg);
211                    Err(DuckError::Docker(error_msg).into())
212                }
213            }
214        } else {
215            Ok(())
216        }
217    }
218}