// pod_execute.go - 在目标 Pod 容器内执行 shell 命令 // Logan.Li package tools import ( "context" "fmt" "strings" "time" k8sclient "ezops/pkg/kubernetesclient" ) // PodExecuteTool 在 Pod 容器内执行命令(K8s exec WebSocket 协议) // Logan.Li type PodExecuteTool struct { client *k8sclient.Client namespace string podName string container string timeout time.Duration } // NewPodExecuteTool 创建 Pod 执行工具 // Logan.Li func NewPodExecuteTool(client *k8sclient.Client, namespace, podName, container string, timeout time.Duration) *PodExecuteTool { if timeout <= 0 { timeout = 60 * time.Second } return &PodExecuteTool{ client: client, namespace: namespace, podName: podName, container: container, timeout: timeout, } } // Name 工具名 // Logan.Li func (t *PodExecuteTool) Name() string { return "pod_execute" } // Description 给 LLM 的工具说明 // Logan.Li func (t *PodExecuteTool) Description() string { return "在已选定的 Kubernetes Pod 容器内执行 shell 命令并返回 stdout+stderr。" + "用于容器内排障(查看进程/配置/连通性)、应用调试。" + "注意:容器内可能没有 systemctl 或宿主机路径;多容器 Pod 可通过 container 参数指定。" + "高危命令需用户确认。" } // Parameters JSON Schema // Logan.Li func (t *PodExecuteTool) Parameters() map[string]interface{} { return map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "command": map[string]interface{}{ "type": "string", "description": "要执行的 shell 命令,例如 ps aux、cat /etc/nginx/nginx.conf、curl -s localhost:8080/health", }, "container": map[string]interface{}{ "type": "string", "description": "可选,多容器 Pod 时指定容器名;默认使用会话绑定的容器", }, "timeout": map[string]interface{}{ "type": "integer", "description": "命令超时秒数(默认 60)", }, }, "required": []string{"command"}, } } // RiskLevel 静态风险 // Logan.Li func (t *PodExecuteTool) RiskLevel() RiskLevel { return RiskHigh } // RiskLevelForArgs 依据命令动态评估风险 // Logan.Li func (t *PodExecuteTool) RiskLevelForArgs(args map[string]interface{}) RiskLevel { return AssessBashRisk(StringParam(args, "command")) } // Execute 在 Pod 内执行命令 // Logan.Li func (t *PodExecuteTool) Execute(ctx context.Context, args map[string]interface{}) (*Result, error) { command := strings.TrimSpace(StringParam(args, "command")) if command == "" { return &Result{Success: false, Error: "参数 command 不能为空"}, nil } if t.podName == "" { return &Result{Success: false, Error: "未绑定目标 Pod,请在前端选择 Pod 或在命令前先 kubernetes_get 确认"}, nil } container := strings.TrimSpace(StringParam(args, "container")) if container == "" { container = t.container } timeout := t.timeout if sec := IntParam(args, "timeout"); sec > 0 { timeout = time.Duration(sec) * time.Second } execCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() // 用 sh -c 保持 shell 语义,并嵌入 exit_code 与 ssh_execute 对齐 // Logan.Li wrapped := command + "; echo [exit_code=$?]" done := make(chan struct { res *k8sclient.ExecResult err error }, 1) go func() { r, err := t.client.ExecInPod(t.namespace, t.podName, container, []string{"sh", "-c", wrapped}) done <- struct { res *k8sclient.ExecResult err error }{r, err} }() var result *k8sclient.ExecResult var err error select { case <-execCtx.Done(): return &Result{Success: false, Error: "命令执行超时"}, nil case out := <-done: result, err = out.res, out.err } if err != nil { return &Result{Success: false, Error: fmt.Sprintf("Pod exec 失败: %v", err)}, nil } output := strings.TrimSpace(result.Stdout) if result.Stderr != "" { if output != "" { output += "\n" } output += strings.TrimSpace(result.Stderr) } success := strings.Contains(output, "[exit_code=0]") if !success && !strings.Contains(output, "[exit_code=") { // 无 exit_code 标记时,无 stderr 视为成功 // Logan.Li success = strings.TrimSpace(result.Stderr) == "" } return &Result{Success: success, Output: output}, nil }