mirror of
https://github.com/yusing/godoxy.git
synced 2026-02-19 15:27:46 +01:00
Route Provider
Discovers and loads routes from Docker containers, YAML files, and remote agents.
Overview
The internal/route/provider package implements route discovery and loading for GoDoxy. It supports multiple provider types (Docker, File, Agent) and manages route lifecycle including validation, start/stop, and event handling.
Primary Consumers
- Main entry point: Creates providers during startup
- Route layer: Provides route instances to registry
- Watcher system: Receives container/config change events
Non-goals
- Does not implement route execution (handled by route package)
- Does not manage global configuration
- Does not provide authentication
Stability
Internal package with stable provider interface.
Public API
Exported Types
type Provider struct {
ProviderImpl
t provider.Type
routes route.Routes
routesMu sync.RWMutex
watcher W.Watcher
}
type ProviderImpl interface {
fmt.Stringer
ShortName() string
IsExplicitOnly() bool
loadRoutesImpl() (route.Routes, error)
NewWatcher() W.Watcher
Logger() *zerolog.Logger
}
Exported Functions
// Create a file-based provider
func NewFileProvider(filename string) (p *Provider, err error)
// Create a Docker-based provider
func NewDockerProvider(name string, dockerCfg types.DockerProviderConfig) *Provider
// Create an agent-based provider
func NewAgentProvider(cfg *agent.AgentConfig) *Provider
Provider Methods
func (p *Provider) GetType() provider.Type
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)
Architecture
Core Components
classDiagram
class Provider {
+ProviderImpl
+t provider.Type
+routes route.Routes
+watcher W.Watcher
+Start(parent) error
+LoadRoutes() error
+IterRoutes(yield)
}
class ProviderImpl {
<<interface>>
+String() string
+ShortName() string
+IsExplicitOnly() bool
+loadRoutesImpl() (route.Routes, error)
+NewWatcher() W.Watcher
+Logger() *zerolog.Logger
}
class DockerProviderImpl {
+name string
+dockerCfg types.DockerProviderConfig
+ShortName() string
+loadRoutesImpl() (route.Routes, error)
}
class FileProviderImpl {
+filename string
+ShortName() string
+loadRoutesImpl() (route.Routes, error)
}
class AgentProviderImpl {
+*agent.AgentConfig
+docker DockerProviderImpl
+ShortName() string
+loadRoutesImpl() (route.Routes, error)
}
Provider --> ProviderImpl : wraps
ProviderImpl <|-- DockerProviderImpl
ProviderImpl <|-- FileProviderImpl
ProviderImpl <|-- AgentProviderImpl
Provider Types
graph TD
A[Provider] --> B{Docker}
A --> C{File}
A --> D{Agent}
B --> E[DockerWatcher]
C --> F[ConfigFileWatcher]
D --> G[DockerWatcher]
E --> H[Container Labels]
F --> I[YAML Files]
G --> J[Remote Agent]
Route Loading Flow
sequenceDiagram
participant Main as Application
participant Provider as Provider
participant Impl as ProviderImpl
participant Watcher as Watcher
participant Routes as Route Registry
Main->>Provider: Start()
Provider->>Provider: LoadRoutes()
Provider->>Impl: loadRoutesImpl()
Impl-->>Provider: Routes
Provider->>Routes: Validate & Add Routes
loop Event Loop
Watcher->>Provider: Container/Config Changed
Provider->>Provider: Reload Routes
Provider->>Impl: loadRoutesImpl()
Provider->>Routes: Update Registry
end
Docker Provider Features
- Lists all containers and reads their labels
- Supports Docker Compose project/service discovery
- Handles host network mode with port detection
- Supports alias references (
#1,#2for container aliases) - Explicit-only mode for providers ending with
!
File Provider Features
- Reads YAML files from the config directory
- Validates routes on load
- Supports embedded presets
Agent Provider Features
- Connects to a remote agent via Unix socket or TCP
- Delegates to a Docker provider internally
- Supports the same Docker label-based route discovery
Configuration Surface
Docker Provider Labels
labels:
proxy.aliases: app1,app2
proxy.app1.listen: http://0.0.0.0:8080
proxy.app1.target: http://app:3000
proxy.app1.rules: |
- name: default
do: pass
File Provider Configuration
# config/routes/myapp.yml
routes:
myapp:
target: http://localhost:8080
rules:
- name: default
do: pass
Agent Provider Configuration
agents:
remote:
socket: /run/godoxy-agent.sock
name: remote-agent
Dependency and Integration Map
| Dependency | Purpose |
|---|---|
internal/route |
Route types and validation |
internal/route/routes |
Route registry |
internal/docker |
Docker API integration |
internal/serialization |
YAML parsing |
internal/watcher |
Container/config watching |
internal/watcher/events |
Event queue handling |
agent/pkg/agent |
Agent configuration |
github.com/yusing/goutils/errs |
Error handling |
Observability
Logs
- INFO: Provider start/stop, route count
- DEBUG: Route loading details, label parsing
- ERROR: Container fetch errors, parse failures
Log context includes: provider, alias, route_count
Metrics
routes_loaded_totalby providerprovider_events_totalby type
Security Considerations
- Docker provider requires socket access
- Agent provider uses Unix socket or TCP with auth
- Route validation prevents SSRF via URL validation
- Container labels are validated before use
Failure Modes and Recovery
| Failure | Behavior | Recovery |
|---|---|---|
| Docker socket unavailable | Provider fails to start | Fix socket permissions |
| Container not found | Route excluded with error | Verify container exists |
| YAML parse error | Route excluded, error logged | Fix configuration file |
| Agent connection lost | Routes removed, reconnection | Fix agent connectivity |
| Watcher error | Provider finishes with error | Check watcher logs |
Usage Examples
Creating a Docker Provider
provider := provider.NewDockerProvider("default", types.DockerProviderConfig{
URL: "unix:///var/run/docker.sock",
})
if err := provider.LoadRoutes(); err != nil {
return err
}
if err := provider.Start(parentTask); err != nil {
return err
}
Creating a File Provider
provider, err := provider.NewFileProvider("routes/myapp.yml")
if err != nil {
return err
}
if err := provider.Start(parentTask); err != nil {
return err
}
Iterating Over Routes
for alias, r := range provider.IterRoutes {
log.Printf("Route: %s -> %s", alias, r.Name())
}
Finding a Service
route, ok := provider.FindService("myproject", "myservice")
if ok {
log.Printf("Found service: %s", route.Name())
}
Testing Notes
- Docker provider tests use test containers
- File provider tests use temp directories
- Agent provider tests use mock agents
- Integration tests cover event handling