yggdrasil/api/code_runner/
readiness.rs1#![cfg(feature = "server")]
11
12use std::collections::HashSet;
13
14use bollard::query_parameters::ListImagesOptions;
15
16use crate::api::code_runner::languages::{is_supported_lang, LANGUAGES};
17use crate::infra::docker::DOCKER_CLIENT;
18
19pub async fn log_runner_readiness() {
25 let docker = match DOCKER_CLIENT.as_ref() {
26 Some(d) => d,
27 None => {
28 tracing::warn!(
29 "代码运行器已禁用:Docker daemon 未连接。本地开发需运行 Docker 并挂载 socket\
30 (dev 容器见 docker-compose.dev.yml),或设置 DOCKER_SOCKET_PATH"
31 );
32 return;
33 }
34 };
35
36 if let Err(e) = docker.version().await {
38 tracing::warn!("Docker daemon 可达性检查失败:{e},代码运行可能不可用");
39 return;
40 }
41
42 let images = match docker.list_images(None::<ListImagesOptions>).await {
44 Ok(v) => v,
45 Err(e) => {
46 tracing::warn!("Docker 镜像列表查询失败:{e},跳过运行器镜像就绪检查");
47 return;
48 }
49 };
50 let tags: HashSet<String> = images
51 .iter()
52 .flat_map(|i| i.repo_tags.iter().cloned())
53 .collect();
54
55 let registered: Vec<(&String, &str)> = LANGUAGES
57 .iter()
58 .filter(|(k, _)| is_supported_lang(k))
59 .map(|(k, def)| (k, def.image.as_str()))
60 .collect();
61
62 if registered.is_empty() {
63 tracing::warn!("无已注册运行器语言");
64 return;
65 }
66
67 let missing: Vec<&str> = registered
68 .iter()
69 .filter_map(|(_, img)| {
70 if tags.contains(*img) {
71 None
72 } else {
73 Some(*img)
74 }
75 })
76 .collect();
77
78 let lang_names: Vec<&str> = registered.iter().map(|(k, _)| k.as_str()).collect();
79
80 if missing.is_empty() {
81 tracing::info!(
82 "代码运行器就绪:{}/{} 镜像已构建({})",
83 registered.len(),
84 registered.len(),
85 lang_names.join("/")
86 );
87 } else {
88 tracing::warn!(
89 "代码运行器镜像未构建:{}。请在宿主执行 `bash docker/build-runners.sh` 构建后再使用代码运行功能",
90 missing.join("、")
91 );
92 }
93}