mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-29 04:10:00 +08:00
feat(hermes): add terminal sandbox image config
This commit is contained in:
@@ -4309,6 +4309,12 @@ function normalizeHermesOptionalModelInteger(value, key) {
|
||||
return parseHermesInteger(raw, key, 0, 1, 10000000, true)
|
||||
}
|
||||
|
||||
function normalizeHermesOptionalString(value, key) {
|
||||
if (value == null || value === '') return ''
|
||||
if (typeof value !== 'string') throw new Error(`${key} 必须是字符串`)
|
||||
return value.trim()
|
||||
}
|
||||
|
||||
export function buildHermesModelConfigValues(config = {}) {
|
||||
const root = config && typeof config === 'object' && !Array.isArray(config) ? config : {}
|
||||
const model = root.model && typeof root.model === 'object' && !Array.isArray(root.model) ? root.model : {}
|
||||
@@ -5153,6 +5159,10 @@ export function buildHermesTerminalConfigValues(config = {}) {
|
||||
terminalLifetimeSeconds: parseHermesInteger(terminal.lifetime_seconds, 'terminal.lifetime_seconds', 300, 0, 86400, false),
|
||||
terminalDockerMountCwdToWorkspace: readHermesBool(terminal.docker_mount_cwd_to_workspace, false),
|
||||
terminalDockerRunAsHostUser: readHermesBool(terminal.docker_run_as_host_user, false),
|
||||
terminalDockerImage: typeof terminal.docker_image === 'string' ? terminal.docker_image.trim() : '',
|
||||
terminalSingularityImage: typeof terminal.singularity_image === 'string' ? terminal.singularity_image.trim() : '',
|
||||
terminalModalImage: typeof terminal.modal_image === 'string' ? terminal.modal_image.trim() : '',
|
||||
terminalDaytonaImage: typeof terminal.daytona_image === 'string' ? terminal.daytona_image.trim() : '',
|
||||
terminalContainerCpu: parseHermesInteger(terminal.container_cpu, 'terminal.container_cpu', 1, 1, 64, false),
|
||||
terminalContainerMemory: parseHermesInteger(terminal.container_memory, 'terminal.container_memory', 5120, 128, 1048576, false),
|
||||
terminalContainerDisk: parseHermesInteger(terminal.container_disk, 'terminal.container_disk', 51200, 1024, 10485760, false),
|
||||
@@ -5172,6 +5182,16 @@ export function mergeHermesTerminalConfig(config = {}, form = {}) {
|
||||
terminal.lifetime_seconds = parseHermesInteger(Object.hasOwn(form, 'terminalLifetimeSeconds') ? form.terminalLifetimeSeconds : currentValues.terminalLifetimeSeconds, 'terminal.lifetime_seconds', 300, 0, 86400, true)
|
||||
terminal.docker_mount_cwd_to_workspace = formHermesBool(form, 'terminalDockerMountCwdToWorkspace', currentValues.terminalDockerMountCwdToWorkspace)
|
||||
terminal.docker_run_as_host_user = formHermesBool(form, 'terminalDockerRunAsHostUser', currentValues.terminalDockerRunAsHostUser)
|
||||
for (const [formKey, yamlKey] of [
|
||||
['terminalDockerImage', 'docker_image'],
|
||||
['terminalSingularityImage', 'singularity_image'],
|
||||
['terminalModalImage', 'modal_image'],
|
||||
['terminalDaytonaImage', 'daytona_image'],
|
||||
]) {
|
||||
const image = normalizeHermesOptionalString(Object.hasOwn(form, formKey) ? form[formKey] : currentValues[formKey], `terminal.${yamlKey}`)
|
||||
if (image) terminal[yamlKey] = image
|
||||
else delete terminal[yamlKey]
|
||||
}
|
||||
terminal.container_cpu = parseHermesInteger(Object.hasOwn(form, 'terminalContainerCpu') ? form.terminalContainerCpu : currentValues.terminalContainerCpu, 'terminal.container_cpu', 1, 1, 64, true)
|
||||
terminal.container_memory = parseHermesInteger(Object.hasOwn(form, 'terminalContainerMemory') ? form.terminalContainerMemory : currentValues.terminalContainerMemory, 'terminal.container_memory', 5120, 128, 1048576, true)
|
||||
terminal.container_disk = parseHermesInteger(Object.hasOwn(form, 'terminalContainerDisk') ? form.terminalContainerDisk : currentValues.terminalContainerDisk, 'terminal.container_disk', 51200, 1024, 10485760, true)
|
||||
|
||||
@@ -2407,6 +2407,14 @@ fn yaml_string_field(map: &serde_yaml::Mapping, key: &str) -> Option<String> {
|
||||
.map(|v| v.to_string())
|
||||
}
|
||||
|
||||
fn set_optional_yaml_string(map: &mut serde_yaml::Mapping, key: &str, value: String) {
|
||||
if value.is_empty() {
|
||||
map.remove(yaml_key(key));
|
||||
} else {
|
||||
map.insert(yaml_key(key), serde_yaml::Value::String(value));
|
||||
}
|
||||
}
|
||||
|
||||
fn yaml_string_sequence_field(map: &serde_yaml::Mapping, key: &str) -> Vec<String> {
|
||||
yaml_get(map, key)
|
||||
.and_then(|value| value.as_sequence())
|
||||
@@ -7826,6 +7834,13 @@ fn merge_hermes_execution_limits_config(
|
||||
fn build_hermes_terminal_config_values(config: &serde_yaml::Value) -> Value {
|
||||
let root = config.as_mapping();
|
||||
let terminal = root.and_then(|map| yaml_get_mapping(map, "terminal"));
|
||||
let terminal_string = |key: &str| {
|
||||
terminal
|
||||
.and_then(|map| yaml_string_field(map, key))
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let terminal_backend = normalize_hermes_terminal_backend(
|
||||
terminal.and_then(|map| yaml_string_field(map, "backend")),
|
||||
false,
|
||||
@@ -7848,6 +7863,10 @@ fn build_hermes_terminal_config_values(config: &serde_yaml::Value) -> Value {
|
||||
let terminal_docker_run_as_host_user = terminal
|
||||
.and_then(|map| yaml_bool_field(map, "docker_run_as_host_user"))
|
||||
.unwrap_or(false);
|
||||
let terminal_docker_image = terminal_string("docker_image");
|
||||
let terminal_singularity_image = terminal_string("singularity_image");
|
||||
let terminal_modal_image = terminal_string("modal_image");
|
||||
let terminal_daytona_image = terminal_string("daytona_image");
|
||||
let terminal_container_cpu = terminal
|
||||
.map(|map| bounded_hermes_i64(yaml_i64_field(map, "container_cpu"), 1, 1, 64))
|
||||
.unwrap_or(1);
|
||||
@@ -7868,6 +7887,10 @@ fn build_hermes_terminal_config_values(config: &serde_yaml::Value) -> Value {
|
||||
"terminalLifetimeSeconds": terminal_lifetime_seconds,
|
||||
"terminalDockerMountCwdToWorkspace": terminal_docker_mount_cwd_to_workspace,
|
||||
"terminalDockerRunAsHostUser": terminal_docker_run_as_host_user,
|
||||
"terminalDockerImage": terminal_docker_image,
|
||||
"terminalSingularityImage": terminal_singularity_image,
|
||||
"terminalModalImage": terminal_modal_image,
|
||||
"terminalDaytonaImage": terminal_daytona_image,
|
||||
"terminalContainerCpu": terminal_container_cpu,
|
||||
"terminalContainerMemory": terminal_container_memory,
|
||||
"terminalContainerDisk": terminal_container_disk,
|
||||
@@ -7935,6 +7958,42 @@ fn merge_hermes_terminal_config(
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
});
|
||||
let terminal_docker_image = form_string(form, "terminalDockerImage")
|
||||
.or_else(|| {
|
||||
current["terminalDockerImage"]
|
||||
.as_str()
|
||||
.map(ToString::to_string)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
let terminal_singularity_image = form_string(form, "terminalSingularityImage")
|
||||
.or_else(|| {
|
||||
current["terminalSingularityImage"]
|
||||
.as_str()
|
||||
.map(ToString::to_string)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
let terminal_modal_image = form_string(form, "terminalModalImage")
|
||||
.or_else(|| {
|
||||
current["terminalModalImage"]
|
||||
.as_str()
|
||||
.map(ToString::to_string)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
let terminal_daytona_image = form_string(form, "terminalDaytonaImage")
|
||||
.or_else(|| {
|
||||
current["terminalDaytonaImage"]
|
||||
.as_str()
|
||||
.map(ToString::to_string)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
let terminal_container_cpu = validate_hermes_i64(
|
||||
if form.get("terminalContainerCpu").is_some() {
|
||||
form_i64(form, "terminalContainerCpu")
|
||||
@@ -7998,6 +8057,10 @@ fn merge_hermes_terminal_config(
|
||||
yaml_key("docker_run_as_host_user"),
|
||||
serde_yaml::Value::Bool(terminal_docker_run_as_host_user),
|
||||
);
|
||||
set_optional_yaml_string(terminal, "docker_image", terminal_docker_image);
|
||||
set_optional_yaml_string(terminal, "singularity_image", terminal_singularity_image);
|
||||
set_optional_yaml_string(terminal, "modal_image", terminal_modal_image);
|
||||
set_optional_yaml_string(terminal, "daytona_image", terminal_daytona_image);
|
||||
terminal.insert(
|
||||
yaml_key("container_cpu"),
|
||||
serde_yaml::Value::Number(terminal_container_cpu.into()),
|
||||
@@ -16515,6 +16578,10 @@ mod hermes_terminal_config_tests {
|
||||
assert_eq!(values["terminalContainerMemory"], 5120);
|
||||
assert_eq!(values["terminalContainerDisk"], 51200);
|
||||
assert_eq!(values["terminalContainerPersistent"], true);
|
||||
assert_eq!(values["terminalDockerImage"], "");
|
||||
assert_eq!(values["terminalSingularityImage"], "");
|
||||
assert_eq!(values["terminalModalImage"], "");
|
||||
assert_eq!(values["terminalDaytonaImage"], "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -16528,6 +16595,10 @@ terminal:
|
||||
lifetime_seconds: 1800
|
||||
docker_mount_cwd_to_workspace: true
|
||||
docker_run_as_host_user: true
|
||||
docker_image: nikolaik/python-nodejs:python3.11-nodejs20
|
||||
singularity_image: docker://nikolaik/python-nodejs:python3.11-nodejs20
|
||||
modal_image: python:3.12
|
||||
daytona_image: ubuntu:24.04
|
||||
container_cpu: 4
|
||||
container_memory: 8192
|
||||
container_disk: 102400
|
||||
@@ -16542,6 +16613,16 @@ terminal:
|
||||
assert_eq!(values["terminalLifetimeSeconds"], 1800);
|
||||
assert_eq!(values["terminalDockerMountCwdToWorkspace"], true);
|
||||
assert_eq!(values["terminalDockerRunAsHostUser"], true);
|
||||
assert_eq!(
|
||||
values["terminalDockerImage"],
|
||||
"nikolaik/python-nodejs:python3.11-nodejs20"
|
||||
);
|
||||
assert_eq!(
|
||||
values["terminalSingularityImage"],
|
||||
"docker://nikolaik/python-nodejs:python3.11-nodejs20"
|
||||
);
|
||||
assert_eq!(values["terminalModalImage"], "python:3.12");
|
||||
assert_eq!(values["terminalDaytonaImage"], "ubuntu:24.04");
|
||||
assert_eq!(values["terminalContainerCpu"], 4);
|
||||
assert_eq!(values["terminalContainerMemory"], 8192);
|
||||
assert_eq!(values["terminalContainerDisk"], 102400);
|
||||
@@ -16575,6 +16656,10 @@ streaming:
|
||||
"terminalLifetimeSeconds": "1200",
|
||||
"terminalDockerMountCwdToWorkspace": true,
|
||||
"terminalDockerRunAsHostUser": true,
|
||||
"terminalDockerImage": "nikolaik/python-nodejs:python3.12-nodejs22",
|
||||
"terminalSingularityImage": "docker://ubuntu:24.04",
|
||||
"terminalModalImage": "debian:bookworm",
|
||||
"terminalDaytonaImage": "ubuntu:22.04",
|
||||
"terminalContainerCpu": "2",
|
||||
"terminalContainerMemory": "6144",
|
||||
"terminalContainerDisk": "20480",
|
||||
@@ -16597,6 +16682,22 @@ streaming:
|
||||
config["terminal"]["docker_run_as_host_user"].as_bool(),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
config["terminal"]["docker_image"].as_str(),
|
||||
Some("nikolaik/python-nodejs:python3.12-nodejs22")
|
||||
);
|
||||
assert_eq!(
|
||||
config["terminal"]["singularity_image"].as_str(),
|
||||
Some("docker://ubuntu:24.04")
|
||||
);
|
||||
assert_eq!(
|
||||
config["terminal"]["modal_image"].as_str(),
|
||||
Some("debian:bookworm")
|
||||
);
|
||||
assert_eq!(
|
||||
config["terminal"]["daytona_image"].as_str(),
|
||||
Some("ubuntu:22.04")
|
||||
);
|
||||
assert_eq!(config["terminal"]["container_cpu"].as_i64(), Some(2));
|
||||
assert_eq!(config["terminal"]["container_memory"].as_i64(), Some(6144));
|
||||
assert_eq!(config["terminal"]["container_disk"].as_i64(), Some(20480));
|
||||
@@ -16604,10 +16705,6 @@ streaming:
|
||||
config["terminal"]["container_persistent"].as_bool(),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
config["terminal"]["docker_image"].as_str(),
|
||||
Some("custom/python-node")
|
||||
);
|
||||
assert_eq!(
|
||||
config["terminal"]["docker_forward_env"][0].as_str(),
|
||||
Some("GITHUB_TOKEN")
|
||||
@@ -16618,6 +16715,41 @@ streaming:
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_terminal_config_removes_empty_images() {
|
||||
let mut config: serde_yaml::Value = serde_yaml::from_str(
|
||||
r#"
|
||||
terminal:
|
||||
docker_image: old-docker
|
||||
singularity_image: old-singularity
|
||||
modal_image: old-modal
|
||||
daytona_image: old-daytona
|
||||
custom_flag: keep-terminal
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
merge_hermes_terminal_config(
|
||||
&mut config,
|
||||
&json!({
|
||||
"terminalDockerImage": "",
|
||||
"terminalSingularityImage": " ",
|
||||
"terminalModalImage": "",
|
||||
"terminalDaytonaImage": " ",
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(config["terminal"]["docker_image"].is_null());
|
||||
assert!(config["terminal"]["singularity_image"].is_null());
|
||||
assert!(config["terminal"]["modal_image"].is_null());
|
||||
assert!(config["terminal"]["daytona_image"].is_null());
|
||||
assert_eq!(
|
||||
config["terminal"]["custom_flag"].as_str(),
|
||||
Some("keep-terminal")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_terminal_config_rejects_invalid_values() {
|
||||
let mut config = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
|
||||
|
||||
@@ -262,6 +262,10 @@ const TERMINAL_DEFAULTS = {
|
||||
terminalLifetimeSeconds: 300,
|
||||
terminalDockerMountCwdToWorkspace: false,
|
||||
terminalDockerRunAsHostUser: false,
|
||||
terminalDockerImage: '',
|
||||
terminalSingularityImage: '',
|
||||
terminalModalImage: '',
|
||||
terminalDaytonaImage: '',
|
||||
terminalContainerCpu: 1,
|
||||
terminalContainerMemory: 5120,
|
||||
terminalContainerDisk: 51200,
|
||||
@@ -1991,6 +1995,22 @@ export function render() {
|
||||
</div>
|
||||
<div class="hm-config-subtitle">${t('engine.hermesTerminalConfigContainerTitle')}</div>
|
||||
<div class="hm-config-runtime-grid hm-config-terminal-grid">
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesTerminalConfigDockerImage')}</span>
|
||||
<input id="hm-terminal-docker-image" class="hm-input" value="${esc(terminalValues.terminalDockerImage)}" placeholder="nikolaik/python-nodejs:python3.11-nodejs20" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesTerminalConfigSingularityImage')}</span>
|
||||
<input id="hm-terminal-singularity-image" class="hm-input" value="${esc(terminalValues.terminalSingularityImage)}" placeholder="docker://nikolaik/python-nodejs:python3.11-nodejs20" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesTerminalConfigModalImage')}</span>
|
||||
<input id="hm-terminal-modal-image" class="hm-input" value="${esc(terminalValues.terminalModalImage)}" placeholder="nikolaik/python-nodejs:python3.11-nodejs20" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesTerminalConfigDaytonaImage')}</span>
|
||||
<input id="hm-terminal-daytona-image" class="hm-input" value="${esc(terminalValues.terminalDaytonaImage)}" placeholder="nikolaik/python-nodejs:python3.11-nodejs20" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesTerminalConfigContainerCpu')}</span>
|
||||
<input id="hm-terminal-container-cpu" class="hm-input" type="number" inputmode="numeric" min="1" max="64" step="1" value="${esc(terminalValues.terminalContainerCpu)}" ${disabled ? 'disabled' : ''}>
|
||||
@@ -3674,6 +3694,10 @@ export function render() {
|
||||
terminalLifetimeSeconds: el.querySelector('#hm-terminal-lifetime-seconds')?.value || '300',
|
||||
terminalDockerMountCwdToWorkspace: !!el.querySelector('#hm-terminal-docker-mount-cwd-to-workspace')?.checked,
|
||||
terminalDockerRunAsHostUser: !!el.querySelector('#hm-terminal-docker-run-as-host-user')?.checked,
|
||||
terminalDockerImage: el.querySelector('#hm-terminal-docker-image')?.value || '',
|
||||
terminalSingularityImage: el.querySelector('#hm-terminal-singularity-image')?.value || '',
|
||||
terminalModalImage: el.querySelector('#hm-terminal-modal-image')?.value || '',
|
||||
terminalDaytonaImage: el.querySelector('#hm-terminal-daytona-image')?.value || '',
|
||||
terminalContainerCpu: el.querySelector('#hm-terminal-container-cpu')?.value || '1',
|
||||
terminalContainerMemory: el.querySelector('#hm-terminal-container-memory')?.value || '5120',
|
||||
terminalContainerDisk: el.querySelector('#hm-terminal-container-disk')?.value || '51200',
|
||||
|
||||
@@ -521,10 +521,14 @@ export default {
|
||||
hermesTerminalConfigDockerRunAsHostUser: _('Docker 使用宿主用户运行', 'Run Docker as host user', 'Docker 使用宿主使用者執行'),
|
||||
hermesTerminalConfigContainerPersistent: _('容器文件系统持久化', 'Persist container filesystem', '容器檔案系統持久化'),
|
||||
hermesTerminalConfigContainerTitle: _('容器资源限制', 'Container resource limits', '容器資源限制'),
|
||||
hermesTerminalConfigDockerImage: _('Docker 镜像(可选)', 'Docker image (optional)', 'Docker 映像(可選)'),
|
||||
hermesTerminalConfigSingularityImage: _('Singularity 镜像(可选)', 'Singularity image (optional)', 'Singularity 映像(可選)'),
|
||||
hermesTerminalConfigModalImage: _('Modal 镜像(可选)', 'Modal image (optional)', 'Modal 映像(可選)'),
|
||||
hermesTerminalConfigDaytonaImage: _('Daytona 镜像(可选)', 'Daytona image (optional)', 'Daytona 映像(可選)'),
|
||||
hermesTerminalConfigContainerCpu: _('CPU 核数', 'CPU cores', 'CPU 核心數'),
|
||||
hermesTerminalConfigContainerMemory: _('内存 MB', 'Memory MB', '記憶體 MB'),
|
||||
hermesTerminalConfigContainerDisk: _('磁盘 MB', 'Disk MB', '磁碟 MB'),
|
||||
hermesTerminalConfigFootnote: _('Docker 挂载启动目录会把宿主目录暴露给沙箱,仅在可信项目和无人值守任务中开启;SSH、镜像、环境变量等高级参数仍可在 raw YAML 中编辑。', 'Mounting the launch cwd exposes host files to the sandbox. Enable it only for trusted projects or unattended jobs. SSH, image, and env advanced fields remain editable in raw YAML.', 'Docker 掛載啟動目錄會把宿主目錄暴露給沙箱,僅在可信專案和無人值守任務中開啟;SSH、映像、環境變數等進階參數仍可在 raw YAML 中編輯。'),
|
||||
hermesTerminalConfigFootnote: _('镜像字段只在对应 Docker、Singularity、Modal 或 Daytona 后端生效;留空会移除覆盖并使用 Hermes 默认值。Docker 挂载启动目录会把宿主目录暴露给沙箱,仅在可信项目和无人值守任务中开启;SSH、环境变量等高级参数仍可在 raw YAML 中编辑。', 'Image fields only apply to the matching Docker, Singularity, Modal, or Daytona backend. Leaving them blank removes the override and uses Hermes defaults. Mounting the launch cwd exposes host files to the sandbox; enable it only for trusted projects or unattended jobs. SSH and environment advanced fields remain editable in raw YAML.', '映像欄位只在對應 Docker、Singularity、Modal 或 Daytona 後端生效;留空會移除覆蓋並使用 Hermes 預設值。Docker 掛載啟動目錄會把宿主目錄暴露給沙箱,僅在可信專案和無人值守任務中開啟;SSH、環境變數等進階參數仍可在 raw YAML 中編輯。'),
|
||||
hermesStreamingConfigTitle: _('网关流式输出', 'Gateway streaming output', '閘道流式輸出'),
|
||||
hermesStreamingConfigDesc: _('控制 Hermes Gateway 回复时是否边生成边更新消息,以及消息刷新节奏。适合需要更快看到长回复进度的渠道。', 'Control whether Hermes Gateway updates messages while replies are generated, plus the refresh cadence. Useful when channels need quicker progress for long replies.', '控制 Hermes Gateway 回覆時是否邊生成邊更新訊息,以及訊息刷新節奏。適合需要更快看到長回覆進度的渠道。'),
|
||||
hermesStreamingConfigStatusReady: _('结构化配置', 'structured settings', '結構化設定'),
|
||||
|
||||
@@ -392,6 +392,10 @@ test('Hermes 配置页会暴露终端执行结构化配置字段', () => {
|
||||
'hm-terminal-lifetime-seconds',
|
||||
'hm-terminal-docker-mount-cwd-to-workspace',
|
||||
'hm-terminal-docker-run-as-host-user',
|
||||
'hm-terminal-docker-image',
|
||||
'hm-terminal-singularity-image',
|
||||
'hm-terminal-modal-image',
|
||||
'hm-terminal-daytona-image',
|
||||
'hm-terminal-container-cpu',
|
||||
'hm-terminal-container-memory',
|
||||
'hm-terminal-container-disk',
|
||||
|
||||
@@ -20,6 +20,10 @@ test('Hermes 终端执行配置读取会提供上游默认值', () => {
|
||||
terminalContainerMemory: 5120,
|
||||
terminalContainerDisk: 51200,
|
||||
terminalContainerPersistent: true,
|
||||
terminalDockerImage: '',
|
||||
terminalSingularityImage: '',
|
||||
terminalModalImage: '',
|
||||
terminalDaytonaImage: '',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -32,6 +36,10 @@ test('Hermes 终端执行配置读取会回显 YAML 字段', () => {
|
||||
lifetime_seconds: 1800,
|
||||
docker_mount_cwd_to_workspace: true,
|
||||
docker_run_as_host_user: true,
|
||||
docker_image: 'nikolaik/python-nodejs:python3.11-nodejs20',
|
||||
singularity_image: 'docker://nikolaik/python-nodejs:python3.11-nodejs20',
|
||||
modal_image: 'python:3.12',
|
||||
daytona_image: 'ubuntu:24.04',
|
||||
container_cpu: 4,
|
||||
container_memory: 8192,
|
||||
container_disk: 102400,
|
||||
@@ -45,6 +53,10 @@ test('Hermes 终端执行配置读取会回显 YAML 字段', () => {
|
||||
assert.equal(values.terminalLifetimeSeconds, 1800)
|
||||
assert.equal(values.terminalDockerMountCwdToWorkspace, true)
|
||||
assert.equal(values.terminalDockerRunAsHostUser, true)
|
||||
assert.equal(values.terminalDockerImage, 'nikolaik/python-nodejs:python3.11-nodejs20')
|
||||
assert.equal(values.terminalSingularityImage, 'docker://nikolaik/python-nodejs:python3.11-nodejs20')
|
||||
assert.equal(values.terminalModalImage, 'python:3.12')
|
||||
assert.equal(values.terminalDaytonaImage, 'ubuntu:24.04')
|
||||
assert.equal(values.terminalContainerCpu, 4)
|
||||
assert.equal(values.terminalContainerMemory, 8192)
|
||||
assert.equal(values.terminalContainerDisk, 102400)
|
||||
@@ -68,6 +80,10 @@ test('Hermes 终端执行配置保存会保留未知字段并写入上游结构'
|
||||
terminalLifetimeSeconds: '1200',
|
||||
terminalDockerMountCwdToWorkspace: true,
|
||||
terminalDockerRunAsHostUser: true,
|
||||
terminalDockerImage: 'nikolaik/python-nodejs:python3.12-nodejs22',
|
||||
terminalSingularityImage: 'docker://ubuntu:24.04',
|
||||
terminalModalImage: 'debian:bookworm',
|
||||
terminalDaytonaImage: 'ubuntu:22.04',
|
||||
terminalContainerCpu: '2',
|
||||
terminalContainerMemory: '6144',
|
||||
terminalContainerDisk: '20480',
|
||||
@@ -82,15 +98,41 @@ test('Hermes 终端执行配置保存会保留未知字段并写入上游结构'
|
||||
assert.equal(next.terminal.lifetime_seconds, 1200)
|
||||
assert.equal(next.terminal.docker_mount_cwd_to_workspace, true)
|
||||
assert.equal(next.terminal.docker_run_as_host_user, true)
|
||||
assert.equal(next.terminal.docker_image, 'nikolaik/python-nodejs:python3.12-nodejs22')
|
||||
assert.equal(next.terminal.singularity_image, 'docker://ubuntu:24.04')
|
||||
assert.equal(next.terminal.modal_image, 'debian:bookworm')
|
||||
assert.equal(next.terminal.daytona_image, 'ubuntu:22.04')
|
||||
assert.equal(next.terminal.container_cpu, 2)
|
||||
assert.equal(next.terminal.container_memory, 6144)
|
||||
assert.equal(next.terminal.container_disk, 20480)
|
||||
assert.equal(next.terminal.container_persistent, false)
|
||||
assert.equal(next.terminal.docker_image, 'custom/python-node')
|
||||
assert.deepEqual(next.terminal.docker_forward_env, ['GITHUB_TOKEN'])
|
||||
assert.equal(next.terminal.custom_flag, 'keep-terminal')
|
||||
})
|
||||
|
||||
test('Hermes 终端执行配置保存空镜像会删除对应字段', () => {
|
||||
const next = mergeHermesTerminalConfig({
|
||||
terminal: {
|
||||
docker_image: 'old-docker',
|
||||
singularity_image: 'old-singularity',
|
||||
modal_image: 'old-modal',
|
||||
daytona_image: 'old-daytona',
|
||||
custom_flag: 'keep-terminal',
|
||||
},
|
||||
}, {
|
||||
terminalDockerImage: '',
|
||||
terminalSingularityImage: ' ',
|
||||
terminalModalImage: '',
|
||||
terminalDaytonaImage: ' ',
|
||||
})
|
||||
|
||||
assert.equal(Object.hasOwn(next.terminal, 'docker_image'), false)
|
||||
assert.equal(Object.hasOwn(next.terminal, 'singularity_image'), false)
|
||||
assert.equal(Object.hasOwn(next.terminal, 'modal_image'), false)
|
||||
assert.equal(Object.hasOwn(next.terminal, 'daytona_image'), false)
|
||||
assert.equal(next.terminal.custom_flag, 'keep-terminal')
|
||||
})
|
||||
|
||||
test('Hermes 终端执行配置保存会拒绝非法后端和越界值', () => {
|
||||
assert.throws(
|
||||
() => mergeHermesTerminalConfig({}, { terminalBackend: 'unsafe' }),
|
||||
|
||||
Reference in New Issue
Block a user