implement godoxy-agent

This commit is contained in:
yusing
2025-02-10 09:36:37 +08:00
parent ecb89f80a0
commit eaf191e350
57 changed files with 1479 additions and 467 deletions

View File

@@ -99,7 +99,7 @@ func (s *FileServer) Start(parent task.Parent) E.Error {
}
if s.UseHealthCheck() {
s.Health = monitor.NewFileServerHealthMonitor(s.TargetName(), s.HealthCheck, s.Root)
s.Health = monitor.NewFileServerHealthMonitor(s.HealthCheck, s.Root)
if err := s.Health.Start(s.task); err != nil {
return err
}

View File

@@ -0,0 +1,34 @@
package provider
import (
"github.com/rs/zerolog"
"github.com/yusing/go-proxy/agent/pkg/agent"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/route"
"github.com/yusing/go-proxy/internal/watcher"
)
type AgentProvider struct {
*agent.AgentConfig
docker ProviderImpl
}
func (p *AgentProvider) ShortName() string {
return p.Name()
}
func (p *AgentProvider) NewWatcher() watcher.Watcher {
return p.docker.NewWatcher()
}
func (p *AgentProvider) IsExplicitOnly() bool {
return p.docker.IsExplicitOnly()
}
func (p *AgentProvider) loadRoutesImpl() (route.Routes, E.Error) {
return p.docker.loadRoutesImpl()
}
func (p *AgentProvider) Logger() *zerolog.Logger {
return p.docker.Logger()
}

View File

@@ -29,7 +29,7 @@ const (
var ErrAliasRefIndexOutOfRange = E.New("index out of range")
func DockerProviderImpl(name, dockerHost string) (ProviderImpl, error) {
func DockerProviderImpl(name, dockerHost string) ProviderImpl {
if dockerHost == common.DockerHostFromEnv {
dockerHost = common.GetEnvString("DOCKER_HOST", client.DefaultDockerHost)
}
@@ -37,7 +37,7 @@ func DockerProviderImpl(name, dockerHost string) (ProviderImpl, error) {
name,
dockerHost,
logging.With().Str("type", "docker").Str("name", name).Logger(),
}, nil
}
}
func (p *DockerProvider) String() string {

View File

@@ -258,16 +258,16 @@ func TestPublicIPLocalhost(t *testing.T) {
c := &types.Container{Names: dummyNames, State: "running"}
r, ok := makeRoutes(c)["a"]
ExpectTrue(t, ok)
ExpectEqual(t, r.Container.PublicIP, "127.0.0.1")
ExpectEqual(t, r.Host, r.Container.PublicIP)
ExpectEqual(t, r.Container.PublicHostname, "127.0.0.1")
ExpectEqual(t, r.Host, r.Container.PublicHostname)
}
func TestPublicIPRemote(t *testing.T) {
c := &types.Container{Names: dummyNames, State: "running"}
raw, ok := makeRoutes(c, testIP)["a"]
ExpectTrue(t, ok)
ExpectEqual(t, raw.Container.PublicIP, testIP)
ExpectEqual(t, raw.Host, raw.Container.PublicIP)
ExpectEqual(t, raw.Container.PublicHostname, testIP)
ExpectEqual(t, raw.Host, raw.Container.PublicHostname)
}
func TestPrivateIPLocalhost(t *testing.T) {
@@ -283,8 +283,8 @@ func TestPrivateIPLocalhost(t *testing.T) {
}
r, ok := makeRoutes(c)["a"]
ExpectTrue(t, ok)
ExpectEqual(t, r.Container.PrivateIP, testDockerIP)
ExpectEqual(t, r.Host, r.Container.PrivateIP)
ExpectEqual(t, r.Container.PrivateHostname, testDockerIP)
ExpectEqual(t, r.Host, r.Container.PrivateHostname)
}
func TestPrivateIPRemote(t *testing.T) {
@@ -301,9 +301,9 @@ func TestPrivateIPRemote(t *testing.T) {
}
r, ok := makeRoutes(c, testIP)["a"]
ExpectTrue(t, ok)
ExpectEqual(t, r.Container.PrivateIP, "")
ExpectEqual(t, r.Container.PublicIP, testIP)
ExpectEqual(t, r.Host, r.Container.PublicIP)
ExpectEqual(t, r.Container.PrivateHostname, "")
ExpectEqual(t, r.Container.PublicHostname, testIP)
ExpectEqual(t, r.Host, r.Container.PublicHostname)
}
func TestStreamDefaultValues(t *testing.T) {

View File

@@ -1,7 +1,6 @@
package provider
import (
"github.com/yusing/go-proxy/internal/common"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/route"
"github.com/yusing/go-proxy/internal/route/provider/types"
@@ -38,25 +37,25 @@ func (handler *EventHandler) Handle(parent task.Parent, events []watcher.Event)
}
}
if common.IsDebug {
eventsLog := E.NewBuilder("events")
for _, event := range events {
eventsLog.Addf("event %s, actor: name=%s, id=%s", event.Action, event.ActorName, event.ActorID)
}
E.LogDebug(eventsLog.About(), eventsLog.Error(), handler.provider.Logger())
// if common.IsDebug {
// eventsLog := E.NewBuilder("events")
// for _, event := range events {
// eventsLog.Addf("event %s, actor: name=%s, id=%s", event.Action, event.ActorName, event.ActorID)
// }
// E.LogDebug(eventsLog.About(), eventsLog.Error(), handler.provider.Logger())
oldRoutesLog := E.NewBuilder("old routes")
for k := range oldRoutes {
oldRoutesLog.Adds(k)
}
E.LogDebug(oldRoutesLog.About(), oldRoutesLog.Error(), handler.provider.Logger())
// oldRoutesLog := E.NewBuilder("old routes")
// for k := range oldRoutes {
// oldRoutesLog.Adds(k)
// }
// E.LogDebug(oldRoutesLog.About(), oldRoutesLog.Error(), handler.provider.Logger())
newRoutesLog := E.NewBuilder("new routes")
for k := range newRoutes {
newRoutesLog.Adds(k)
}
E.LogDebug(newRoutesLog.About(), newRoutesLog.Error(), handler.provider.Logger())
}
// newRoutesLog := E.NewBuilder("new routes")
// for k := range newRoutes {
// newRoutesLog.Adds(k)
// }
// E.LogDebug(newRoutesLog.About(), newRoutesLog.Error(), handler.provider.Logger())
// }
for k, oldr := range oldRoutes {
newr, ok := newRoutes[k]
@@ -85,7 +84,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() {
case types.ProviderTypeDocker:
case types.ProviderTypeDocker, types.ProviderTypeAgent:
return route.Container.ContainerID == event.ActorID ||
route.Container.ContainerName == event.ActorName
case types.ProviderTypeFile:

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/rs/zerolog"
"github.com/yusing/go-proxy/agent/pkg/agent"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/route"
"github.com/yusing/go-proxy/internal/route/provider/types"
@@ -64,14 +65,22 @@ func NewDockerProvider(name string, dockerHost string) (p *Provider, err error)
}
p = newProvider(types.ProviderTypeDocker)
p.ProviderImpl, err = DockerProviderImpl(name, dockerHost)
if err != nil {
return nil, err
}
p.ProviderImpl = DockerProviderImpl(name, dockerHost)
p.watcher = p.NewWatcher()
return
}
func NewAgentProvider(cfg *agent.AgentConfig) *Provider {
p := newProvider(types.ProviderTypeAgent)
agent := &AgentProvider{
AgentConfig: cfg,
docker: DockerProviderImpl(cfg.Name(), cfg.FakeDockerHost()),
}
p.ProviderImpl = agent
p.watcher = p.NewWatcher()
return p
}
func (p *Provider) GetType() types.ProviderType {
return p.t
}

View File

@@ -5,4 +5,5 @@ type ProviderType string
const (
ProviderTypeDocker ProviderType = "docker"
ProviderTypeFile ProviderType = "file"
ProviderTypeAgent ProviderType = "agent"
)

View File

@@ -1,8 +1,11 @@
package route
import (
"crypto/tls"
"net/http"
"github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/agent/pkg/agentproxy"
"github.com/yusing/go-proxy/internal/api/v1/favicon"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/docker"
@@ -38,20 +41,27 @@ type (
// var globalMux = http.NewServeMux() // TODO: support regex subdomain matching.
// TODO: fix this for agent
func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, E.Error) {
trans := gphttp.DefaultTransport
httpConfig := base.HTTPConfig
proxyURL := base.ProxyURL
if httpConfig.NoTLSVerify {
trans = gphttp.DefaultTransportNoTLS
}
if httpConfig.ResponseHeaderTimeout > 0 {
trans = trans.Clone()
trans.ResponseHeaderTimeout = httpConfig.ResponseHeaderTimeout
trans := gphttp.NewTransport()
a := base.Agent()
if a != nil {
trans = a.Transport()
proxyURL = agent.HTTPProxyURL
} else {
if httpConfig.NoTLSVerify {
trans.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
if httpConfig.ResponseHeaderTimeout > 0 {
trans.ResponseHeaderTimeout = httpConfig.ResponseHeaderTimeout
}
}
service := base.TargetName()
rp := reverseproxy.NewReverseProxy(service, base.ProxyURL, trans)
rp := reverseproxy.NewReverseProxy(service, proxyURL, trans)
if len(base.Middlewares) > 0 {
err := middleware.PatchReverseProxy(rp, base.Middlewares)
@@ -60,6 +70,20 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, E.Error) {
}
}
if a != nil {
headers := &agentproxy.AgentProxyHeaders{
Host: base.ProxyURL.Host,
IsHTTPS: base.ProxyURL.Scheme == "https",
SkipTLSVerify: httpConfig.NoTLSVerify,
ResponseHeaderTimeout: int(httpConfig.ResponseHeaderTimeout.Seconds()),
}
ori := rp.HandlerFunc
rp.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
agentproxy.SetAgentProxyHeaders(r, headers)
ori(w, r)
}
}
r := &ReveseProxyRoute{
Route: base,
rp: rp,
@@ -88,13 +112,13 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) E.Error {
if r.IsDocker() {
client, err := docker.ConnectClient(r.Idlewatcher.DockerHost)
if err == nil {
fallback := monitor.NewHTTPHealthChecker(r.rp.TargetURL, r.HealthCheck)
fallback := r.newHealthMonitor()
r.HealthMon = monitor.NewDockerHealthMonitor(client, r.Idlewatcher.ContainerID, r.TargetName(), r.HealthCheck, fallback)
r.task.OnCancel("close_docker_client", client.Close)
}
}
if r.HealthMon == nil {
r.HealthMon = monitor.NewHTTPHealthMonitor(r.rp.TargetURL, r.HealthCheck)
r.HealthMon = r.newHealthMonitor()
}
}
@@ -178,6 +202,17 @@ func (r *ReveseProxyRoute) HealthMonitor() health.HealthMonitor {
return r.HealthMon
}
func (r *ReveseProxyRoute) newHealthMonitor() interface {
health.HealthMonitor
health.HealthChecker
} {
if a := r.Agent(); a != nil {
target := monitor.AgentCheckHealthTargetFromURL(r.ProxyURL)
return monitor.NewAgentRouteMonitor(a, r.HealthCheck, target)
}
return monitor.NewHTTPHealthMonitor(r.ProxyURL, r.HealthCheck)
}
func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent) {
var lb *loadbalancer.LoadBalancer
cfg := r.LoadBalance

View File

@@ -5,6 +5,7 @@ import (
"strconv"
"strings"
"github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/internal/docker"
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
"github.com/yusing/go-proxy/internal/homepage"
@@ -159,6 +160,17 @@ func (r *Route) Type() types.RouteType {
panic(fmt.Errorf("unexpected scheme %s for alias %s", r.Scheme, r.Alias))
}
func (r *Route) Agent() *agent.AgentConfig {
if r.Container == nil {
return nil
}
return r.Container.Agent
}
func (r *Route) IsAgent() bool {
return r.Container != nil && r.Container.Agent != nil
}
func (r *Route) HealthMonitor() health.HealthMonitor {
return r.impl.HealthMonitor()
}
@@ -240,24 +252,24 @@ func (r *Route) Finalize() {
switch {
case !isDocker:
r.Host = "localhost"
case cont.PrivateIP != "":
r.Host = cont.PrivateIP
case cont.PublicIP != "":
r.Host = cont.PublicIP
case cont.PrivateHostname != "":
r.Host = cont.PrivateHostname
case cont.PublicHostname != "":
r.Host = cont.PublicHostname
}
}
lp, pp := r.Port.Listening, r.Port.Proxy
if isDocker {
if port, ok := common.ServiceNamePortMapTCP[cont.ImageName]; ok {
if port, ok := common.ImageNamePortMapTCP[cont.ImageName]; ok {
if pp == 0 {
pp = port
}
if r.Scheme == "" {
r.Scheme = "tcp"
}
} else if port, ok := common.ImageNamePortMap[cont.ImageName]; ok {
} else if port, ok := common.ImageNamePortMapHTTP[cont.ImageName]; ok {
if pp == 0 {
pp = port
}
@@ -268,39 +280,34 @@ func (r *Route) Finalize() {
}
if pp == 0 {
switch {
case r.Scheme == "https":
pp = 443
case !isDocker:
pp = 80
default:
if isDocker {
pp = lowestPort(cont.PrivatePortMapping)
if pp == 0 {
pp = lowestPort(cont.PublicPortMapping)
}
} else if r.Scheme == "https" {
pp = 443
} else {
pp = 80
}
}
if isDocker {
// replace private port with public port if using public IP.
if r.Host == cont.PublicIP {
if r.Host == cont.PublicHostname {
if p, ok := cont.PrivatePortMapping[pp]; ok {
pp = int(p.PublicPort)
if r.Scheme == "" && p.Type == "udp" {
r.Scheme = "udp"
}
}
}
// replace public port with private port if using private IP.
if r.Host == cont.PrivateIP {
} else {
// replace public port with private port if using private IP.
if p, ok := cont.PublicPortMapping[pp]; ok {
pp = int(p.PrivatePort)
}
}
if r.Scheme == "" {
switch {
case r.Host == cont.PublicIP && cont.PublicPortMapping[pp].Type == "udp":
r.Scheme = "udp"
case r.Host == cont.PrivateIP && cont.PrivatePortMapping[pp].Type == "udp":
r.Scheme = "udp"
if r.Scheme == "" && p.Type == "udp" {
r.Scheme = "udp"
}
}
}
}
@@ -322,13 +329,10 @@ func (r *Route) Finalize() {
r.HealthCheck = health.DefaultHealthConfig
}
// set or keep at least default
if !r.HealthCheck.Disable {
if r.HealthCheck.Interval == 0 {
r.HealthCheck.Interval = common.HealthCheckIntervalDefault
}
if r.HealthCheck.Timeout == 0 {
r.HealthCheck.Timeout = common.HealthCheckTimeoutDefault
}
r.HealthCheck.Interval |= common.HealthCheckIntervalDefault
r.HealthCheck.Timeout |= common.HealthCheckTimeoutDefault
}
if isDocker && cont.IdleTimeout != "" {

View File

@@ -125,7 +125,11 @@ func HomepageConfig(useDefaultCategories bool, categoryFilter, providerFilter st
if item.Category == "" {
item.Category = "Docker"
}
item.SourceType = string(provider.ProviderTypeDocker)
if r.IsAgent() {
item.SourceType = string(provider.ProviderTypeAgent)
} else {
item.SourceType = string(provider.ProviderTypeDocker)
}
case r.UseLoadBalance():
if item.Category == "" {
item.Category = "Load-balanced"

View File

@@ -164,7 +164,7 @@ var commands = map[string]struct {
if target.Scheme == "" {
target.Scheme = "http"
}
rp := reverseproxy.NewReverseProxy("", target, gphttp.DefaultTransport)
rp := reverseproxy.NewReverseProxy("", target, gphttp.NewTransport())
return ReturningCommand(rp.ServeHTTP)
},
},

View File

@@ -234,7 +234,8 @@ func TestOnCorrectness(t *testing.T) {
tests = append(tests, genCorrectnessTestCases("header", func(k, v string) *http.Request {
return &http.Request{
Header: http.Header{k: []string{v}}}
Header: http.Header{k: []string{v}},
}
})...)
tests = append(tests, genCorrectnessTestCases("query", func(k, v string) *http.Request {
return &http.Request{

View File

@@ -3,6 +3,7 @@ package types
import (
"net/http"
"github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/internal/docker"
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
"github.com/yusing/go-proxy/internal/homepage"
@@ -31,7 +32,10 @@ type (
HomepageConfig() *homepage.Item
ContainerInfo() *docker.Container
Agent() *agent.AgentConfig
IsDocker() bool
IsAgent() bool
UseLoadBalance() bool
UseIdleWatcher() bool
UseHealthCheck() bool