mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-27 02:38:33 +02:00
docs: add per package README for implementation details (AI generated with human review)
This commit is contained in:
332
internal/watcher/README.md
Normal file
332
internal/watcher/README.md
Normal file
@@ -0,0 +1,332 @@
|
||||
# Watcher
|
||||
|
||||
Provides file and Docker event watching capabilities for GoDoxy, enabling dynamic configuration updates.
|
||||
|
||||
## Overview
|
||||
|
||||
The watcher package implements event watching systems for detecting changes in configuration files and Docker containers. It provides a unified interface for event streaming that allows GoDoxy to react to infrastructure changes in real-time.
|
||||
|
||||
### Primary Consumers
|
||||
|
||||
- `internal/route` - Route provider reloads configuration on container events
|
||||
- `internal/config` - Configuration file monitoring
|
||||
- `internal/idlewatcher` - Container idle state detection
|
||||
|
||||
### Non-goals
|
||||
|
||||
- Does not provide a general-purpose filesystem watcher beyond configuration files
|
||||
- Does not implement container orchestration or management operations
|
||||
- Does not persist events beyond in-memory buffering
|
||||
|
||||
### Stability
|
||||
|
||||
Internal package. Public interfaces are stable, but implementation details may change.
|
||||
|
||||
## Public API
|
||||
|
||||
### Exported Types
|
||||
|
||||
#### Event Type
|
||||
|
||||
```go
|
||||
type Event = events.Event
|
||||
```
|
||||
|
||||
Alias to `events.Event` for convenience.
|
||||
|
||||
#### Watcher Interface
|
||||
|
||||
```go
|
||||
type Watcher interface {
|
||||
// Events returns channels for receiving events and errors.
|
||||
// The channels are closed when the context is cancelled.
|
||||
Events(ctx context.Context) (<-chan Event, <-chan gperr.Error)
|
||||
}
|
||||
```
|
||||
|
||||
Core interface that all watchers implement. Callers receive:
|
||||
|
||||
- `<-chan Event` - Events as they occur
|
||||
- `<-chan gperr.Error` - Errors during event watching
|
||||
|
||||
### Docker Watcher
|
||||
|
||||
#### Constructor
|
||||
|
||||
```go
|
||||
func NewDockerWatcher(dockerCfg types.DockerProviderConfig) DockerWatcher
|
||||
```
|
||||
|
||||
Creates a Docker watcher for the given Docker configuration.
|
||||
|
||||
#### Event Streaming
|
||||
|
||||
```go
|
||||
func (w DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan gperr.Error)
|
||||
func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerListOptions) (<-chan Event, <-chan gperr.Error)
|
||||
```
|
||||
|
||||
Returns event and error channels. `Events` uses default filters; `EventsWithOptions` allows custom filters.
|
||||
|
||||
#### Predefined Filters
|
||||
|
||||
```go
|
||||
var (
|
||||
DockerFilterContainer = NewDockerFilter("type", string(dockerEvents.ContainerEventType))
|
||||
DockerFilterStart = NewDockerFilter("event", string(dockerEvents.ActionStart))
|
||||
DockerFilterStop = NewDockerFilter("event", string(dockerEvents.ActionStop))
|
||||
DockerFilterDie = NewDockerFilter("event", string(dockerEvents.ActionDie))
|
||||
DockerFilterDestroy = NewDockerFilter("event", string(dockerEvents.ActionDestroy))
|
||||
DockerFilterKill = NewDockerFilter("event", string(dockerEvents.ActionKill))
|
||||
DockerFilterPause = NewDockerFilter("event", string(dockerEvents.ActionPause))
|
||||
DockerFilterUnpause = NewDockerFilter("event", string(dockerEvents.ActionUnPause))
|
||||
)
|
||||
```
|
||||
|
||||
Common filters for Docker event watching.
|
||||
|
||||
#### Filter Builder
|
||||
|
||||
```go
|
||||
func NewDockerFilter(term string, values ...string) DockerFilter
|
||||
func NewDockerFilters(filters ...DockerFilter) client.Filters
|
||||
func DockerFilterContainerNameID(nameOrID string) DockerFilter
|
||||
```
|
||||
|
||||
Create custom event filters.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Watcher Interface] --> B[DockerWatcher]
|
||||
A --> C[fileWatcher]
|
||||
A --> D[ConfigFileWatcher]
|
||||
A --> E[DirectoryWatcher]
|
||||
|
||||
B --> F[Docker Client]
|
||||
G[events.EventQueue] --> H[Event Consumers]
|
||||
```
|
||||
|
||||
| Component | Responsibility |
|
||||
| ------------------- | ------------------------------------------------------ |
|
||||
| `Watcher` | Interface defining event streaming contract |
|
||||
| `DockerWatcher` | Connects to Docker daemon and streams container events |
|
||||
| `fileWatcher` | Watches individual files for changes |
|
||||
| `ConfigFileWatcher` | Watches configuration files for reloads |
|
||||
| `DirectoryWatcher` | Watches directories for file changes |
|
||||
|
||||
### Event Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Docker as Docker Daemon
|
||||
participant Watcher as DockerWatcher
|
||||
participant Queue as EventQueue
|
||||
participant Consumer as Route Provider
|
||||
|
||||
Docker->>Watcher: Container Start Event
|
||||
Watcher->>Watcher: Parse and normalize
|
||||
Watcher->>Queue: eventCh <- Event
|
||||
Queue->>Consumer: onFlush(events)
|
||||
Consumer->>Consumer: Update routes
|
||||
```
|
||||
|
||||
### Docker Event Processing
|
||||
|
||||
Events are parsed and mapped to watcher event types:
|
||||
|
||||
```go
|
||||
func (w DockerWatcher) handleEvent(event dockerEvents.Message, ch chan<- Event) {
|
||||
action, ok := events.DockerEventMap[event.Action]
|
||||
if !ok {
|
||||
return // Unknown action, ignore
|
||||
}
|
||||
ch <- Event{
|
||||
Type: events.EventTypeDocker,
|
||||
ActorID: event.Actor.ID,
|
||||
ActorAttributes: event.Actor.Attributes,
|
||||
ActorName: event.Actor.Attributes["name"],
|
||||
Action: action,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling and Recovery
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Connected
|
||||
Connected --> Error: Connection failed
|
||||
Error --> Reconnecting: Log error
|
||||
Error --> [*]: Context cancelled
|
||||
Reconnecting --> Connected: After 3s retry
|
||||
Reconnecting --> [*]: Context cancelled
|
||||
Connected --> [*]: Context cancelled
|
||||
```
|
||||
|
||||
On connection errors:
|
||||
|
||||
1. Error is emitted to error channel
|
||||
1. Force reload event is sent to clear routes
|
||||
1. 3-second retry interval begins
|
||||
1. On success, force reload triggers route reload
|
||||
1. Event channel is reopened
|
||||
|
||||
## Configuration Surface
|
||||
|
||||
Docker watcher is configured via `types.DockerProviderConfig`:
|
||||
|
||||
- `Host` - Docker daemon socket/URL
|
||||
- `Timeout` - Connection timeout
|
||||
|
||||
## Dependency and Integration Map
|
||||
|
||||
### Internal Dependencies
|
||||
|
||||
| Package | Purpose |
|
||||
| -------------------------------- | -------------------------------- |
|
||||
| `internal/docker` | Docker client management |
|
||||
| `internal/watcher/events` | Event type definitions and queue |
|
||||
| `internal/types` | Configuration types |
|
||||
| `github.com/yusing/goutils/task` | Lifetime management |
|
||||
|
||||
### External Dependencies
|
||||
|
||||
| Dependency | Purpose |
|
||||
| ----------------------- | --------------------------- |
|
||||
| `github.com/moby/moby` | Docker API types and client |
|
||||
| `github.com/rs/zerolog` | Structured logging |
|
||||
|
||||
### Integration Points
|
||||
|
||||
- Events channel feeds into `EventQueue` for buffering
|
||||
- Route provider subscribes to events for configuration reloads
|
||||
|
||||
## Observability
|
||||
|
||||
### Logs
|
||||
|
||||
| Level | Message Pattern | When |
|
||||
| ----- | ------------------------------------------- | ----------------------- |
|
||||
| Debug | Docker watcher opened/closed | Connection lifecycle |
|
||||
| Error | docker watcher: failed to initialize client | Client creation failure |
|
||||
| Debug | docker watcher: connection failed | Reconnection attempts |
|
||||
|
||||
### Metrics
|
||||
|
||||
None exposed directly. Polling metrics are tracked by the period package for uptime monitoring.
|
||||
|
||||
## Failure Modes and Recovery
|
||||
|
||||
| Failure | Detection | Recovery |
|
||||
| ------------------------- | -------------------------------- | ------------------------ |
|
||||
| Docker daemon unavailable | `client.IsErrConnectionFailed` | Auto-retry every 3s |
|
||||
| Context cancellation | `<-ctx.Done()` | Graceful shutdown |
|
||||
| Unknown event actions | Missing in `DockerEventMap` | Event ignored (no error) |
|
||||
| Channel blocked writing | 3-second timeout on notification | Event dropped, continue |
|
||||
|
||||
### Panic Recovery
|
||||
|
||||
The Docker watcher does not recover from panics in the event loop. Panic will terminate the process.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Docker Event Watching
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"github.com/yusing/godoxy/internal/watcher"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
|
||||
func watchContainers(ctx context.Context, dockerCfg types.DockerProviderConfig) error {
|
||||
dw := watcher.NewDockerWatcher(dockerCfg)
|
||||
eventCh, errCh := dw.Events(ctx)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case event := <-eventCh:
|
||||
handleContainerEvent(event)
|
||||
case err := <-errCh:
|
||||
log.Error().Err(err).Msg("docker watcher error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleContainerEvent(event watcher.Event) {
|
||||
switch event.Action {
|
||||
case events.ActionContainerStart:
|
||||
log.Info().Str("container", event.ActorName).Msg("Container started")
|
||||
case events.ActionContainerStop, events.ActionContainerDie:
|
||||
log.Info().Str("container", event.ActorName).Msg("Container stopped")
|
||||
case events.ActionContainerDestroy:
|
||||
log.Info().Str("container", event.ActorName).Msg("Container destroyed")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Event Filters
|
||||
|
||||
```go
|
||||
import "github.com/yusing/godoxy/internal/watcher"
|
||||
|
||||
options := watcher.DockerListOptions{
|
||||
Filters: watcher.NewDockerFilters(
|
||||
watcher.DockerFilterContainer,
|
||||
watcher.DockerFilterStart,
|
||||
watcher.DockerFilterContainerNameID("my-app"),
|
||||
),
|
||||
}
|
||||
|
||||
dw := watcher.NewDockerWatcher(cfg)
|
||||
eventCh, errCh := dw.EventsWithOptions(ctx, options)
|
||||
```
|
||||
|
||||
### Integration with Event Queue
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/yusing/godoxy/internal/watcher"
|
||||
"github.com/yusing/godoxy/internal/watcher/events"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
func watchWithQueue(ctx context.Context) {
|
||||
dw := watcher.NewDockerWatcher(cfg)
|
||||
eventCh, errCh := dw.Events(ctx)
|
||||
|
||||
queue := events.NewEventQueue(
|
||||
task.Subtask("event-flush"),
|
||||
5*time.Second,
|
||||
func(batch []events.Event) {
|
||||
// Process batch of events
|
||||
for _, e := range batch {
|
||||
log.Info().Str("event", e.String()).Msg("event batch")
|
||||
}
|
||||
},
|
||||
func(err gperr.Error) {
|
||||
log.Error().Err(err).Msg("event error")
|
||||
},
|
||||
)
|
||||
|
||||
queue.Start(eventCh, errCh)
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Notes
|
||||
|
||||
- Mock Docker client via `internal/docker` package
|
||||
- Event queue can be tested with synthetic events
|
||||
- Connection failures tested via `client.IsErrConnectionFailed` simulation
|
||||
|
||||
## Related Packages
|
||||
|
||||
- `internal/watcher/events` - Event definitions and queuing
|
||||
- `internal/docker` - Docker client management
|
||||
- `internal/route/routes` - Route management
|
||||
Reference in New Issue
Block a user