v0.5: (BREAKING) simplified config format, improved error output, updated proxy entry default value for 'port'

This commit is contained in:
default
2024-08-14 02:41:11 +08:00
parent 23e7d06081
commit 719693deb7
21 changed files with 197 additions and 302 deletions

View File

@@ -35,7 +35,7 @@ const (
ProvidersSchemaPath = SchemaBasePath + "providers.schema.json"
)
const DockerHostFromEnv = "FROM_ENV"
const DockerHostFromEnv = "$DOCKER_HOST"
const (
ProxyHTTPPort = ":80"

View File

@@ -134,16 +134,9 @@ func (cfg *Config) Statistics() map[string]interface{} {
panic("bug: should not reach here")
}
})
stats["type"] = p.GetType()
stats["num_streams"] = nStreams
stats["num_reverse_proxies"] = nRPs
switch p.ProviderImpl.(type) {
case *PR.DockerProvider:
stats["type"] = "docker"
case *PR.FileProvider:
stats["type"] = "file"
default:
panic("bug: should not reach here")
}
providerStats[p.GetName()] = stats
})
@@ -202,7 +195,7 @@ func (cfg *Config) load() E.NestedError {
}
if !common.NoSchemaValidation {
if err := Validate(data); err.IsNotNil() {
if err = Validate(data); err.IsNotNil() {
return err
}
}
@@ -220,13 +213,19 @@ func (cfg *Config) load() E.NestedError {
cfg.l.Debug("starting providers")
cfg.proxyProviders = F.NewMap[string, *PR.Provider]()
for name, pm := range model.Providers {
p := PR.NewProvider(name, pm)
cfg.proxyProviders.Set(name, p)
if err := p.StartAllRoutes(); err.IsNotNil() {
warnings.Add(E.Failure("start routes").Subjectf("provider %s", name).With(err))
}
for _, filename := range model.Providers.Files {
p := PR.NewFileProvider(filename)
cfg.proxyProviders.Set(p.GetName(), p)
}
for name, dockerHost := range model.Providers.Docker {
p := PR.NewDockerProvider(name, dockerHost)
cfg.proxyProviders.Set(p.GetName(), p)
}
cfg.proxyProviders.EachKV(func(name string, p *PR.Provider) {
if err := p.StartAllRoutes(); err.IsNotNil() {
warnings.Add(E.Failure("start routes").Subject(p).With(err))
}
})
cfg.l.Debug("started providers")
cfg.value = model
@@ -244,7 +243,7 @@ func (cfg *Config) controlProviders(action string, do func(*PR.Provider) E.Neste
cfg.proxyProviders.EachKVParallel(func(name string, p *PR.Provider) {
if err := do(p); err.IsNotNil() {
errors.Add(E.From(err).Subjectf("provider %s", name))
errors.Add(E.From(err).Subject(p))
}
})

View File

@@ -20,7 +20,7 @@ type (
// Caller then should handle the nested error,
// and continue with the valid values.
NestedError struct {
subject any
subject string
err error // can be nil
extras []NestedError
}
@@ -96,7 +96,14 @@ func (ne NestedError) Extraf(format string, args ...any) NestedError {
}
func (ne NestedError) Subject(s any) NestedError {
ne.subject = s
switch ss := s.(type) {
case string:
ne.subject = ss
case fmt.Stringer:
ne.subject = ss.String()
default:
ne.subject = fmt.Sprint(s)
}
return ne
}
@@ -107,7 +114,8 @@ func (ne NestedError) Subjectf(format string, args ...any) NestedError {
if strings.Contains(format, "%w") {
panic("Subjectf format should not contain %w")
}
return ne.Subject(fmt.Sprintf(format, args...))
ne.subject = fmt.Sprintf(format, args...)
return ne
}
func (ne NestedError) IsNil() bool {
@@ -134,7 +142,7 @@ func (ne *NestedError) writeToSB(sb *strings.Builder, level int, prefix string)
if ne.err != nil {
sb.WriteString(ne.err.Error())
}
if ne.subject != nil {
if ne.subject != "" {
if ne.err != nil {
sb.WriteString(fmt.Sprintf(" for %q", ne.subject))
} else {

View File

@@ -3,8 +3,8 @@ module github.com/yusing/go-proxy
go 1.22
require (
github.com/docker/cli v27.1.1+incompatible
github.com/docker/docker v27.1.1+incompatible
github.com/docker/cli v27.1.2+incompatible
github.com/docker/docker v27.1.2+incompatible
github.com/fsnotify/fsnotify v1.7.0
github.com/go-acme/lego/v4 v4.17.4
github.com/santhosh-tekuri/jsonschema v1.2.4

View File

@@ -13,10 +13,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/cli v27.1.2+incompatible h1:nYviRv5Y+YAKx3dFrTvS1ErkyVVunKOhoweCTE1BsnI=
github.com/docker/cli v27.1.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY=
github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=

View File

@@ -40,4 +40,10 @@ func (e *ProxyEntry) SetDefaults() {
if e.Path == "" {
e.Path = "/"
}
switch e.Scheme {
case "http":
e.Port = "80"
case "https":
e.Port = "443"
}
}

View File

@@ -1,9 +0,0 @@
package model
type (
ProxyProvider struct {
Kind string `json:"kind"` // docker, file
Value string `json:"value"`
}
ProxyProviders = map[string]ProxyProvider
)

View File

@@ -0,0 +1,6 @@
package model
type ProxyProviders struct {
Files []string `yaml:"include" json:"include"` // docker, file
Docker map[string]string `yaml:"docker" json:"docker"`
}

View File

@@ -11,7 +11,7 @@ type Port int
func NewPort(v string) (Port, E.NestedError) {
p, err := strconv.Atoi(v)
if err != nil {
return ErrPort, E.From(err)
return ErrPort, E.Invalid("port number", v).With(err)
}
return NewPortInt(p)
}

View File

@@ -16,8 +16,8 @@ type DockerProvider struct {
dockerHost string
}
func DockerProviderImpl(model *M.ProxyProvider) ProviderImpl {
return &DockerProvider{dockerHost: model.Value}
func DockerProviderImpl(dockerHost string) ProviderImpl {
return &DockerProvider{dockerHost: dockerHost}
}
// GetProxyEntries returns proxy entries from a docker client.
@@ -32,15 +32,16 @@ func DockerProviderImpl(model *M.ProxyProvider) ProviderImpl {
// - p: A pointer to the DockerProvider struct.
//
// Returns:
// - P.EntryModelSlice: A slice of EntryModel structs representing the proxy entries.
// - P.EntryModelSlice: (non-nil) A slice of EntryModel structs representing the proxy entries.
// - error: An error object if there was an error retrieving the docker client information or parsing the labels.
func (p DockerProvider) GetProxyEntries() (M.ProxyEntries, E.NestedError) {
entries := M.NewProxyEntries()
info, err := D.GetClientInfo(p.dockerHost)
if err.IsNotNil() {
return nil, E.From(err)
return entries, E.From(err)
}
entries := M.NewProxyEntries()
errors := E.NewBuilder("errors when parse docker labels for %q", p.dockerHost)
for _, container := range info.Containers {

View File

@@ -16,10 +16,10 @@ type FileProvider struct {
path string
}
func FileProviderImpl(m *M.ProxyProvider) ProviderImpl {
func FileProviderImpl(filename string) ProviderImpl {
return &FileProvider{
fileName: m.Value,
path: path.Join(common.ConfigBasePath, m.Value),
fileName: filename,
path: path.Join(common.ConfigBasePath, filename),
}
}
@@ -27,13 +27,17 @@ func Validate(data []byte) E.NestedError {
return U.ValidateYaml(U.GetSchema(common.ProvidersSchemaPath), data)
}
func (p *FileProvider) String() string {
return p.fileName
}
func (p *FileProvider) GetProxyEntries() (M.ProxyEntries, E.NestedError) {
entries := M.NewProxyEntries()
data, err := E.Check(os.ReadFile(p.path))
if err.IsNotNil() {
return entries, E.Failure("read file").Subject(p.fileName).With(err)
return entries, E.Failure("read file").Subject(p).With(err)
}
ne := E.Failure("validation").Subject(p.fileName)
ne := E.Failure("validation").Subject(p)
if !common.NoSchemaValidation {
if err = Validate(data); err.IsNotNil() {
return entries, ne.With(err)

View File

@@ -2,9 +2,10 @@ package provider
import (
"context"
"fmt"
"path"
"github.com/sirupsen/logrus"
"github.com/yusing/go-proxy/common"
E "github.com/yusing/go-proxy/error"
M "github.com/yusing/go-proxy/models"
R "github.com/yusing/go-proxy/route"
@@ -20,6 +21,7 @@ type Provider struct {
ProviderImpl
name string
t ProviderType
routes *R.Routes
reloadReqCh chan struct{}
@@ -30,27 +32,49 @@ type Provider struct {
l *logrus.Entry
}
func NewProvider(name string, model M.ProxyProvider) (p *Provider) {
p = &Provider{
type ProviderType string
const (
ProviderTypeDocker ProviderType = "docker"
ProviderTypeFile ProviderType = "file"
)
func newProvider(name string, t ProviderType) *Provider {
return &Provider{
name: name,
t: t,
routes: R.NewRoutes(),
reloadReqCh: make(chan struct{}, 1),
l: logrus.WithField("provider", name),
}
switch model.Kind {
case common.ProviderKind_Docker:
p.ProviderImpl = DockerProviderImpl(&model)
case common.ProviderKind_File:
p.ProviderImpl = FileProviderImpl(&model)
}
}
func NewFileProvider(filename string) *Provider {
name := path.Base(filename)
p := newProvider(name, ProviderTypeFile)
p.ProviderImpl = FileProviderImpl(filename)
p.watcher = p.NewWatcher()
return
return p
}
func NewDockerProvider(name string, dockerHost string) *Provider {
p := newProvider(name, ProviderTypeDocker)
p.ProviderImpl = DockerProviderImpl(dockerHost)
p.watcher = p.NewWatcher()
return p
}
func (p *Provider) GetName() string {
return p.name
}
func (p *Provider) GetType() ProviderType {
return p.t
}
func (p *Provider) String() string {
return fmt.Sprintf("%s (%s provider)", p.name, p.t)
}
func (p *Provider) StartAllRoutes() E.NestedError {
err := p.loadRoutes()
@@ -58,23 +82,24 @@ func (p *Provider) StartAllRoutes() E.NestedError {
p.watcherCtx, p.watcherCancel = context.WithCancel(context.Background())
go p.watchEvents()
if err.IsNotNil() {
return err
}
errors := E.NewBuilder("errors starting routes for provider %q", p.name)
errors := E.NewBuilder("errors in routes")
nStarted := 0
nFailed := 0
if err.IsNotNil() {
errors.Add(err)
}
p.routes.EachKVParallel(func(alias string, r R.Route) {
if err := r.Start(); err.IsNotNil() {
errors.Add(err.Subject(alias))
errors.Add(err.Subject(r))
nFailed++
} else {
nStarted++
}
})
if err := errors.Build(); err.IsNotNil() {
return err
}
p.l.Infof("%d routes started", nStarted)
return E.Nil()
p.l.Infof("%d routes started, %d failed", nStarted, nFailed)
return errors.Build()
}
func (p *Provider) StopAllRoutes() E.NestedError {
@@ -87,7 +112,7 @@ func (p *Provider) StopAllRoutes() E.NestedError {
nStopped := 0
p.routes.EachKVParallel(func(alias string, r R.Route) {
if err := r.Stop(); err.IsNotNil() {
errors.Add(err.Subject(alias))
errors.Add(err.Subject(r))
} else {
nStopped++
}
@@ -146,7 +171,7 @@ func (p *Provider) loadRoutes() E.NestedError {
entries, err := p.GetProxyEntries()
if err.IsNotNil() {
p.l.Warn(err.Subjectf("provider %s", p.name))
p.l.Warn(err.Subject(p))
}
p.routes = R.NewRoutes()

View File

@@ -106,6 +106,10 @@ func NewHTTPRoute(entry *P.Entry) (*HTTPRoute, E.NestedError) {
return r, E.Nil()
}
func (r *HTTPRoute) String() string {
return fmt.Sprintf("%s (reverse proxy)", r.Alias)
}
func (r *HTTPRoute) Start() E.NestedError {
httpRoutes.Set(r.Alias.String(), r)
return E.Nil()

View File

@@ -11,6 +11,7 @@ type (
Route interface {
Start() E.NestedError
Stop() E.NestedError
String() string
}
Routes = F.Map[string, Route]
)

View File

@@ -49,6 +49,10 @@ func NewStreamRoute(entry *P.StreamEntry) (*StreamRoute, E.NestedError) {
return base, E.Nil()
}
func (r *StreamRoute) String() string {
return fmt.Sprintf("%s (%v stream)", r.Alias, r.Scheme)
}
func (r *StreamRoute) Start() E.NestedError {
if r.started.Load() {
return E.Invalid("state", "already started")