Files
managed-portal/internal/managed/registry.go
2026-04-27 10:04:36 +08:00

135 lines
3.7 KiB
Go

package managed
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
var ErrServiceNotFound = errors.New("managed service not found")
type Registry struct {
Services []Service `yaml:"services"`
}
type Service struct {
ID string `yaml:"id" json:"id"`
DisplayName string `yaml:"display_name" json:"display_name"`
ProjectType string `yaml:"project_type" json:"project_type"`
ProjectRoot string `yaml:"project_root" json:"project_root"`
ContainerName string `yaml:"container_name" json:"container_name"`
APIBaseURL string `yaml:"api_base_url" json:"api_base_url"`
ServiceName string `yaml:"service_name" json:"service_name"`
ConfigPath string `yaml:"config_path" json:"config_path"`
RTSPField string `yaml:"rtsp_field" json:"rtsp_field"`
ResultType string `yaml:"result_type" json:"result_type"`
ResultPaths map[string]string `yaml:"result_paths" json:"result_paths"`
}
func EmptyRegistry() *Registry {
return &Registry{Services: []Service{}}
}
func LoadRegistry(path string) (*Registry, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read managed registry: %w", err)
}
var registry Registry
if err := yaml.Unmarshal(data, &registry); err != nil {
return nil, fmt.Errorf("parse managed registry: %w", err)
}
baseDir := filepath.Dir(path)
ids := make(map[string]struct{}, len(registry.Services))
for i := range registry.Services {
svc := &registry.Services[i]
if err := normalizeService(baseDir, svc); err != nil {
return nil, fmt.Errorf("service[%d]: %w", i, err)
}
if _, exists := ids[svc.ID]; exists {
return nil, fmt.Errorf("duplicate service id %q", svc.ID)
}
ids[svc.ID] = struct{}{}
}
if registry.Services == nil {
registry.Services = []Service{}
}
return &registry, nil
}
func (r *Registry) Get(id string) (Service, bool) {
for _, svc := range r.Services {
if svc.ID == id {
return svc, true
}
}
return Service{}, false
}
func normalizeService(baseDir string, svc *Service) error {
svc.ID = strings.TrimSpace(svc.ID)
svc.DisplayName = strings.TrimSpace(svc.DisplayName)
svc.ProjectType = strings.TrimSpace(svc.ProjectType)
svc.ContainerName = strings.TrimSpace(svc.ContainerName)
svc.APIBaseURL = strings.TrimSpace(svc.APIBaseURL)
svc.ServiceName = strings.TrimSpace(svc.ServiceName)
svc.ConfigPath = strings.TrimSpace(svc.ConfigPath)
svc.RTSPField = strings.TrimSpace(svc.RTSPField)
svc.ResultType = strings.TrimSpace(svc.ResultType)
if svc.ID == "" {
return errors.New("id is required")
}
if svc.DisplayName == "" {
return errors.New("display_name is required")
}
if svc.ProjectType == "" {
return errors.New("project_type is required")
}
if svc.ContainerName == "" {
return errors.New("container_name is required")
}
if svc.APIBaseURL == "" {
return errors.New("api_base_url is required")
}
if svc.ResultType == "" {
return errors.New("result_type is required")
}
projectRoot := strings.TrimSpace(svc.ProjectRoot)
if projectRoot == "" {
return errors.New("project_root is required")
}
svc.ProjectRoot = resolvePath(baseDir, projectRoot)
if svc.ServiceName == "" {
svc.ServiceName = svc.ContainerName
}
if svc.ConfigPath != "" {
svc.ConfigPath = resolvePath(baseDir, svc.ConfigPath)
}
if svc.ResultPaths == nil {
svc.ResultPaths = map[string]string{}
}
for key, path := range svc.ResultPaths {
svc.ResultPaths[key] = resolvePath(baseDir, strings.TrimSpace(path))
}
return nil
}
func resolvePath(baseDir, path string) string {
if filepath.IsAbs(path) {
return filepath.Clean(path)
}
return filepath.Clean(filepath.Join(baseDir, path))
}