添加qiming-rcoder模块
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
//! 容器自检测器
|
||||
//!
|
||||
//! 用于在容器内部通过 Docker API 检测自己的挂载信息,
|
||||
//! 获取容器内路径对应的宿主机绝对路径
|
||||
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use bollard::{API_DEFAULT_VERSION, Docker};
|
||||
use tokio::fs;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
/// 容器自检测器
|
||||
///
|
||||
/// 用于检测当前容器的挂载信息,获取容器内路径对应的宿主机路径
|
||||
pub struct ContainerSelfInspector {
|
||||
/// Docker 客户端
|
||||
docker_client: Docker,
|
||||
/// 当前容器ID
|
||||
container_id: String,
|
||||
}
|
||||
|
||||
impl ContainerSelfInspector {
|
||||
/// 创建新的容器自检测器
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `docker_socket_path` - Docker socket 路径
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result<Self>` - 检测器实例或错误
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust,no_run
|
||||
/// use docker_manager::ContainerSelfInspector;
|
||||
///
|
||||
/// # async fn example() -> anyhow::Result<()> {
|
||||
/// let inspector = ContainerSelfInspector::new("/var/run/docker.sock").await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn new(docker_socket_path: &str) -> Result<Self> {
|
||||
info!(
|
||||
"Initializing container self inspector, Docker socket: {}",
|
||||
docker_socket_path
|
||||
);
|
||||
|
||||
// 创建 Docker 客户端
|
||||
let docker_client =
|
||||
Docker::connect_with_socket(docker_socket_path, 120, API_DEFAULT_VERSION)
|
||||
.context("Failed to connect to Docker socket")?;
|
||||
|
||||
// 测试 Docker 连接
|
||||
docker_client
|
||||
.ping()
|
||||
.await
|
||||
.context("Failed to test Docker connection, please check socket path and permissions")?;
|
||||
|
||||
info!("Docker connectionsucceeded");
|
||||
|
||||
// 获取当前容器ID
|
||||
let container_id = Self::get_current_container_id()
|
||||
.await
|
||||
.context("Failed to get current container ID")?;
|
||||
|
||||
info!("Detected container ID: {}", container_id);
|
||||
|
||||
Ok(Self {
|
||||
docker_client,
|
||||
container_id,
|
||||
})
|
||||
}
|
||||
|
||||
/// 检测容器内路径对应的宿主机路径
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `container_path` - 容器内路径(如 "/app/project_workspace")
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result<String>` - 宿主机绝对路径或错误
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust,no_run
|
||||
/// use docker_manager::ContainerSelfInspector;
|
||||
///
|
||||
/// # async fn example() -> anyhow::Result<()> {
|
||||
/// # let inspector = ContainerSelfInspector::new("/var/run/docker.sock").await?;
|
||||
/// let host_path = inspector.detect_host_path_for_container_dir("/app/project_workspace").await?;
|
||||
/// println!(" message path: {:?}", host_path);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn detect_host_path_for_container_dir(&self, container_path: &str) -> Result<String> {
|
||||
info!("Detecting path {} host path", container_path);
|
||||
|
||||
// 获取容器详细信息
|
||||
let inspect_result = self
|
||||
.docker_client
|
||||
.inspect_container(
|
||||
&self.container_id,
|
||||
None::<bollard::query_parameters::InspectContainerOptions>,
|
||||
)
|
||||
.await
|
||||
.context("Failed to call Docker inspect API")?;
|
||||
|
||||
debug!(
|
||||
"Container inspect result: {:?}",
|
||||
serde_json::to_string_pretty(&inspect_result)?
|
||||
);
|
||||
|
||||
// 解析挂载信息
|
||||
if let Some(mounts) = inspect_result.mounts {
|
||||
debug!("Container has {} mounts", mounts.len());
|
||||
|
||||
for (index, mount) in mounts.iter().enumerate() {
|
||||
let mount_destination = mount
|
||||
.destination
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("mount {} has no destination field", index))?
|
||||
.clone();
|
||||
|
||||
debug!(
|
||||
"Mount point {}: {} -> {}",
|
||||
index,
|
||||
mount_destination,
|
||||
mount.source.as_ref().unwrap_or(&String::new()).clone()
|
||||
);
|
||||
|
||||
// 检查是否是我们要找的路径
|
||||
if mount_destination == container_path {
|
||||
let host_path = mount
|
||||
.source
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("mount {} has no source field", index))?
|
||||
.clone();
|
||||
|
||||
info!(
|
||||
" mount: {} -> {}",
|
||||
container_path, host_path
|
||||
);
|
||||
return Ok(host_path);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没找到,列出所有挂载点供调试
|
||||
warn!(
|
||||
"not found path {} in mount, mount info:",
|
||||
container_path
|
||||
);
|
||||
for (index, mount) in mounts.iter().enumerate() {
|
||||
if let (Some(dest), Some(source)) = (&mount.destination, &mount.source) {
|
||||
warn!(" {}: {} -> {}", index, dest, source);
|
||||
}
|
||||
}
|
||||
|
||||
bail!("mount info for path {} not found", container_path);
|
||||
} else {
|
||||
bail!("container has no mount info (mounts field is empty)");
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取当前容器ID
|
||||
///
|
||||
/// 通过读取 `/proc/self/cgroup` 文件解析容器ID
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result<String>` - 容器ID或错误
|
||||
async fn get_current_container_id() -> Result<String> {
|
||||
debug!("starting get containerID");
|
||||
|
||||
let cgroup_content = fs::read_to_string("/proc/self/cgroup")
|
||||
.await
|
||||
.with_context(|| "Failed to read /proc/self/cgroup file")?;
|
||||
|
||||
debug!("cgroup file: {}", cgroup_content);
|
||||
|
||||
// 解析 cgroup 文件获取容器ID
|
||||
// 格式示例: 12:perf_event:/docker/abc123def456...
|
||||
for line in cgroup_content.lines() {
|
||||
debug!(" cgroup line: {}", line);
|
||||
|
||||
let parts: Vec<&str> = line.split(':').collect();
|
||||
if parts.len() >= 3 {
|
||||
let cgroup_path = parts[2];
|
||||
|
||||
// 检查是否是 Docker 容器
|
||||
if cgroup_path.contains("/docker/") || cgroup_path.contains(".scope") {
|
||||
debug!("Found Docker cgroup: {}", cgroup_path);
|
||||
|
||||
// 提取容器ID
|
||||
let container_id = if cgroup_path.contains("/docker/") {
|
||||
// 格式: /docker/abc123def456...
|
||||
let id_parts: Vec<&str> = cgroup_path.split('/').collect();
|
||||
if id_parts.len() >= 3 {
|
||||
id_parts[2].to_string()
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if cgroup_path.contains(".scope") {
|
||||
// 格式: /system.slice/docker-abc123def456...scope
|
||||
let scope_name = cgroup_path.split('/').next_back().unwrap_or("");
|
||||
if scope_name.starts_with("docker-") && scope_name.ends_with(".scope") {
|
||||
// 移除 "docker-" 前缀和 ".scope" 后缀
|
||||
let id = &scope_name[7..scope_name.len() - 6];
|
||||
id.to_string()
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// 验证容器ID格式(应该是64个字符的十六进制字符串)
|
||||
if container_id.len() == 64
|
||||
&& container_id.chars().all(|c| c.is_ascii_hexdigit())
|
||||
{
|
||||
info!("Detected container ID: {}", container_id);
|
||||
return Ok(container_id);
|
||||
} else {
|
||||
debug!("Skipping container ID: {}", container_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果 cgroup 方法失败,尝试其他方法
|
||||
warn!("Unable to get container ID from cgroup");
|
||||
|
||||
// 方法2:尝试读取 /proc/1/cgroup(主进程)
|
||||
if let Ok(cgroup_content) = fs::read_to_string("/proc/1/cgroup").await {
|
||||
debug!(" reading /proc/1/cgroup");
|
||||
for line in cgroup_content.lines() {
|
||||
if line.contains("/docker/") || line.contains(".scope") {
|
||||
debug!(" /proc/1/cgroup line: {}", line);
|
||||
// 类似的解析逻辑...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 方法3:尝试读取主机名(某些环境容器ID会作为主机名)
|
||||
if let Ok(hostname) = std::env::var("HOSTNAME") {
|
||||
debug!("check HOSTNAME: {}", hostname);
|
||||
if hostname.len() == 12 && hostname.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
// 可能是短格式的容器ID(前12位)
|
||||
info!(
|
||||
" HOSTNAME get containerID: {}",
|
||||
hostname
|
||||
);
|
||||
return Ok(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
bail!("unable to get current container ID, please ensure container has sufficient permissions to access /proc/self/cgroup");
|
||||
}
|
||||
|
||||
/// 验证 Docker socket 连接
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result<()>` - 连接成功或错误
|
||||
pub async fn verify_docker_connection(&self) -> Result<()> {
|
||||
self.docker_client
|
||||
.ping()
|
||||
.await
|
||||
.context("Docker socket connection test failed")?;
|
||||
info!("Docker socket connection succeeded");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取容器所有挂载点信息(用于调试)
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result<Vec<(String, String)>>` - 挂载点列表(容器路径 -> 宿主机路径)
|
||||
pub async fn get_all_mounts(&self) -> Result<Vec<(String, String)>> {
|
||||
let inspect_result = self
|
||||
.docker_client
|
||||
.inspect_container(
|
||||
&self.container_id,
|
||||
None::<bollard::query_parameters::InspectContainerOptions>,
|
||||
)
|
||||
.await
|
||||
.context("Failed to call Docker inspect API")?;
|
||||
|
||||
let mut mounts = Vec::new();
|
||||
|
||||
if let Some(mount_infos) = inspect_result.mounts {
|
||||
for mount in mount_infos {
|
||||
if let (Some(dest), Some(source)) = (&mount.destination, &mount.source) {
|
||||
mounts.push((dest.clone(), source.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(mounts)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_container_id_parsing() {
|
||||
// 这里应该模拟 cgroup 文件内容进行测试
|
||||
// 由于测试环境不在容器内,这个测试可能需要跳过
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user