refactor(config): enhance provider conflict error messages, streamline provider loading, and improve validation for config files, rename provider.GetType() to Type()

This commit is contained in:
yusing 2025-04-05 13:30:24 +08:00
parent 73a5c57d67
commit b53dd17b84
4 changed files with 35 additions and 47 deletions

View file

@ -51,6 +51,8 @@ You may run "ls-config" to show or dump the current config.`
var Validate = config.Validate var Validate = config.Validate
var ErrProviderNameConflict = gperr.New("provider name conflict")
func newConfig() *Config { func newConfig() *Config {
return &Config{ return &Config{
value: config.DefaultConfig(), value: config.DefaultConfig(),
@ -263,56 +265,48 @@ func (cfg *Config) initAutoCert(autocertCfg *autocert.AutocertConfig) (err gperr
} }
func (cfg *Config) errIfExists(p *proxy.Provider) gperr.Error { func (cfg *Config) errIfExists(p *proxy.Provider) gperr.Error {
if _, ok := cfg.providers.Load(p.String()); ok { if conflict, ok := cfg.providers.Load(p.String()); ok {
return gperr.Errorf("provider %s already exists", p.String()) return ErrProviderNameConflict.
Subject(p.String()).
Withf("one is %q", conflict.Type()).
Withf("the other is %q", p.Type())
} }
return nil return nil
} }
func (cfg *Config) storeProvider(p *proxy.Provider) {
cfg.providers.Store(p.String(), p)
}
func (cfg *Config) loadRouteProviders(providers *config.Providers) gperr.Error { func (cfg *Config) loadRouteProviders(providers *config.Providers) gperr.Error {
errs := gperr.NewBuilder("route provider errors") errs := gperr.NewBuilder("route provider errors")
results := gperr.NewBuilder("loaded route providers") results := gperr.NewBuilder("loaded route providers")
removeAllAgents() removeAllAgents()
routeProviders := make([]*proxy.Provider, 0, len(providers.Agents)+len(providers.Docker)+len(providers.Files))
for _, agent := range providers.Agents { for _, agent := range providers.Agents {
if err := agent.Init(cfg.task.Context()); err != nil { if err := agent.Init(cfg.task.Context()); err != nil {
errs.Add(err.Subject(agent.String())) errs.Add(err.Subject(agent.String()))
continue continue
} }
addAgent(agent) addAgent(agent)
p := proxy.NewAgentProvider(agent) routeProviders = append(routeProviders, proxy.NewAgentProvider(agent))
if err := cfg.errIfExists(p); err != nil {
errs.Add(err.Subject(p.String()))
continue
}
cfg.storeProvider(p)
} }
for _, filename := range providers.Files { for _, filename := range providers.Files {
p, err := proxy.NewFileProvider(filename) routeProviders = append(routeProviders, proxy.NewFileProvider(filename))
if err == nil {
err = cfg.errIfExists(p)
}
if err != nil {
errs.Add(gperr.PrependSubject(filename, err))
continue
}
cfg.storeProvider(p)
} }
for name, dockerHost := range providers.Docker { for name, dockerHost := range providers.Docker {
p := proxy.NewDockerProvider(name, dockerHost) routeProviders = append(routeProviders, proxy.NewDockerProvider(name, dockerHost))
}
if len(routeProviders) == 0 {
return nil
}
// check if all providers are unique (should not happen but just in case)
for _, p := range routeProviders {
if err := cfg.errIfExists(p); err != nil { if err := cfg.errIfExists(p); err != nil {
errs.Add(err.Subject(p.String())) errs.Add(err)
continue continue
} }
cfg.storeProvider(p) cfg.providers.Store(p.String(), p)
}
if cfg.providers.Size() == 0 {
return nil
} }
lenLongestName := 0 lenLongestName := 0

View file

@ -2,12 +2,15 @@ package types
import ( import (
"context" "context"
"os"
"path"
"regexp" "regexp"
"sync" "sync"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/yusing/go-proxy/agent/pkg/agent" "github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/internal/autocert" "github.com/yusing/go-proxy/internal/autocert"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/net/gphttp/accesslog" "github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
"github.com/yusing/go-proxy/internal/notif" "github.com/yusing/go-proxy/internal/notif"
@ -24,10 +27,10 @@ type (
TimeoutShutdown int `json:"timeout_shutdown" validate:"gte=0"` TimeoutShutdown int `json:"timeout_shutdown" validate:"gte=0"`
} }
Providers struct { Providers struct {
Files []string `json:"include" yaml:"include,omitempty" validate:"dive,filepath"` Files []string `json:"include" yaml:"include,omitempty" validate:"unique,dive,config_file_exists"`
Docker map[string]string `json:"docker" yaml:"docker,omitempty" validate:"non_empty_docker_keys,dive,unix_addr|url"` Docker map[string]string `json:"docker" yaml:"docker,omitempty" validate:"unique,dive,unix_addr|url"`
Agents []*agent.AgentConfig `json:"agents" yaml:"agents,omitempty"` Agents []*agent.AgentConfig `json:"agents" yaml:"agents,omitempty" validate:"unique=Addr"`
Notification []notif.NotificationConfig `json:"notification" yaml:"notification,omitempty"` Notification []notif.NotificationConfig `json:"notification" yaml:"notification,omitempty" validate:"unique=ProviderName"`
} }
Entrypoint struct { Entrypoint struct {
Middlewares []map[string]any `json:"middlewares"` Middlewares []map[string]any `json:"middlewares"`
@ -97,13 +100,9 @@ func init() {
} }
return true return true
}) })
utils.MustRegisterValidation("non_empty_docker_keys", func(fl validator.FieldLevel) bool { utils.MustRegisterValidation("config_file_exists", func(fl validator.FieldLevel) bool {
m := fl.Field().Interface().(map[string]string) filename := fl.Field().Interface().(string)
for k := range m { info, err := os.Stat(path.Join(common.ConfigBasePath, filename))
if k == "" { return err == nil && !info.IsDir()
return false
}
}
return true
}) })
} }

View file

@ -73,7 +73,7 @@ func (handler *EventHandler) matchAny(events []watcher.Event, route *route.Route
} }
func (handler *EventHandler) match(event watcher.Event, route *route.Route) bool { func (handler *EventHandler) match(event watcher.Event, route *route.Route) bool {
switch handler.provider.GetType() { switch handler.provider.Type() {
case types.ProviderTypeDocker, types.ProviderTypeAgent: case types.ProviderTypeDocker, types.ProviderTypeAgent:
return route.Container.ContainerID == event.ActorID || return route.Container.ContainerID == event.ActorID ||
route.Container.ContainerName == event.ActorName route.Container.ContainerName == event.ActorName

View file

@ -20,17 +20,12 @@ type FileProvider struct {
l zerolog.Logger l zerolog.Logger
} }
func FileProviderImpl(filename string) (ProviderImpl, error) { func FileProviderImpl(filename string) ProviderImpl {
impl := &FileProvider{ return &FileProvider{
fileName: filename, fileName: filename,
path: path.Join(common.ConfigBasePath, filename), path: path.Join(common.ConfigBasePath, filename),
l: logging.With().Str("type", "file").Str("name", filename).Logger(), l: logging.With().Str("type", "file").Str("name", filename).Logger(),
} }
_, err := os.Stat(impl.path)
if err != nil {
return nil, err
}
return impl, nil
} }
func validate(data []byte) (routes route.Routes, err gperr.Error) { func validate(data []byte) (routes route.Routes, err gperr.Error) {