Add the Loki query UI, access policies, ingress insights, alert and registry settings, and their backend APIs. Include the related Kubernetes deployment and collector updates required by the integrated application. Validated with go test ./..., npm run build, and npm run lint.
121 lines
3.3 KiB
Go
121 lines
3.3 KiB
Go
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)
|
|
}
|
|
}
|