d
This commit is contained in:
@@ -39,6 +39,13 @@ CREATE TABLE IF NOT EXISTS ops_managed_nodes (
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ops_managed_node_secrets (
|
||||
node_code VARCHAR(64) PRIMARY KEY REFERENCES ops_managed_nodes(node_code) ON DELETE CASCADE,
|
||||
ssh_password TEXT NOT NULL DEFAULT '',
|
||||
ssh_private_key TEXT NOT NULL DEFAULT '',
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ops_jobs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
job_code VARCHAR(64) NOT NULL UNIQUE,
|
||||
@@ -321,6 +328,107 @@ def _serialize_node_row(row: tuple) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _load_node_secret_flags(node_codes: list[str]) -> dict[str, dict]:
|
||||
normalized_codes = [str(item or "").strip() for item in node_codes if str(item or "").strip()]
|
||||
if not normalized_codes:
|
||||
return {}
|
||||
result: dict[str, dict] = {}
|
||||
with get_db() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT node_code, ssh_password, ssh_private_key
|
||||
FROM ops_managed_node_secrets
|
||||
WHERE node_code = ANY(%s)
|
||||
""",
|
||||
(normalized_codes,),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
for row in rows:
|
||||
node_code = str(row[0] or "").strip()
|
||||
result[node_code] = {
|
||||
"ssh_password_configured": bool(str(row[1] or "").strip()),
|
||||
"ssh_private_key_configured": bool(str(row[2] or "").strip()),
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
def _parse_ssh_entry(raw_value: object) -> dict:
|
||||
raw = str(raw_value or "").strip()
|
||||
if not raw:
|
||||
return {}
|
||||
parts = raw.split(maxsplit=2)
|
||||
if len(parts) < 2:
|
||||
return {}
|
||||
host_port = str(parts[0] or "").strip()
|
||||
ssh_user = str(parts[1] or "").strip()
|
||||
secret = str(parts[2] or "").strip() if len(parts) >= 3 else ""
|
||||
ssh_host = host_port
|
||||
ssh_port = 22
|
||||
if ":" in host_port:
|
||||
host_candidate, port_candidate = host_port.rsplit(":", 1)
|
||||
if host_candidate and port_candidate.isdigit():
|
||||
ssh_host = host_candidate
|
||||
ssh_port = max(int(port_candidate), 1)
|
||||
if secret.startswith("<") and secret.endswith(">") and len(secret) >= 2:
|
||||
secret = secret[1:-1].strip()
|
||||
payload = {
|
||||
"ssh_host": ssh_host,
|
||||
"ssh_port": ssh_port,
|
||||
"ssh_user": ssh_user,
|
||||
}
|
||||
if secret:
|
||||
payload["auth_mode"] = "password"
|
||||
payload["ssh_password"] = secret
|
||||
return payload
|
||||
|
||||
|
||||
def _upsert_managed_node_secret(
|
||||
*,
|
||||
node_code: str,
|
||||
ssh_password: str = "",
|
||||
ssh_private_key: str = "",
|
||||
clear_ssh_password: bool = False,
|
||||
clear_ssh_private_key: bool = False,
|
||||
) -> None:
|
||||
normalized_node_code = str(node_code or "").strip()
|
||||
if not normalized_node_code:
|
||||
return
|
||||
with get_db() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO ops_managed_node_secrets (
|
||||
node_code, ssh_password, ssh_private_key, updated_at
|
||||
) VALUES (%s, %s, %s, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (node_code) DO UPDATE SET
|
||||
ssh_password = CASE
|
||||
WHEN %s THEN ''
|
||||
WHEN %s <> '' THEN %s
|
||||
ELSE ops_managed_node_secrets.ssh_password
|
||||
END,
|
||||
ssh_private_key = CASE
|
||||
WHEN %s THEN ''
|
||||
WHEN %s <> '' THEN %s
|
||||
ELSE ops_managed_node_secrets.ssh_private_key
|
||||
END,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
""",
|
||||
(
|
||||
normalized_node_code,
|
||||
"" if clear_ssh_password else ssh_password,
|
||||
"" if clear_ssh_private_key else ssh_private_key,
|
||||
clear_ssh_password,
|
||||
ssh_password,
|
||||
ssh_password,
|
||||
clear_ssh_private_key,
|
||||
ssh_private_key,
|
||||
ssh_private_key,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _pick_text_value(payload: dict, key: str, fallback: str = "", *, default: str = "") -> str:
|
||||
if key in payload:
|
||||
normalized = str(payload.get(key) or "").strip()
|
||||
@@ -397,6 +505,15 @@ def upsert_managed_node(payload: dict) -> tuple[bool, str, dict]:
|
||||
node_code = str(payload.get("node_code") or "").strip()
|
||||
if not node_code:
|
||||
return False, "node_code 不能为空", {}
|
||||
parsed_ssh_entry = _parse_ssh_entry(payload.get("ssh_entry"))
|
||||
merged_payload = {
|
||||
**dict(payload or {}),
|
||||
**{key: value for key, value in parsed_ssh_entry.items() if value not in ("", None)},
|
||||
}
|
||||
ssh_password = str(merged_payload.get("ssh_password") or "").strip()
|
||||
ssh_private_key = str(merged_payload.get("ssh_private_key") or "")
|
||||
clear_ssh_password = bool(merged_payload.get("clear_ssh_password", False))
|
||||
clear_ssh_private_key = bool(merged_payload.get("clear_ssh_private_key", False))
|
||||
|
||||
with get_db() as conn:
|
||||
with conn.cursor() as cur:
|
||||
@@ -413,22 +530,22 @@ def upsert_managed_node(payload: dict) -> tuple[bool, str, dict]:
|
||||
existing_row = cur.fetchone()
|
||||
existing_node = _serialize_node_row(existing_row) if existing_row else {}
|
||||
existing_metadata = dict(existing_node.get("metadata") or {})
|
||||
incoming_metadata = dict(payload.get("metadata") or {})
|
||||
incoming_metadata = dict(merged_payload.get("metadata") or {})
|
||||
|
||||
region = _pick_text_value(payload, "region", str(existing_node.get("region") or ""), default="unknown") or "unknown"
|
||||
role = _pick_text_value(payload, "role", str(existing_node.get("role") or ""), default="worker") or "worker"
|
||||
title = _pick_text_value(payload, "title", str(existing_node.get("title") or ""), default=node_code) or node_code
|
||||
ssh_host = _pick_text_value(payload, "ssh_host", str(existing_node.get("ssh_host") or ""))
|
||||
ssh_port = _pick_int_value(payload, "ssh_port", int(existing_node.get("ssh_port") or 22), default=22, minimum=1)
|
||||
ssh_user = _pick_text_value(payload, "ssh_user", str(existing_node.get("ssh_user") or ""))
|
||||
auth_mode = _pick_text_value(payload, "auth_mode", str(existing_node.get("auth_mode") or ""), default="key") or "key"
|
||||
region = _pick_text_value(merged_payload, "region", str(existing_node.get("region") or ""), default="unknown") or "unknown"
|
||||
role = _pick_text_value(merged_payload, "role", str(existing_node.get("role") or ""), default="worker") or "worker"
|
||||
title = _pick_text_value(merged_payload, "title", str(existing_node.get("title") or ""), default=node_code) or node_code
|
||||
ssh_host = _pick_text_value(merged_payload, "ssh_host", str(existing_node.get("ssh_host") or ""))
|
||||
ssh_port = _pick_int_value(merged_payload, "ssh_port", int(existing_node.get("ssh_port") or 22), default=22, minimum=1)
|
||||
ssh_user = _pick_text_value(merged_payload, "ssh_user", str(existing_node.get("ssh_user") or ""))
|
||||
auth_mode = _pick_text_value(merged_payload, "auth_mode", str(existing_node.get("auth_mode") or ""), default="key") or "key"
|
||||
deploy_channel = _pick_text_value(
|
||||
payload,
|
||||
merged_payload,
|
||||
"deploy_channel",
|
||||
str(existing_node.get("deploy_channel") or ""),
|
||||
default="stable",
|
||||
) or "stable"
|
||||
is_enabled = bool(payload["is_enabled"]) if "is_enabled" in payload else bool(existing_node.get("is_enabled", True))
|
||||
is_enabled = bool(merged_payload["is_enabled"]) if "is_enabled" in merged_payload else bool(existing_node.get("is_enabled", True))
|
||||
metadata = {
|
||||
**existing_metadata,
|
||||
**incoming_metadata,
|
||||
@@ -469,7 +586,16 @@ def upsert_managed_node(payload: dict) -> tuple[bool, str, dict]:
|
||||
)
|
||||
row = cur.fetchone()
|
||||
conn.commit()
|
||||
return True, "托管节点已保存", {"node": _serialize_node_row(row)}
|
||||
_upsert_managed_node_secret(
|
||||
node_code=node_code,
|
||||
ssh_password=ssh_password,
|
||||
ssh_private_key=ssh_private_key,
|
||||
clear_ssh_password=clear_ssh_password,
|
||||
clear_ssh_private_key=clear_ssh_private_key,
|
||||
)
|
||||
node = _serialize_node_row(row)
|
||||
node.update(_load_node_secret_flags([node_code]).get(node_code, {}))
|
||||
return True, "托管节点已保存", {"node": node}
|
||||
|
||||
|
||||
def list_managed_nodes() -> list[dict]:
|
||||
@@ -485,7 +611,11 @@ def list_managed_nodes() -> list[dict]:
|
||||
"""
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
return [_serialize_node_row(row) for row in rows]
|
||||
items = [_serialize_node_row(row) for row in rows]
|
||||
secret_flags = _load_node_secret_flags([str(item.get("node_code") or "") for item in items])
|
||||
for item in items:
|
||||
item.update(secret_flags.get(str(item.get("node_code") or "").strip(), {}))
|
||||
return items
|
||||
|
||||
|
||||
def sync_managed_nodes_from_cluster(*, dry_run: bool = False) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user