Files
godoxy/internal/route/provider

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, #2 for 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_total by provider
  • provider_events_total by 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