diff --git a/internal/config/config.go b/internal/config/config.go index 5b612ec..bfab72e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -51,6 +51,8 @@ You may run "ls-config" to show or dump the current config.` var Validate = config.Validate +var ErrProviderNameConflict = gperr.New("provider name conflict") + func newConfig() *Config { return &Config{ 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 { - if _, ok := cfg.providers.Load(p.String()); ok { - return gperr.Errorf("provider %s already exists", p.String()) + if conflict, ok := cfg.providers.Load(p.String()); ok { + return ErrProviderNameConflict. + Subject(p.String()). + Withf("one is %q", conflict.Type()). + Withf("the other is %q", p.Type()) } return nil } -func (cfg *Config) storeProvider(p *proxy.Provider) { - cfg.providers.Store(p.String(), p) -} - func (cfg *Config) loadRouteProviders(providers *config.Providers) gperr.Error { errs := gperr.NewBuilder("route provider errors") results := gperr.NewBuilder("loaded route providers") removeAllAgents() + routeProviders := make([]*proxy.Provider, 0, len(providers.Agents)+len(providers.Docker)+len(providers.Files)) + for _, agent := range providers.Agents { if err := agent.Init(cfg.task.Context()); err != nil { errs.Add(err.Subject(agent.String())) continue } addAgent(agent) - p := proxy.NewAgentProvider(agent) - if err := cfg.errIfExists(p); err != nil { - errs.Add(err.Subject(p.String())) - continue - } - cfg.storeProvider(p) + routeProviders = append(routeProviders, proxy.NewAgentProvider(agent)) } for _, filename := range providers.Files { - p, err := proxy.NewFileProvider(filename) - if err == nil { - err = cfg.errIfExists(p) - } - if err != nil { - errs.Add(gperr.PrependSubject(filename, err)) - continue - } - cfg.storeProvider(p) + routeProviders = append(routeProviders, proxy.NewFileProvider(filename)) } 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 { - errs.Add(err.Subject(p.String())) + errs.Add(err) continue } - cfg.storeProvider(p) - } - if cfg.providers.Size() == 0 { - return nil + cfg.providers.Store(p.String(), p) } lenLongestName := 0 diff --git a/internal/config/types/config.go b/internal/config/types/config.go index 3db2ceb..bad04b2 100644 --- a/internal/config/types/config.go +++ b/internal/config/types/config.go @@ -2,12 +2,15 @@ package types import ( "context" + "os" + "path" "regexp" "sync" "github.com/go-playground/validator/v10" "github.com/yusing/go-proxy/agent/pkg/agent" "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/net/gphttp/accesslog" "github.com/yusing/go-proxy/internal/notif" @@ -24,10 +27,10 @@ type ( TimeoutShutdown int `json:"timeout_shutdown" validate:"gte=0"` } Providers struct { - Files []string `json:"include" yaml:"include,omitempty" validate:"dive,filepath"` - Docker map[string]string `json:"docker" yaml:"docker,omitempty" validate:"non_empty_docker_keys,dive,unix_addr|url"` - Agents []*agent.AgentConfig `json:"agents" yaml:"agents,omitempty"` - Notification []notif.NotificationConfig `json:"notification" yaml:"notification,omitempty"` + Files []string `json:"include" yaml:"include,omitempty" validate:"unique,dive,config_file_exists"` + Docker map[string]string `json:"docker" yaml:"docker,omitempty" validate:"unique,dive,unix_addr|url"` + Agents []*agent.AgentConfig `json:"agents" yaml:"agents,omitempty" validate:"unique=Addr"` + Notification []notif.NotificationConfig `json:"notification" yaml:"notification,omitempty" validate:"unique=ProviderName"` } Entrypoint struct { Middlewares []map[string]any `json:"middlewares"` @@ -97,13 +100,9 @@ func init() { } return true }) - utils.MustRegisterValidation("non_empty_docker_keys", func(fl validator.FieldLevel) bool { - m := fl.Field().Interface().(map[string]string) - for k := range m { - if k == "" { - return false - } - } - return true + utils.MustRegisterValidation("config_file_exists", func(fl validator.FieldLevel) bool { + filename := fl.Field().Interface().(string) + info, err := os.Stat(path.Join(common.ConfigBasePath, filename)) + return err == nil && !info.IsDir() }) } diff --git a/internal/route/provider/event_handler.go b/internal/route/provider/event_handler.go index 2f2b939..73e2c16 100644 --- a/internal/route/provider/event_handler.go +++ b/internal/route/provider/event_handler.go @@ -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 { - switch handler.provider.GetType() { + switch handler.provider.Type() { case types.ProviderTypeDocker, types.ProviderTypeAgent: return route.Container.ContainerID == event.ActorID || route.Container.ContainerName == event.ActorName diff --git a/internal/route/provider/file.go b/internal/route/provider/file.go index 6c23a60..84bf84f 100644 --- a/internal/route/provider/file.go +++ b/internal/route/provider/file.go @@ -20,17 +20,12 @@ type FileProvider struct { l zerolog.Logger } -func FileProviderImpl(filename string) (ProviderImpl, error) { - impl := &FileProvider{ +func FileProviderImpl(filename string) ProviderImpl { + return &FileProvider{ fileName: filename, path: path.Join(common.ConfigBasePath, filename), 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) {