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