添加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,185 @@
//! 网络检测功能
//!
//! 从 container_manager.rs 迁移的网络检测逻辑
use crate::{DockerError, DockerResult, RCODER_NETWORK_BASE_NAME};
use bollard::{Docker, query_parameters::InspectContainerOptions};
use tracing::{debug, info, warn};
/// 网络检测器
pub struct NetworkDetector<'a> {
docker: &'a Docker,
}
impl<'a> NetworkDetector<'a> {
/// 创建新的网络检测器
pub fn new(docker: &'a Docker) -> Self {
Self { docker }
}
/// 动态检测当前主容器所在的网络名称
///
/// 通过检查当前容器的网络配置来确定主网络名称
///
/// # Returns
/// * `DockerResult<String>` - 网络名称或错误
pub async fn detect_main_network(&self) -> DockerResult<String> {
// 从 HOSTNAME 环境变量获取容器ID
let hostname = std::env::var("HOSTNAME").map_err(|_| {
DockerError::ConnectionError(
"unable to get HOSTNAME environment variable. Please ensure code is running in Docker container.".to_string(),
)
})?;
debug!("Detecting container hostname: {}", hostname);
// Inspect 当前容器
let inspect = self
.docker
.inspect_container(&hostname, None::<InspectContainerOptions>)
.await
.map_err(|e| {
DockerError::ConnectionError(format!(
"unable to get current container info (hostname: {}): {}",
hostname, e
))
})?;
// 获取网络配置
if let Some(network_settings) = inspect.network_settings
&& let Some(networks) = network_settings.networks
{
// 查找包含 "agent-network" 的网络
for network_name in networks.keys() {
if network_name.contains(RCODER_NETWORK_BASE_NAME) {
info!(" detected network: {}", network_name);
return Ok(network_name.clone());
}
}
// 如果没找到回退到第一个可用网络Docker Compose 默认网络)
if let Some(fallback_name) = networks.keys().next() {
warn!(
"未找到包含 '{}' 的网络,回退使用当前容器的默认网络: {} (可用网络: {:?})",
RCODER_NETWORK_BASE_NAME,
fallback_name,
networks.keys().collect::<Vec<_>>()
);
return Ok(fallback_name.clone());
}
return Err(DockerError::ConnectionError(
"当前容器没有任何网络配置".to_string(),
));
}
Err(DockerError::ConnectionError(format!(
"Current container (hostname: {}) has no network configuration information",
hostname
)))
}
/// 从容器 labels 获取 Compose 项目名称
///
/// # Arguments
/// * `container_id` - 容器ID或hostname
///
/// # Returns
/// * `DockerResult<Option<String>>` - 项目名称或None
pub async fn get_compose_project_from_labels(
&self,
container_id: &str,
) -> DockerResult<Option<String>> {
let inspect = self
.docker
.inspect_container(container_id, None::<InspectContainerOptions>)
.await
.map_err(|e| DockerError::ConnectionError(format!("failed to get container info: {}", e)))?;
// 从 labels 中获取项目名称
if let Some(labels) = inspect.config.and_then(|c| c.labels) {
// Docker Compose 会添加 com.docker.compose.project 标签
if let Some(project_name) = labels.get("com.docker.compose.project") {
info!(
" container labels get project: {}",
project_name
);
return Ok(Some(project_name.clone()));
}
}
Ok(None)
}
/// 从容器名称推断 Compose 项目名称
///
/// # Arguments
/// * `container_id` - 容器ID或hostname
///
/// # Returns
/// * `DockerResult<Option<String>>` - 项目名称或None
pub async fn get_compose_project_from_name(
&self,
container_id: &str,
) -> DockerResult<Option<String>> {
let inspect = self
.docker
.inspect_container(container_id, None::<InspectContainerOptions>)
.await
.map_err(|e| DockerError::ConnectionError(format!("failed to get container info: {}", e)))?;
// 从容器名称推断项目名称
if let Some(name) = inspect.name {
// 容器名称格式: /{project_name}-{service_name}-{number}
let clean_name = name.trim_start_matches('/');
if let Some(project_name) = clean_name.split('-').next() {
info!(
" container project: {}",
project_name
);
return Ok(Some(project_name.to_string()));
}
}
Ok(None)
}
/// 动态获取 Compose 项目名称(多种策略)
///
/// 尝试多种方法获取项目名称:
/// 1. 环境变量 COMPOSE_PROJECT_NAME
/// 2. 容器 labels
/// 3. 容器名称推断
///
/// # Arguments
/// * `container_id` - 容器ID或hostname (None表示使用当前容器)
///
/// # Returns
/// * `DockerResult<Option<String>>` - 项目名称或None
pub async fn get_dynamic_compose_project(
&self,
container_id: Option<&str>,
) -> DockerResult<Option<String>> {
// 方法1: 环境变量
if let Ok(project_name) = std::env::var("COMPOSE_PROJECT_NAME") {
info!(" got project: {}", project_name);
return Ok(Some(project_name));
}
let hostname = std::env::var("HOSTNAME").unwrap_or_else(|_| "self".to_string());
let cid = container_id.unwrap_or(&hostname);
// 方法2: 容器 labels
if let Some(project_name) = self.get_compose_project_from_labels(cid).await? {
return Ok(Some(project_name));
}
// 方法3: 容器名称推断
if let Some(project_name) = self.get_compose_project_from_name(cid).await? {
return Ok(Some(project_name));
}
warn!("Unable to get Docker Compose project");
Ok(None)
}
}

View File

@@ -0,0 +1,9 @@
//! Docker 网络管理模块
//!
//! 提供网络检测、Compose 项目名称获取等功能
pub mod detection;
pub mod utils;
pub use detection::*;
pub use utils::*;

View File

@@ -0,0 +1,65 @@
//! 网络工具函数
/// 根据项目名称构建网络名称
///
/// # Arguments
/// * `project_name` - Docker Compose 项目名称
///
/// # Returns
/// * `String` - 完整的网络名称
///
/// # Examples
/// ```
/// use docker_manager::network::build_network_name;
/// let network = build_network_name("rcoder");
/// // 返回: "rcoder_agent-network"
/// ```
pub fn build_network_name(project_name: &str) -> String {
format!("{}_{}", project_name, crate::RCODER_NETWORK_BASE_NAME)
}
/// 解析网络名称获取项目名称
///
/// # Arguments
/// * `network_name` - 完整的网络名称
///
/// # Returns
/// * `Option<String>` - 项目名称,如果无法解析返回 None
pub fn parse_project_from_network(network_name: &str) -> Option<String> {
if let Some(pos) = network_name.rfind('_') {
let suffix = &network_name[pos + 1..];
if suffix == crate::RCODER_NETWORK_BASE_NAME {
return Some(network_name[..pos].to_string());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_network_name() {
let network = build_network_name("rcoder");
assert_eq!(network, "rcoder_agent-network");
let network = build_network_name("myproject");
assert_eq!(network, "myproject_agent-network");
}
#[test]
fn test_parse_project_from_network() {
assert_eq!(
parse_project_from_network("rcoder_agent-network"),
Some("rcoder".to_string())
);
assert_eq!(
parse_project_from_network("myproject_agent-network"),
Some("myproject".to_string())
);
assert_eq!(parse_project_from_network("invalid_network"), None);
}
}