feat(dameng): support JVM system properties
This commit is contained in:
parent
31f0b7d6ef
commit
7f5cb3b0cc
|
|
@ -40,6 +40,7 @@ import { MQ_PINNED_VERSION_OPTIONS, pinnedVersionToSelection, selectionToPinnedV
|
|||
import { mongodbAuthFailureHint, mongoUrlParam, mongoUrlParamIsTrue, normalizeMongoTlsFormState, setMongoUrlParam, setMongoUrlParamBoolean } from "@/lib/mongo/mongoConnectionOptions";
|
||||
import { mysqlCleartextPasswordAuthEnabled, setMysqlCleartextPasswordAuthEnabled } from "@/lib/database/mysqlConnectionOptions";
|
||||
import { applyDamengSslUrlParams, damengSslFormConfig } from "@/lib/database/damengSslOptions";
|
||||
import { DamengJvmSystemPropertyError, damengJvmSystemPropertiesText, parseDamengJvmSystemProperties } from "@/lib/database/damengJvmOptions";
|
||||
import { copyToClipboard } from "@/lib/common/clipboard";
|
||||
import { configuredDatabaseProductName, connectionConfigFingerprint, databaseInfoCopyText, databaseInfoRows, normalizeDatabaseConnectionInfo, type DatabaseInfoField } from "@/lib/connection/connectionDatabaseInfo";
|
||||
import { agentDriverInstallKey, appendAgentDriverUpdateHint, hasAgentDriverUpdate, showAgentDriverInstallHint, type AgentDriverInstallState, type DriverStoreFocus } from "@/lib/connection/agentDriverInstallHint";
|
||||
|
|
@ -532,6 +533,7 @@ const hiveKrb5ConfPath = ref("");
|
|||
const hiveJaasConfigPath = ref("");
|
||||
const hiveUseSubjectCredsOnlyFalse = ref(false);
|
||||
const hiveExtraJavaOptions = ref("");
|
||||
const damengJvmOptions = ref("");
|
||||
const dialogStep = ref<DialogStep>("select");
|
||||
const dbPickerView = ref<DbPickerView>(loadConnectionPickerView());
|
||||
const dbSearchQuery = ref("");
|
||||
|
|
@ -1180,6 +1182,10 @@ function resetHiveKerberosFields(config?: Pick<ConnectionConfig, "url_params" |
|
|||
hiveExtraJavaOptions.value = kerberos.extraJavaOptions;
|
||||
}
|
||||
|
||||
function resetDamengJvmOptions(config?: Pick<ConnectionConfig, "agent_java_options">) {
|
||||
damengJvmOptions.value = damengJvmSystemPropertiesText(config?.agent_java_options);
|
||||
}
|
||||
|
||||
function buildInfluxDbExternalConfig(): InfluxDbExternalConfig {
|
||||
if (influxDbVersion.value !== "2") return { version: "1" };
|
||||
const org = influxDbOrg.value.trim();
|
||||
|
|
@ -1850,6 +1856,7 @@ function applyProfile(val: string, preserveConnectionFields = false) {
|
|||
form.value.username = profile.user;
|
||||
form.value.url_params = profile.urlParams || "";
|
||||
form.value.agent_java_options = [];
|
||||
damengJvmOptions.value = "";
|
||||
if (profile.host) {
|
||||
form.value.host = profile.host;
|
||||
}
|
||||
|
|
@ -2013,6 +2020,7 @@ watch(
|
|||
}
|
||||
resetElasticsearchProxyFields(config.db_type === "elasticsearch" ? config.external_config : undefined);
|
||||
resetHiveKerberosFields(config.db_type === "hive" ? config : undefined);
|
||||
resetDamengJvmOptions(config.db_type === "dameng" ? config : undefined);
|
||||
h2ConnectionMode.value = h2ConnectionModeForConfig(config);
|
||||
customColorInput.value = config.color || "";
|
||||
selectedTransportLayerId.value = form.value.transport_layers?.[0]?.id || null;
|
||||
|
|
@ -2050,6 +2058,7 @@ watch(
|
|||
resetInfluxDbFields();
|
||||
resetElasticsearchProxyFields();
|
||||
resetHiveKerberosFields();
|
||||
resetDamengJvmOptions();
|
||||
oceanbaseSubMode.value = "mysql";
|
||||
h2ConnectionMode.value = "file";
|
||||
dremioConnectionMode.value = "legacy";
|
||||
|
|
@ -3137,6 +3146,15 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo
|
|||
});
|
||||
config.url_params = hiveKerberos.urlParams;
|
||||
config.agent_java_options = hiveKerberos.agentJavaOptions;
|
||||
} else if (config.db_type === "dameng") {
|
||||
try {
|
||||
config.agent_java_options = parseDamengJvmSystemProperties(damengJvmOptions.value);
|
||||
} catch (error) {
|
||||
if (error instanceof DamengJvmSystemPropertyError) {
|
||||
throw new Error(t("connection.damengJvmOptionsInvalid", { line: error.lineNumber }));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} else if (!(config.db_type === "jdbc" && config.driver_profile === JDBCX_DRIVER_PROFILE)) {
|
||||
config.agent_java_options = undefined;
|
||||
}
|
||||
|
|
@ -6080,6 +6098,20 @@ function openExternalUrl(url: string) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="form.db_type === 'dameng'" class="grid grid-cols-4 items-start gap-4">
|
||||
<Label :class="connectionLabelTopClass">{{ t("connection.damengJvmOptions") }}</Label>
|
||||
<div class="col-span-3 space-y-1.5">
|
||||
<textarea
|
||||
v-model="damengJvmOptions"
|
||||
class="min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
:placeholder="t('connection.damengJvmOptionsPlaceholder')"
|
||||
/>
|
||||
<p class="text-xs leading-5 text-muted-foreground">
|
||||
{{ t("connection.damengJvmOptionsHint") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="isPrestoSqlConnection">
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
<Label :class="connectionLabelTopClass">{{ t("connection.jdbcDriverPaths") }}</Label>
|
||||
|
|
|
|||
|
|
@ -263,6 +263,10 @@ export default {
|
|||
damengSslProtocol: "SSL protocol",
|
||||
damengSslProtocolPlaceholder: "Optional, e.g. TLSv1.2",
|
||||
damengSslVerificationHint: "Certificate verification is controlled by the DM8 server ENABLE_ENCRYPT setting. Mode 5 verifies the server certificate; mode 4 encrypts without certificate verification.",
|
||||
damengJvmOptions: "JVM system properties",
|
||||
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
|
||||
damengJvmOptionsHint: "One -Dkey or -Dkey=value per line. Values with spaces do not need shell quotes.",
|
||||
damengJvmOptionsInvalid: "Line {line} must be a -Dkey or -Dkey=value system property without shell quotes.",
|
||||
redisTlsInsecure: "Skip certificate verification",
|
||||
redisTlsInsecureHint: "Equivalent to redis-cli --tls --insecure, for self-signed certificates or private CAs.",
|
||||
mysqlTlsMode: "TLS Mode",
|
||||
|
|
|
|||
|
|
@ -647,6 +647,10 @@ export default withEnglishFallback({
|
|||
sshHostKeyNoticeRejected: "Ha rechazado la verificación de la clave del host {host}:{port}. La conexión se ha abortado, no se enviaron credenciales.",
|
||||
sshHostKeyNoticeGeneric: "Falló la verificación de la clave del host SSH {host}:{port}.",
|
||||
sshHostKeyNoticeLearnFailed: "No se pudo guardar la clave de host de {host}:{port} — este host solo es de confianza en esta sesión, la próxima conexión requerirá confirmación nuevamente.",
|
||||
damengJvmOptions: "Propiedades del sistema JVM",
|
||||
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
|
||||
damengJvmOptionsHint: "Una propiedad -Dkey o -Dkey=value por línea. Los valores con espacios no necesitan comillas del shell.",
|
||||
damengJvmOptionsInvalid: "La línea {line} debe ser una propiedad -Dkey o -Dkey=value sin comillas del shell.",
|
||||
},
|
||||
editor: {
|
||||
pressToExecute: "Presiona {mod}+Enter para ejecutar",
|
||||
|
|
|
|||
|
|
@ -645,6 +645,10 @@ export default withEnglishFallback({
|
|||
sshHostKeyNoticeRejected: "Hai rifiutato la verifica della chiave host di {host}:{port}, la connessione è stata interrotta e le credenziali non sono state inviate.",
|
||||
sshHostKeyNoticeGeneric: "Verifica della chiave host SSH {host}:{port} fallita.",
|
||||
sshHostKeyNoticeLearnFailed: "Impossibile salvare la chiave host di {host}:{port} — Questo host è considerato affidabile solo per questa sessione, alla prossima connessione sarà necessaria una nuova conferma.",
|
||||
damengJvmOptions: "Proprietà di sistema JVM",
|
||||
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
|
||||
damengJvmOptionsHint: "Una proprietà -Dkey o -Dkey=value per riga. I valori con spazi non richiedono virgolette della shell.",
|
||||
damengJvmOptionsInvalid: "La riga {line} deve essere una proprietà -Dkey o -Dkey=value senza virgolette della shell.",
|
||||
},
|
||||
editor: {
|
||||
pressToExecute: "Premi {mod}+Enter per eseguire",
|
||||
|
|
|
|||
|
|
@ -645,6 +645,10 @@ export default withEnglishFallback({
|
|||
sshHostKeyNoticeRejected: "ホスト {host}:{port} のホストキー検証を拒否しました。接続は中止され、資格情報は送信されませんでした。",
|
||||
sshHostKeyNoticeGeneric: "SSHホスト {host}:{port} のキー検証に失敗しました。",
|
||||
sshHostKeyNoticeLearnFailed: "{host}:{port} のホストキーを保存できません — このホストは現在のセッションでのみ信頼され、次回の接続時に再確認が必要です。",
|
||||
damengJvmOptions: "JVM システムプロパティ",
|
||||
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
|
||||
damengJvmOptionsHint: "1 行に 1 つの -Dkey または -Dkey=value を入力します。空白を含む値にもシェル引用符は不要です。",
|
||||
damengJvmOptionsInvalid: "{line} 行目は、シェル引用符を使わない -Dkey または -Dkey=value 形式で入力してください。",
|
||||
},
|
||||
editor: {
|
||||
pressToExecute: "{mod}+Enter で実行",
|
||||
|
|
|
|||
|
|
@ -646,6 +646,10 @@ export default withEnglishFallback({
|
|||
sshHostKeyNoticeRejected: "Você rejeitou a verificação da chave do host {host}:{port}. A conexão foi abortada e as credenciais não foram enviadas.",
|
||||
sshHostKeyNoticeGeneric: "Falha na verificação da chave do host SSH {host}:{port}.",
|
||||
sshHostKeyNoticeLearnFailed: "Não foi possível salvar a chave do host {host}:{port} — o host é confiável apenas nesta sessão, necessitando reconfirmação na próxima conexão.",
|
||||
damengJvmOptions: "Propriedades do sistema da JVM",
|
||||
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
|
||||
damengJvmOptionsHint: "Uma propriedade -Dkey ou -Dkey=value por linha. Valores com espaços não precisam de aspas do shell.",
|
||||
damengJvmOptionsInvalid: "A linha {line} deve ser uma propriedade -Dkey ou -Dkey=value sem aspas do shell.",
|
||||
},
|
||||
editor: {
|
||||
pressToExecute: "Pressione {mod}+Enter para executar",
|
||||
|
|
|
|||
|
|
@ -265,6 +265,10 @@ export default withEnglishFallback({
|
|||
damengSslProtocol: "SSL 协议",
|
||||
damengSslProtocolPlaceholder: "可选,例如 TLSv1.2",
|
||||
damengSslVerificationHint: "证书验证方式由 DM8 服务端 ENABLE_ENCRYPT 决定:模式 5 验证服务端证书,模式 4 仅加密而不验证证书。",
|
||||
damengJvmOptions: "JVM 系统属性",
|
||||
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
|
||||
damengJvmOptionsHint: "每行一个 -Dkey 或 -Dkey=value;值包含空格时也不要使用 shell 引号。",
|
||||
damengJvmOptionsInvalid: "第 {line} 行必须是未使用 shell 引号的 -Dkey 或 -Dkey=value 系统属性。",
|
||||
redisTlsInsecure: "跳过证书验证",
|
||||
redisTlsInsecureHint: "等价于 redis-cli --tls --insecure,适用于自签名证书或私有 CA。",
|
||||
mysqlTlsMode: "TLS 模式",
|
||||
|
|
|
|||
|
|
@ -645,6 +645,10 @@ export default withEnglishFallback({
|
|||
sshHostKeyNoticeRejected: "您已拒絕 {host}:{port} 的主機金鑰驗證,連線已中止,憑證未發送。",
|
||||
sshHostKeyNoticeGeneric: "SSH 主機 {host}:{port} 的金鑰驗證失敗。",
|
||||
sshHostKeyNoticeLearnFailed: "無法儲存 {host}:{port} 的主機金鑰 — 該主機僅在本次會話內受信任,下次連線仍需重新確認。",
|
||||
damengJvmOptions: "JVM 系統屬性",
|
||||
damengJvmOptionsPlaceholder: "-Djava.net.preferIPv4Stack=true",
|
||||
damengJvmOptionsHint: "每行一個 -Dkey 或 -Dkey=value;值包含空格時也不要使用 shell 引號。",
|
||||
damengJvmOptionsInvalid: "第 {line} 行必須是未使用 shell 引號的 -Dkey 或 -Dkey=value 系統屬性。",
|
||||
},
|
||||
editor: {
|
||||
pressToExecute: "按 {mod}+Enter 執行查詢",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
const JAVA_SYSTEM_PROPERTY_PREFIX = "-D";
|
||||
const SHELL_QUOTE_PATTERN = /["']/;
|
||||
|
||||
export class DamengJvmSystemPropertyError extends Error {
|
||||
constructor(readonly lineNumber: number) {
|
||||
super(`Invalid Dameng JVM system property on line ${lineNumber}`);
|
||||
this.name = "DamengJvmSystemPropertyError";
|
||||
}
|
||||
}
|
||||
|
||||
export function damengJvmSystemPropertiesText(options?: string[]): string {
|
||||
return (options ?? [])
|
||||
.map((option) => option.trim())
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
export function parseDamengJvmSystemProperties(value: string): string[] {
|
||||
const options: string[] = [];
|
||||
|
||||
for (const [index, line] of value.split(/\r?\n/).entries()) {
|
||||
const option = line.trim();
|
||||
if (!option) continue;
|
||||
if (!isJavaSystemProperty(option)) {
|
||||
throw new DamengJvmSystemPropertyError(index + 1);
|
||||
}
|
||||
options.push(option);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function isJavaSystemProperty(option: string): boolean {
|
||||
if (!option.startsWith(JAVA_SYSTEM_PROPERTY_PREFIX) || SHELL_QUOTE_PATTERN.test(option)) return false;
|
||||
const property = option.slice(JAVA_SYSTEM_PROPERTY_PREFIX.length);
|
||||
const separator = property.indexOf("=");
|
||||
const key = separator >= 0 ? property.slice(0, separator) : property;
|
||||
return key.length > 0 && !/\s/.test(key);
|
||||
}
|
||||
|
|
@ -7,7 +7,9 @@ use std::sync::{Arc, Mutex as StdMutex};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::db::agent_driver::{AgentDriverClient, AgentLaunchSpec, AgentMethod, AgentRuntimeClient};
|
||||
use crate::db::agent_driver::{
|
||||
validate_dameng_java_system_properties, AgentDriverClient, AgentLaunchSpec, AgentMethod, AgentRuntimeClient,
|
||||
};
|
||||
use crate::models::connection::DatabaseType;
|
||||
|
||||
pub const DEFAULT_JRE_KEY: &str = "21";
|
||||
|
|
@ -668,6 +670,9 @@ impl AgentManager {
|
|||
jre_key: &str,
|
||||
extra_java_args: &[String],
|
||||
) -> Result<AgentLaunchSpec, String> {
|
||||
if driver_key == "dameng" {
|
||||
validate_dameng_java_system_properties(extra_java_args)?;
|
||||
}
|
||||
let driver_dir = self.driver_dir(driver_key);
|
||||
let config_path = self.driver_launch_config_path(driver_key);
|
||||
if config_path.exists() {
|
||||
|
|
|
|||
|
|
@ -1728,6 +1728,27 @@ fn agent_java_args_with_extra_args(jar_path: &str, extra_java_args: &[String]) -
|
|||
agent_java_args_with_extra_opts(jar_path, std::env::var(AGENT_JAVA_OPTS_ENV).ok().as_deref(), extra_java_args)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_dameng_java_system_properties(options: &[String]) -> Result<(), String> {
|
||||
for (index, option) in options.iter().enumerate() {
|
||||
let option = option.trim();
|
||||
let Some(property) = option.strip_prefix("-D") else {
|
||||
return Err(java_system_property_error(index));
|
||||
};
|
||||
let key = property.split_once('=').map_or(property, |(key, _)| key);
|
||||
if key.is_empty() || key.chars().any(char::is_whitespace) || option.contains('"') || option.contains('\'') {
|
||||
return Err(java_system_property_error(index));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn java_system_property_error(index: usize) -> String {
|
||||
format!(
|
||||
"Dameng JVM option #{} must be a Java system property (-Dkey or -Dkey=value) without shell quotes",
|
||||
index + 1
|
||||
)
|
||||
}
|
||||
|
||||
fn agent_java_args_with_extra_opts(
|
||||
jar_path: &str,
|
||||
extra_opts: Option<&str>,
|
||||
|
|
@ -1983,9 +2004,10 @@ mod tests {
|
|||
agent_schema_table_params, agent_supports_capability, agent_transaction_params, format_agent_process_error,
|
||||
format_agent_startup_error, is_agent_rpc_response_error, is_unsupported_handshake_error,
|
||||
mongo_collection_params, mongo_database_params, mongo_document_id_params, parse_agent_java_opts,
|
||||
read_agent_line, start_stderr_collector, AgentCapability, AgentDriverClient, AgentHandshake, AgentKvMethod,
|
||||
AgentLaunchSpec, AgentMethod, AgentRuntimeClient, AgentTableReadCloseParams, AgentTableReadPageParams,
|
||||
AgentTableReadStartParams, MongoAgentMethod, StderrTail, AGENT_PROTOCOL_VERSION,
|
||||
read_agent_line, start_stderr_collector, validate_dameng_java_system_properties, AgentCapability,
|
||||
AgentDriverClient, AgentHandshake, AgentKvMethod, AgentLaunchSpec, AgentMethod, AgentRuntimeClient,
|
||||
AgentTableReadCloseParams, AgentTableReadPageParams, AgentTableReadStartParams, MongoAgentMethod, StderrTail,
|
||||
AGENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use std::io::Cursor;
|
||||
use std::io::Write;
|
||||
|
|
@ -2072,6 +2094,30 @@ mod tests {
|
|||
assert!(connection < jar);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_java_system_properties_allow_flags_and_values_with_spaces() {
|
||||
validate_dameng_java_system_properties(&[
|
||||
"-Djava.net.preferIPv4Stack".to_string(),
|
||||
"-Ddm.config.path=C:\\Program Files\\DM\\dm.ini".to_string(),
|
||||
])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_java_system_properties_reject_launcher_options_and_empty_keys() {
|
||||
for option in ["-jar", "-javaagent:agent.jar", "-agentpath:agent.dll", "-Xmx1g", "", "-D", "-D=value"] {
|
||||
assert!(validate_dameng_java_system_properties(&[option.to_string()]).is_err(), "{option}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_java_system_properties_reject_shell_quotes() {
|
||||
assert!(validate_dameng_java_system_properties(&[
|
||||
r#"-Ddm.config.path="C:\Program Files\DM\dm.ini""#.to_string(),
|
||||
])
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_java_opts_parser_preserves_quoted_windows_paths() {
|
||||
let args = parse_agent_java_opts(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "vitest";
|
||||
import { DamengJvmSystemPropertyError, damengJvmSystemPropertiesText, parseDamengJvmSystemProperties } from "../../apps/desktop/src/lib/database/damengJvmOptions.ts";
|
||||
|
||||
test("normalizes Dameng JVM system properties and preserves spaces in values", () => {
|
||||
assert.deepEqual(parseDamengJvmSystemProperties("\n -Djava.net.preferIPv4Stack \r\n-Ddm.config.path=C:\\Program Files\\DM\\dm.ini\n"), ["-Djava.net.preferIPv4Stack", "-Ddm.config.path=C:\\Program Files\\DM\\dm.ini"]);
|
||||
});
|
||||
|
||||
test("rejects JVM launcher options and empty property keys", () => {
|
||||
for (const option of ["-jar", "-javaagent:agent.jar", "-agentpath:agent.dll", "-Xmx1g", "-D", "-D=value"]) {
|
||||
assert.throws(() => parseDamengJvmSystemProperties(option), DamengJvmSystemPropertyError);
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects shell quotes instead of preserving them in the property value", () => {
|
||||
assert.throws(() => parseDamengJvmSystemProperties('-Ddm.config.path="C:\\Program Files\\DM\\dm.ini"'), DamengJvmSystemPropertyError);
|
||||
});
|
||||
|
||||
test("hydrates missing legacy options as an empty form value", () => {
|
||||
assert.equal(damengJvmSystemPropertiesText(undefined), "");
|
||||
assert.equal(damengJvmSystemPropertiesText(["", " -Dkey=value "]), "-Dkey=value");
|
||||
});
|
||||
Loading…
Reference in New Issue