添加qiming-rcoder模块

This commit is contained in:
Codex
2026-06-01 13:54:52 +08:00
parent 8092c4b1f8
commit 4b1a580132
539 changed files with 151650 additions and 0 deletions

View File

@@ -0,0 +1,350 @@
//! 容器配置构建器
//!
//! 使用 Builder 模式构建 DockerContainerConfig
use crate::{DockerContainerConfig, DockerResult, MountPoint, ResourceLimits};
use std::collections::HashMap;
use tracing::debug;
/// 容器配置构建器
///
/// 使用 Builder 模式提供灵活的容器配置构建接口
///
/// # Examples
/// ```no_run
/// use docker_manager::container_builder::ContainerConfigBuilder;
///
/// # async fn example() -> docker_manager::DockerResult<()> {
/// let config = ContainerConfigBuilder::new("project-123")
/// .image("registry.example.com/agent-runner:latest")
/// .host_path("/host/path")
/// .container_path("/app/project_workspace/project-123")
/// .env("PROJECT_ID", "project-123")
/// .network_name("rcoder_agent-network")
/// .build()?;
/// # Ok(())
/// # }
/// ```
pub struct ContainerConfigBuilder {
project_id: String,
image: Option<String>,
name_prefix: Option<String>,
host_path: Option<String>,
container_path: Option<String>,
work_dir: Option<String>,
env_vars: HashMap<String, String>,
port_bindings: HashMap<String, String>,
network_mode: Option<String>,
auto_remove: bool,
resource_limits: Option<ResourceLimits>,
extra_mounts: Vec<MountPoint>,
command: Option<Vec<String>>,
entrypoint: Option<Vec<String>>,
network_name: Option<String>,
// 新增字段(隔离类型支持)
pod_id: Option<String>,
tenant_id: Option<String>,
space_id: Option<String>,
isolation_type: Option<String>,
}
impl ContainerConfigBuilder {
/// 创建新的容器配置构建器
///
/// # Arguments
/// * `project_id` - 项目ID必需
pub fn new(project_id: impl Into<String>) -> Self {
Self {
project_id: project_id.into(),
image: None,
name_prefix: None,
host_path: None,
container_path: None,
work_dir: None,
env_vars: HashMap::new(),
port_bindings: HashMap::new(),
network_mode: None,
auto_remove: false,
resource_limits: None,
extra_mounts: Vec::new(),
command: None,
entrypoint: None,
network_name: None,
// 新增字段(隔离类型支持)
pod_id: None,
tenant_id: None,
space_id: None,
isolation_type: None,
}
}
/// 设置 Docker 镜像
pub fn image(mut self, image: impl Into<String>) -> Self {
self.image = Some(image.into());
self
}
/// 设置容器名称前缀
pub fn name_prefix(mut self, prefix: impl Into<String>) -> Self {
self.name_prefix = Some(prefix.into());
self
}
/// 设置宿主机路径
pub fn host_path(mut self, path: impl Into<String>) -> Self {
self.host_path = Some(path.into());
self
}
/// 设置容器内路径
pub fn container_path(mut self, path: impl Into<String>) -> Self {
self.container_path = Some(path.into());
self
}
/// 设置工作目录
pub fn work_dir(mut self, dir: impl Into<String>) -> Self {
self.work_dir = Some(dir.into());
self
}
/// 添加单个环境变量
pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env_vars.insert(key.into(), value.into());
self
}
/// 批量添加环境变量
pub fn envs(mut self, vars: HashMap<String, String>) -> Self {
self.env_vars.extend(vars);
self
}
/// 添加端口映射
pub fn port_binding(
mut self,
container_port: impl Into<String>,
host_port: impl Into<String>,
) -> Self {
self.port_bindings
.insert(container_port.into(), host_port.into());
self
}
/// 批量添加端口映射
pub fn port_bindings(mut self, bindings: HashMap<String, String>) -> Self {
self.port_bindings.extend(bindings);
self
}
/// 设置网络模式
pub fn network_mode(mut self, mode: impl Into<String>) -> Self {
self.network_mode = Some(mode.into());
self
}
/// 设置自动删除标志
pub fn auto_remove(mut self, enabled: bool) -> Self {
self.auto_remove = enabled;
self
}
/// 设置资源限制
pub fn resource_limits(mut self, limits: ResourceLimits) -> Self {
self.resource_limits = Some(limits);
self
}
/// 添加单个挂载点
pub fn add_mount(mut self, mount: MountPoint) -> Self {
self.extra_mounts.push(mount);
self
}
/// 批量添加挂载点
pub fn add_mounts(mut self, mounts: Vec<MountPoint>) -> Self {
self.extra_mounts.extend(mounts);
self
}
/// 设置启动命令
pub fn command(mut self, command: Vec<String>) -> Self {
self.command = Some(command);
self
}
/// 设置入口点
pub fn entrypoint(mut self, entrypoint: Vec<String>) -> Self {
self.entrypoint = Some(entrypoint);
self
}
/// 设置网络名称
pub fn network_name(mut self, name: impl Into<String>) -> Self {
self.network_name = Some(name.into());
self
}
/// 设置 pod_id容器唯一标识用于容器复用
pub fn pod_id(mut self, pod_id: impl Into<String>) -> Self {
self.pod_id = Some(pod_id.into());
self
}
/// 设置 tenant_id租户 ID
pub fn tenant_id(mut self, tenant_id: impl Into<String>) -> Self {
self.tenant_id = Some(tenant_id.into());
self
}
/// 设置 space_id空间 ID
pub fn space_id(mut self, space_id: impl Into<String>) -> Self {
self.space_id = Some(space_id.into());
self
}
/// 设置 isolation_type隔离类型
pub fn isolation_type(mut self, isolation_type: impl Into<String>) -> Self {
self.isolation_type = Some(isolation_type.into());
self
}
/// 构建 DockerContainerConfig
///
/// # Returns
/// * `DockerResult<DockerContainerConfig>` - 构建的配置或错误
pub fn build(self) -> DockerResult<DockerContainerConfig> {
debug!("builtcontainerconfig, projectID: {}", self.project_id);
// 使用默认值或提供的值
let image = self.image.unwrap_or_else(crate::default_docker_image);
let name_prefix = self
.name_prefix
.unwrap_or_else(|| "rcoder-agent".to_string());
let host_path = self.host_path.unwrap_or_default();
let container_path = self
.container_path
.unwrap_or_else(|| crate::DEFAULT_WORK_DIR.to_string());
let work_dir = self
.work_dir
.unwrap_or_else(|| crate::DEFAULT_WORK_DIR.to_string());
let network_mode = self
.network_mode
.unwrap_or_else(|| crate::DEFAULT_NETWORK_MODE.to_string());
let config = DockerContainerConfig {
project_id: self.project_id,
image,
name_prefix,
host_path,
container_path,
work_dir,
env_vars: self.env_vars,
port_bindings: self.port_bindings,
network_mode,
auto_remove: self.auto_remove,
resource_limits: self.resource_limits,
extra_mounts: self.extra_mounts,
command: self.command,
entrypoint: self.entrypoint,
network_name: self.network_name,
// 新增字段(隔离类型支持)
pod_id: self.pod_id,
tenant_id: self.tenant_id,
space_id: self.space_id,
isolation_type: self.isolation_type,
};
debug!(
"Container config built: image={}, network={:?}, mounts={}",
config.image,
config.network_name,
config.extra_mounts.len()
);
Ok(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_minimal() {
let config = ContainerConfigBuilder::new("test-project").build().unwrap();
assert_eq!(config.project_id, "test-project");
assert_eq!(config.name_prefix, "rcoder-agent");
assert!(!config.auto_remove);
}
#[test]
fn test_builder_full() {
let config = ContainerConfigBuilder::new("test-project")
.image("custom-image:latest")
.name_prefix("custom-prefix")
.host_path("/host/path")
.container_path("/container/path")
.work_dir("/work")
.env("KEY1", "value1")
.env("KEY2", "value2")
.port_binding("8080", "8080")
.network_mode("bridge")
.auto_remove(true)
.network_name("test-network")
.build()
.unwrap();
assert_eq!(config.project_id, "test-project");
assert_eq!(config.image, "custom-image:latest");
assert_eq!(config.name_prefix, "custom-prefix");
assert_eq!(config.host_path, "/host/path");
assert_eq!(config.container_path, "/container/path");
assert_eq!(config.work_dir, "/work");
assert_eq!(config.env_vars.len(), 2);
assert_eq!(config.port_bindings.len(), 1);
assert_eq!(config.network_mode, "bridge");
assert!(config.auto_remove);
assert_eq!(config.network_name, Some("test-network".to_string()));
}
#[test]
fn test_builder_with_mounts() {
let mount = MountPoint {
host_path: "/host/mount".to_string(),
container_path: "/container/mount".to_string(),
read_only: false,
};
let config = ContainerConfigBuilder::new("test-project")
.add_mount(mount)
.build()
.unwrap();
assert_eq!(config.extra_mounts.len(), 1);
assert_eq!(config.extra_mounts[0].host_path, "/host/mount");
}
#[test]
fn test_builder_with_resource_limits() {
let limits = ResourceLimits {
memory_limit: Some((512 * 1024 * 1024) as f64), // 512MB
cpu_limit: Some(1.0),
swap_limit: None,
};
let config = ContainerConfigBuilder::new("test-project")
.resource_limits(limits)
.build()
.unwrap();
assert!(config.resource_limits.is_some());
let resource_limits = config.resource_limits.unwrap();
assert_eq!(
resource_limits.memory_limit,
Some((512 * 1024 * 1024) as f64)
);
assert_eq!(resource_limits.cpu_limit, Some(1.0));
}
}

View File

@@ -0,0 +1,9 @@
//! 容器构建器模块
//!
//! 提供 Builder 模式的容器配置构建和挂载点处理功能
pub mod config_builder;
pub mod mount_processor;
pub use config_builder::*;
pub use mount_processor::*;

View File

@@ -0,0 +1,222 @@
//! 挂载点处理器
//!
//! 处理容器挂载点的路径解析和变量替换
use crate::path::HostPathResolver;
use crate::{DockerResult, MountPoint};
use std::collections::HashMap;
use std::path::Path;
use tracing::{debug, info};
/// 挂载点处理器
///
/// 提供挂载点路径解析、变量替换等功能
pub struct MountProcessor {
resolver: HostPathResolver,
}
impl MountProcessor {
/// 创建新的挂载点处理器
///
/// # Arguments
/// * `resolver` - 路径解析器
pub fn new(resolver: HostPathResolver) -> Self {
Self { resolver }
}
/// 创建新的挂载点处理器(异步,自动创建路径解析器)
///
/// # Returns
/// * `DockerResult<Self>` - 挂载点处理器或错误
pub async fn new_async() -> DockerResult<Self> {
let resolver = HostPathResolver::new().await?;
Ok(Self { resolver })
}
/// 使用指定的 Docker socket 创建挂载点处理器
///
/// # Arguments
/// * `docker_socket_path` - Docker socket 路径
///
/// # Returns
/// * `DockerResult<Self>` - 挂载点处理器或错误
pub async fn new_with_docker_socket(docker_socket_path: Option<String>) -> DockerResult<Self> {
let resolver = HostPathResolver::new_with_docker_socket(docker_socket_path).await?;
Ok(Self { resolver })
}
/// 处理单个挂载点
///
/// # Arguments
/// * `container_path` - 容器内路径
/// * `host_path` - 宿主机路径(可能包含变量或相对路径)
/// * `read_only` - 是否只读
/// * `variables` - 变量映射表(可选)
///
/// # Returns
/// * `DockerResult<MountPoint>` - 处理后的挂载点或错误
pub fn process_mount(
&self,
container_path: impl AsRef<str>,
host_path: impl AsRef<str>,
read_only: bool,
variables: Option<&HashMap<String, String>>,
) -> DockerResult<MountPoint> {
let container_path = container_path.as_ref();
let mut host_path = host_path.as_ref().to_string();
debug!(
"Processing mount point: {} -> {} (read_only: {})",
container_path, host_path, read_only
);
// 变量替换
if let Some(vars) = variables {
for (key, value) in vars {
let pattern = format!("{{{}}}", key);
if host_path.contains(&pattern) {
host_path = host_path.replace(&pattern, value);
debug!(" substituted: {} -> {}", pattern, value);
}
}
}
// 路径解析
let normalized_host_path = self.resolve_path(&host_path)?;
info!(
"Mount point processing completed: {} -> {}",
container_path, normalized_host_path
);
Ok(MountPoint {
container_path: container_path.to_string(),
host_path: normalized_host_path,
read_only,
})
}
/// 批量处理挂载点
///
/// # Arguments
/// * `mounts` - 挂载点列表 (container_path, host_path, read_only)
/// * `variables` - 变量映射表(可选)
///
/// # Returns
/// * `DockerResult<Vec<MountPoint>>` - 处理后的挂载点列表或错误
pub fn process_mounts(
&self,
mounts: Vec<(String, String, bool)>,
variables: Option<&HashMap<String, String>>,
) -> DockerResult<Vec<MountPoint>> {
debug!("Processing {} mounts", mounts.len());
let processed: DockerResult<Vec<MountPoint>> = mounts
.into_iter()
.map(|(container_path, host_path, read_only)| {
self.process_mount(&container_path, &host_path, read_only, variables)
})
.collect();
let processed = processed?;
info!(
"Mount processing completed: {} mounts",
processed.len()
);
Ok(processed)
}
/// 解析路径(处理相对路径和容器内路径)
///
/// # Arguments
/// * `path` - 待解析的路径
///
/// # Returns
/// * `DockerResult<String>` - 解析后的宿主机绝对路径
fn resolve_path(&self, path: &str) -> DockerResult<String> {
let path_obj = Path::new(path);
// 处理相对路径:转换为容器内绝对路径
let container_absolute_path = if path_obj.is_relative() {
let current_dir =
std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("/app"));
current_dir.join(path_obj)
} else {
path_obj.to_path_buf()
};
// 检查是否为容器内路径
if container_absolute_path.starts_with("/app") {
// 容器内路径:转换为宿主机路径
debug!(
"Detected container path, resolving to host path: {}",
container_absolute_path.display()
);
let host_abs_path = self
.resolver
.resolve_to_host_path(&container_absolute_path)?;
Ok(host_abs_path.to_string_lossy().to_string())
} else {
// 可能已经是宿主机路径,直接使用
debug!(
"Using potential host path: {}",
container_absolute_path.display()
);
Ok(container_absolute_path.to_string_lossy().to_string())
}
}
/// 获取路径解析器的引用
pub fn resolver(&self) -> &HostPathResolver {
&self.resolver
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mount_processor_variable_substitution() {
// 注意:此测试需要在容器环境中运行才能完整验证路径解析
// 这里仅测试变量替换逻辑
let mut variables = HashMap::new();
variables.insert("project_id".to_string(), "test-123".to_string());
let host_path = "/path/to/{project_id}/data";
let mut result = host_path.to_string();
for (key, value) in &variables {
let pattern = format!("{{{}}}", key);
result = result.replace(&pattern, value);
}
assert_eq!(result, "/path/to/test-123/data");
}
#[test]
fn test_mount_point_structure() {
let mount = MountPoint {
container_path: "/app/data".to_string(),
host_path: "/host/data".to_string(),
read_only: false,
};
assert_eq!(mount.container_path, "/app/data");
assert_eq!(mount.host_path, "/host/data");
assert!(!mount.read_only);
}
// 注意:以下测试需要在 Docker 容器环境中运行
#[tokio::test]
#[ignore]
async fn test_mount_processor_creation() {
// 此测试仅在容器内有效
if std::env::var("HOSTNAME").is_ok() {
let result = MountProcessor::new_async().await;
assert!(result.is_ok());
}
}
}