mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-28 03:07:07 +02:00
v0.26.0
This commit is contained in:
@@ -39,7 +39,7 @@ type ProviderImpl interface {
|
||||
fmt.Stringer
|
||||
ShortName() string
|
||||
IsExplicitOnly() bool
|
||||
loadRoutesImpl() (route.Routes, gperr.Error)
|
||||
loadRoutesImpl() (route.Routes, error)
|
||||
NewWatcher() W.Watcher
|
||||
Logger() *zerolog.Logger
|
||||
}
|
||||
@@ -62,8 +62,8 @@ func NewAgentProvider(cfg *agent.AgentConfig) *Provider
|
||||
|
||||
```go
|
||||
func (p *Provider) GetType() provider.Type
|
||||
func (p *Provider) Start(parent task.Parent) gperr.Error
|
||||
func (p *Provider) LoadRoutes() gperr.Error
|
||||
func (p *Provider) Start(parent task.Parent) error
|
||||
func (p *Provider) LoadRoutes() error
|
||||
func (p *Provider) IterRoutes(yield func(string, types.Route) bool)
|
||||
func (p *Provider) GetRoute(alias string) (types.Route, bool)
|
||||
func (p *Provider) FindService(project, service string) (types.Route, bool)
|
||||
@@ -80,8 +80,8 @@ classDiagram
|
||||
+t provider.Type
|
||||
+routes route.Routes
|
||||
+watcher W.Watcher
|
||||
+Start(parent) gperr.Error
|
||||
+LoadRoutes() gperr.Error
|
||||
+Start(parent) error
|
||||
+LoadRoutes() error
|
||||
+IterRoutes(yield)
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ classDiagram
|
||||
+String() string
|
||||
+ShortName() string
|
||||
+IsExplicitOnly() bool
|
||||
+loadRoutesImpl() (route.Routes, gperr.Error)
|
||||
+loadRoutesImpl() (route.Routes, error)
|
||||
+NewWatcher() W.Watcher
|
||||
+Logger() *zerolog.Logger
|
||||
}
|
||||
@@ -99,20 +99,20 @@ classDiagram
|
||||
+name string
|
||||
+dockerCfg types.DockerProviderConfig
|
||||
+ShortName() string
|
||||
+loadRoutesImpl() (route.Routes, gperr.Error)
|
||||
+loadRoutesImpl() (route.Routes, error)
|
||||
}
|
||||
|
||||
class FileProviderImpl {
|
||||
+filename string
|
||||
+ShortName() string
|
||||
+loadRoutesImpl() (route.Routes, gperr.Error)
|
||||
+loadRoutesImpl() (route.Routes, error)
|
||||
}
|
||||
|
||||
class AgentProviderImpl {
|
||||
+*agent.AgentConfig
|
||||
+docker DockerProviderImpl
|
||||
+ShortName() string
|
||||
+loadRoutesImpl() (route.Routes, gperr.Error)
|
||||
+loadRoutesImpl() (route.Routes, error)
|
||||
}
|
||||
|
||||
Provider --> ProviderImpl : wraps
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
"github.com/yusing/godoxy/internal/watcher"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type AgentProvider struct {
|
||||
@@ -25,7 +24,7 @@ func (p *AgentProvider) IsExplicitOnly() bool {
|
||||
return p.docker.IsExplicitOnly()
|
||||
}
|
||||
|
||||
func (p *AgentProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
func (p *AgentProvider) loadRoutesImpl() (route.Routes, error) {
|
||||
return p.docker.loadRoutesImpl()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,18 +2,44 @@ example: # matching `example.y.z`
|
||||
scheme: http
|
||||
host: 10.0.0.254
|
||||
port: 80
|
||||
bind: 0.0.0.0
|
||||
root: /var/www/example
|
||||
spa: true
|
||||
index: index.html
|
||||
no_tls_verify: true
|
||||
disable_compression: false
|
||||
response_header_timeout: 30s
|
||||
ssl_server_name: "" # empty uses target hostname, "off" disables SNI
|
||||
ssl_trusted_certificate: /etc/ssl/certs/ca-certificates.crt
|
||||
ssl_certificate: /etc/ssl/client.crt
|
||||
ssl_certificate_key: /etc/ssl/client.key
|
||||
ssl_protocols:
|
||||
- tlsv1.2
|
||||
- tlsv1.3
|
||||
path_patterns: # Check https://pkg.go.dev/net/http#hdr-Patterns-ServeMux for syntax
|
||||
- GET / # accept any GET request
|
||||
- POST /auth # for /auth and /auth/* accept only POST
|
||||
- GET /home/{$} # for exactly /home
|
||||
rules:
|
||||
- name: default
|
||||
do: pass
|
||||
- name: block-admin
|
||||
on: path /admin
|
||||
do: error 403 Forbidden
|
||||
rule_file: embed://webui.yml
|
||||
healthcheck:
|
||||
disabled: false
|
||||
use_get: true
|
||||
path: /
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: -1 # -1: immediate fail, 0: use default, >0: retry count
|
||||
load_balance:
|
||||
link: app
|
||||
mode: ip_hash
|
||||
link: app # link to another route alias
|
||||
mode: roundrobin # roundrobin, leastconn, iphash
|
||||
weight: 1
|
||||
sticky: false
|
||||
sticky_max_age: 1h
|
||||
options:
|
||||
header: X-Forwarded-For
|
||||
middlewares:
|
||||
@@ -23,15 +49,19 @@ example: # matching `example.y.z`
|
||||
- 10.0.0.0/8
|
||||
status_code: 403
|
||||
message: IP not allowed
|
||||
hideXForwarded:
|
||||
homepage:
|
||||
show: true
|
||||
name: Example App
|
||||
icon: "@selfhst/adguard-home.png"
|
||||
description: An example app
|
||||
category: example
|
||||
access_log:
|
||||
buffer_size: 100
|
||||
path: /var/log/example.log
|
||||
stdout: false
|
||||
retention:
|
||||
days: 30
|
||||
rotate_interval: 24h
|
||||
format: combined # common, combined, json
|
||||
filters:
|
||||
status_codes:
|
||||
values:
|
||||
@@ -53,14 +83,29 @@ example: # matching `example.y.z`
|
||||
- 192.168.10.0/24
|
||||
fields:
|
||||
headers:
|
||||
default: keep
|
||||
config:
|
||||
foo: redact
|
||||
query:
|
||||
default: drop
|
||||
config:
|
||||
foo: keep
|
||||
cookies:
|
||||
default: redact
|
||||
foo: redact
|
||||
authorization: drop
|
||||
query:
|
||||
default: keep
|
||||
config:
|
||||
foo: keep
|
||||
password: redact
|
||||
cookies:
|
||||
default: drop
|
||||
config:
|
||||
session: keep
|
||||
idlewatcher:
|
||||
idle_timeout: 30m
|
||||
wake_timeout: 30s
|
||||
stop_timeout: 1m
|
||||
stop_method: stop # pause, stop, kill
|
||||
stop_signal: SIGTERM
|
||||
start_endpoint: /api/wake
|
||||
depends_on:
|
||||
- other-service
|
||||
no_loading_page: false
|
||||
docker:
|
||||
container_id: abc123
|
||||
container_name: example-app
|
||||
|
||||
@@ -58,13 +58,13 @@ func (p *DockerProvider) NewWatcher() watcher.Watcher {
|
||||
return watcher.NewDockerWatcher(p.dockerCfg)
|
||||
}
|
||||
|
||||
func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
func (p *DockerProvider) loadRoutesImpl() (route.Routes, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
containers, err := docker.ListContainers(ctx, p.dockerCfg)
|
||||
if err != nil {
|
||||
return nil, gperr.Wrap(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gperr.NewBuilder("")
|
||||
@@ -74,21 +74,21 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
container := docker.FromDocker(&c, p.dockerCfg)
|
||||
|
||||
if container.Errors != nil {
|
||||
errs.Add(gperr.PrependSubject(container.ContainerName, container.Errors))
|
||||
errs.AddSubject(container.Errors, container.ContainerName)
|
||||
continue
|
||||
}
|
||||
|
||||
if container.IsHostNetworkMode {
|
||||
err := docker.UpdatePorts(ctx, container)
|
||||
if err != nil {
|
||||
errs.Add(gperr.PrependSubject(container.ContainerName, err))
|
||||
errs.AddSubject(err, container.ContainerName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
newEntries, err := p.routesFromContainerLabels(container)
|
||||
if err != nil {
|
||||
errs.Add(err.Subject(container.ContainerName))
|
||||
errs.AddSubject(err, container.ContainerName)
|
||||
}
|
||||
for k, v := range newEntries {
|
||||
if conflict, ok := routes[k]; ok {
|
||||
@@ -97,7 +97,7 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
Addf("container %s", container.ContainerName).
|
||||
Addf("conflicting container %s", conflict.Container.ContainerName)
|
||||
if conflict.ShouldExclude() || v.ShouldExclude() {
|
||||
gperr.LogWarn("skipping conflicting route", err)
|
||||
log.Warn().Err(err).Msg("skipping conflicting route")
|
||||
} else {
|
||||
errs.Add(err)
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
|
||||
// Returns a list of proxy entries for a container.
|
||||
// Always non-nil.
|
||||
func (p *DockerProvider) routesFromContainerLabels(container *types.Container) (route.Routes, gperr.Error) {
|
||||
func (p *DockerProvider) routesFromContainerLabels(container *types.Container) (route.Routes, error) {
|
||||
if !container.IsExplicit && p.IsExplicitOnly() {
|
||||
return make(route.Routes, 0), nil
|
||||
}
|
||||
@@ -150,7 +150,7 @@ func (p *DockerProvider) routesFromContainerLabels(container *types.Container) (
|
||||
panic(fmt.Errorf("invalid entry map type %T", entryMapAny))
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(yamlStr), &entryMap); err != nil {
|
||||
errs.Add(gperr.Wrap(err).Subject(alias))
|
||||
errs.AddSubject(err, alias)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -185,7 +185,7 @@ func (p *DockerProvider) routesFromContainerLabels(container *types.Container) (
|
||||
// deserialize map into entry object
|
||||
err := serialization.MapUnmarshalValidate(entryMap, r)
|
||||
if err != nil {
|
||||
errs.Add(err.Subject(alias))
|
||||
errs.AddSubject(err, alias)
|
||||
} else {
|
||||
routes[alias] = r
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ proxy.app: |
|
||||
description: An example app
|
||||
category: example
|
||||
access_log:
|
||||
buffer_size: 100
|
||||
path: /var/log/example.log
|
||||
filters:
|
||||
status_codes:
|
||||
@@ -92,7 +91,6 @@ proxy.app1.homepage.name: Example App
|
||||
proxy.app1.homepage.icon: "@selfhst/adguard-home.png"
|
||||
proxy.app1.homepage.description: An example app
|
||||
proxy.app1.homepage.category: example
|
||||
proxy.app1.access_log.buffer_size: 100
|
||||
proxy.app1.access_log.path: /var/log/example.log
|
||||
proxy.app1.access_log.filters: |
|
||||
status_codes:
|
||||
|
||||
@@ -81,7 +81,7 @@ func (handler *EventHandler) match(event watcher.Event, route *route.Route) bool
|
||||
func (handler *EventHandler) Add(parent task.Parent, route *route.Route) {
|
||||
err := handler.provider.startRoute(parent, route)
|
||||
if err != nil {
|
||||
handler.errs.Add(err.Subject("add"))
|
||||
handler.errs.AddSubjectf(err, "add")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,12 +93,12 @@ func (handler *EventHandler) Update(parent task.Parent, oldRoute *route.Route, n
|
||||
oldRoute.FinishAndWait("route update")
|
||||
err := handler.provider.startRoute(parent, newRoute)
|
||||
if err != nil {
|
||||
handler.errs.Add(err.Subject("update"))
|
||||
handler.errs.AddSubjectf(err, "update")
|
||||
}
|
||||
}
|
||||
|
||||
func (handler *EventHandler) Log() {
|
||||
if err := handler.errs.Error(); err != nil {
|
||||
handler.provider.Logger().Info().Msg(err.Error())
|
||||
handler.provider.Logger().Error().Msg(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
W "github.com/yusing/godoxy/internal/watcher"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type FileProvider struct {
|
||||
@@ -34,7 +33,7 @@ func FileProviderImpl(filename string) (ProviderImpl, error) {
|
||||
return impl, nil
|
||||
}
|
||||
|
||||
func removeXPrefix(m map[string]any) gperr.Error {
|
||||
func removeXPrefix(m map[string]any) error {
|
||||
for alias := range m {
|
||||
if strings.HasPrefix(alias, "x-") {
|
||||
delete(m, alias)
|
||||
@@ -43,12 +42,12 @@ func removeXPrefix(m map[string]any) gperr.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validate(data []byte) (routes route.Routes, err gperr.Error) {
|
||||
func validate(data []byte) (routes route.Routes, err error) {
|
||||
err = serialization.UnmarshalValidate(data, &routes, yaml.Unmarshal, removeXPrefix)
|
||||
return routes, err
|
||||
}
|
||||
|
||||
func Validate(data []byte) (err gperr.Error) {
|
||||
func Validate(data []byte) (err error) {
|
||||
_, err = validate(data)
|
||||
return err
|
||||
}
|
||||
@@ -69,16 +68,16 @@ func (p *FileProvider) Logger() *zerolog.Logger {
|
||||
return &p.l
|
||||
}
|
||||
|
||||
func (p *FileProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
func (p *FileProvider) loadRoutesImpl() (route.Routes, error) {
|
||||
data, err := os.ReadFile(p.path)
|
||||
if err != nil {
|
||||
return nil, gperr.Wrap(err)
|
||||
return nil, err
|
||||
}
|
||||
routes, err := validate(data)
|
||||
if err != nil && len(routes) == 0 {
|
||||
return nil, gperr.Wrap(err)
|
||||
return nil, err
|
||||
}
|
||||
return routes, gperr.Wrap(err)
|
||||
return routes, err
|
||||
}
|
||||
|
||||
func (p *FileProvider) NewWatcher() W.Watcher {
|
||||
|
||||
@@ -15,8 +15,10 @@ import (
|
||||
provider "github.com/yusing/godoxy/internal/route/provider/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
W "github.com/yusing/godoxy/internal/watcher"
|
||||
"github.com/yusing/godoxy/internal/watcher/events"
|
||||
watcherEvents "github.com/yusing/godoxy/internal/watcher/events"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/eventqueue"
|
||||
"github.com/yusing/goutils/events"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
@@ -34,7 +36,7 @@ type (
|
||||
fmt.Stringer
|
||||
ShortName() string
|
||||
IsExplicitOnly() bool
|
||||
loadRoutesImpl() (route.Routes, gperr.Error)
|
||||
loadRoutesImpl() (route.Routes, error)
|
||||
NewWatcher() W.Watcher
|
||||
Logger() *zerolog.Logger
|
||||
}
|
||||
@@ -90,13 +92,13 @@ func (p *Provider) GetType() provider.Type {
|
||||
return p.t
|
||||
}
|
||||
|
||||
// to work with json marshaller.
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (p *Provider) MarshalText() ([]byte, error) {
|
||||
return []byte(p.String()), nil
|
||||
}
|
||||
|
||||
// Start implements task.TaskStarter.
|
||||
func (p *Provider) Start(parent task.Parent) gperr.Error {
|
||||
func (p *Provider) Start(parent task.Parent) error {
|
||||
errs := gperr.NewGroup("routes error")
|
||||
|
||||
t := parent.Subtask("provider."+p.String(), false)
|
||||
@@ -115,19 +117,29 @@ func (p *Provider) Start(parent task.Parent) gperr.Error {
|
||||
|
||||
err := errs.Wait().Error()
|
||||
|
||||
eventQueue := events.NewEventQueue(
|
||||
t.Subtask("event_queue", false),
|
||||
providerEventFlushInterval,
|
||||
func(events []events.Event) {
|
||||
opts := eventqueue.Options[watcherEvents.Event]{
|
||||
FlushInterval: providerEventFlushInterval,
|
||||
OnFlush: func(evs []watcherEvents.Event) {
|
||||
handler := p.newEventHandler()
|
||||
// routes' lifetime should follow the provider's lifetime
|
||||
handler.Handle(t, events)
|
||||
handler.Handle(t, evs)
|
||||
handler.Log()
|
||||
|
||||
globalEvents := make([]events.Event, len(evs))
|
||||
for i, ev := range evs {
|
||||
globalEvents[i] = events.NewEvent(events.LevelInfo, "provider_event", ev.Action.String(), map[string]any{
|
||||
"provider": p.String(),
|
||||
"type": ev.Type, // file / docker
|
||||
"actor": ev.ActorName, // file path / container name
|
||||
})
|
||||
}
|
||||
events.Global.AddAll(globalEvents)
|
||||
},
|
||||
func(err gperr.Error) {
|
||||
gperr.LogError("event error", err, p.Logger())
|
||||
OnError: func(err error) {
|
||||
p.Logger().Err(err).Msg("event error")
|
||||
},
|
||||
)
|
||||
}
|
||||
eventQueue := eventqueue.New(t.Subtask("event_queue", false), opts)
|
||||
eventQueue.Start(p.watcher.Events(t.Context()))
|
||||
|
||||
if err != nil {
|
||||
@@ -136,7 +148,7 @@ func (p *Provider) Start(parent task.Parent) gperr.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) LoadRoutes() (err gperr.Error) {
|
||||
func (p *Provider) LoadRoutes() (err error) {
|
||||
p.routes, err = p.loadRoutes()
|
||||
return err
|
||||
}
|
||||
@@ -188,7 +200,7 @@ func (p *Provider) GetRoute(alias string) (types.Route, bool) {
|
||||
return r.Impl(), true
|
||||
}
|
||||
|
||||
func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) {
|
||||
func (p *Provider) loadRoutes() (routes route.Routes, err error) {
|
||||
routes, err = p.loadRoutesImpl()
|
||||
if err != nil && len(routes) == 0 {
|
||||
return route.Routes{}, err
|
||||
@@ -201,7 +213,7 @@ func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) {
|
||||
r.Alias = alias
|
||||
r.SetProvider(p)
|
||||
if err := r.Validate(); err != nil {
|
||||
errs.Add(err.Subject(alias))
|
||||
errs.AddSubject(err, alias)
|
||||
delete(routes, alias)
|
||||
continue
|
||||
}
|
||||
@@ -210,11 +222,11 @@ func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) {
|
||||
return routes, errs.Error()
|
||||
}
|
||||
|
||||
func (p *Provider) startRoute(parent task.Parent, r *route.Route) gperr.Error {
|
||||
func (p *Provider) startRoute(parent task.Parent, r *route.Route) error {
|
||||
err := r.Start(parent)
|
||||
if err != nil {
|
||||
p.lockDeleteRoute(r.Alias)
|
||||
return err.Subject(r.Alias)
|
||||
return gperr.PrependSubject(err, r.Alias)
|
||||
}
|
||||
|
||||
p.lockAddRoute(r)
|
||||
|
||||
Reference in New Issue
Block a user