package services import ( "context" "fmt" "strings" "gopkg.in/yaml.v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "time" ) type LiveLokiConfig struct { Location string `json:"location"` YAML string `json:"yaml"` } // GetLiveConfig 读取实际运行的 Loki 配置。仅返回 loki.yaml,不返回整个 ConfigMap 元数据。 func (s *DeploymentService) GetLiveConfig(ctx context.Context, id uint) (*LiveLokiConfig, error) { d, err := s.Get(id) if err != nil { return nil, err } var raw, location string switch d.Mode { case "k8s-yaml": if d.Cluster == nil { return nil, fmt.Errorf("部署未关联 Kubernetes 集群") } client, err := clusterClient(d.Cluster, 30*time.Second) if err != nil { return nil, err } ns := d.Namespace if ns == "" { ns = "log" } configMap, err := client.CoreV1().ConfigMaps(ns).Get(ctx, "loki-config", metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("无法读取 %s/loki-config: %w", ns, err) } raw = configMap.Data["loki.yaml"] location = fmt.Sprintf("ConfigMap %s/loki-config → loki.yaml", ns) case "host-binary": if d.Host == nil { return nil, fmt.Errorf("部署未关联主机") } client, err := NewSSHClientWithAuth(HostSSHAuth(d.Host)) if err != nil { return nil, fmt.Errorf("连接部署主机失败") } defer client.Close() output, _, err := client.Execute(ctx, "cat /etc/loki/loki.yaml") if err != nil { return nil, fmt.Errorf("无法读取主机上的 /etc/loki/loki.yaml") } raw = output location = "/etc/loki/loki.yaml" case "host-docker": if d.Host == nil { return nil, fmt.Errorf("部署未关联主机") } client, err := NewSSHClientWithAuth(HostSSHAuth(d.Host)) if err != nil { return nil, fmt.Errorf("连接部署主机失败") } defer client.Close() location = dockerConfigPath(d.ID) output, _, err := client.Execute(ctx, "cat "+location) if err != nil { return nil, fmt.Errorf("无法读取主机上的 Loki 配置") } raw = output default: return nil, fmt.Errorf("当前部署模式暂不支持查看运行配置") } if strings.TrimSpace(raw) == "" { return nil, fmt.Errorf("运行配置为空或尚未部署") } redacted, err := redactLokiYAML(raw) if err != nil { return nil, fmt.Errorf("运行配置无法安全展示:YAML 格式无效") } return &LiveLokiConfig{Location: location, YAML: redacted}, nil } func redactLokiYAML(source string) (string, error) { var document yaml.Node if err := yaml.Unmarshal([]byte(source), &document); err != nil { return "", err } redactYAMLNode(&document) output, err := yaml.Marshal(&document) return string(output), err } func redactYAMLNode(node *yaml.Node) { if node.Kind == yaml.ScalarNode && strings.HasPrefix(node.Value, "ENC:") { node.Value = "********" return } if node.Kind == yaml.MappingNode { for i := 0; i+1 < len(node.Content); i += 2 { key, value := node.Content[i], node.Content[i+1] switch strings.ToLower(key.Value) { case "access_key_id", "secret_access_key", "account_key", "password", "token", "client_secret": value.Kind = yaml.ScalarNode value.Tag = "!!str" value.Value = "********" value.Content = nil continue } redactYAMLNode(value) } return } for _, child := range node.Content { redactYAMLNode(child) } }