Documentation
¶
Overview ¶
Package config provides a Config interface with YAML + environment variable loading. Environment variables take precedence over YAML values. Nested keys use dot notation (e.g., "server.port").
ref: go-micro/go-micro config/config.go — Config interface with Get/Scan pattern Adopted: Get/Scan/Keys interface shape. Deviated: simpler flat-map model with dot-separated keys instead of go-micro's source/reader/value abstraction layers.
Package config provides YAML + environment variable configuration loading for GoCell applications. Environment variables take precedence over YAML values. Nested keys use dot notation (e.g., "server.port").
Example:
cfg, err := config.Load("config.yaml", "APP")
if err != nil { ... }
port := cfg.Get("server.http.port")
For testing, use NewFromMap to create a Config from an existing map:
cfg := config.NewFromMap(map[string]any{"server.port": 8080})
Package config — watcher.go ¶
ref: spf13/viper viper.go — WatchConfig watches the parent directory for atomic saves and renames; fsnotify docs recommend directory-level watch. Adopted: watch filepath.Dir(path), filter by filepath.Base(path). Deviated from go-micro (file-level watch with Rename re-add): directory-level handles atomic replace (rename+create, remove+recreate).
ref: thanos-io/thanos pkg/reloader/reloader.go — debounce with configurable delay interval and timer reset on each event. Adopted: WithDebounce option.
ref: spf13/viper viper.go — filepath.EvalSymlinks for Kubernetes ConfigMap ..data symlink pivot detection. Adopted: checkSymlinkPivot on each event.
ref: kubernetes/kubernetes — generation/observedGeneration pattern for tracking desired vs applied config state. Adopted: ObservedGenerationer on config.
Index ¶
- Constants
- func DeepCloneValue(v any) any
- func Diff(oldData, newData map[string]any) (added, updated, removed []string)
- func HasDrift(cfg Config) bool
- type Config
- type Generationer
- type KeyFilter
- type NoopWatcherCollector
- type ObservedGenerationer
- type Reloader
- type Snapshotter
- type WatchEvent
- type Watcher
- type WatcherCollector
- type WatcherOption
- func WithDebounce(d time.Duration) WatcherOption
- func WithDrainTimeout(d time.Duration) WatcherOption
- func WithKeyFilter(prefixes ...string) WatcherOption
- func WithMaxDebounce(d time.Duration) WatcherOption
- func WithMetrics(m WatcherCollector) WatcherOption
- func WithSymlinkPollInterval(d time.Duration) WatcherOption
Constants ¶
const ( EventTypeWrite = "write" EventTypeCreate = "create" EventTypeSymlinkPivot = "symlink_pivot" )
Event type constants for WatcherCollector.RecordEvent.
Variables ¶
This section is empty.
Functions ¶
func DeepCloneValue ¶
DeepCloneValue recursively deep-copies a config value. It handles the types produced by YAML unmarshalling: map[string]any, []any, and primitives (string, int, float64, bool) which are immutable and returned as-is.
func Diff ¶
Diff computes the difference between two flat config maps. It returns keys that were added, updated (value changed), or removed. Returned slices are sorted for deterministic output.
ref: micro/go-micro config/watcher.go — checksum-based change dedup Adopted: explicit key-set diff for deterministic change detection.
Types ¶
type Config ¶
type Config interface {
// Get returns the value for the given dot-separated key, or nil if absent.
Get(key string) any
// Scan unmarshals the entire configuration into dest.
Scan(dest any) error
// Keys returns all available configuration keys sorted alphabetically.
Keys() []string
}
Config provides read access to configuration values.
func Load ¶
Load reads a YAML file and overlays environment variable overrides. Environment variables override YAML values. The env prefix maps to nested keys by replacing underscores with dots and lowering the case. For example, APP_SERVER_PORT overrides the key "app.server.port".
func NewFromMap ¶
NewFromMap creates a Config from an existing map (useful for testing).
type Generationer ¶
type Generationer interface {
Generation() int64
}
Generationer is an optional interface for configs that track a monotonically increasing reload generation. Generation starts at 0 (initial load) and increments by 1 on each successful Reload. Failed reloads do not increment.
type KeyFilter ¶
type KeyFilter struct {
// contains filtered or unexported fields
}
KeyFilter checks whether any of a set of changed keys matches registered prefixes. This is a utility type for bootstrap-level filtering — the watcher itself does not apply key filtering (it watches files, not config keys).
func NewKeyFilter ¶
NewKeyFilter creates a KeyFilter for the given key prefixes.
type NoopWatcherCollector ¶
type NoopWatcherCollector struct{}
NoopWatcherCollector is a no-op implementation used when metrics are disabled. All methods are safe for concurrent use.
func (NoopWatcherCollector) RecordDebounceCoalesced ¶
func (NoopWatcherCollector) RecordDebounceCoalesced()
func (NoopWatcherCollector) RecordEvent ¶
func (NoopWatcherCollector) RecordEvent(string)
func (NoopWatcherCollector) RecordLastEventTimestamp ¶
func (NoopWatcherCollector) RecordLastEventTimestamp(time.Time)
type ObservedGenerationer ¶
ObservedGenerationer is an optional interface for configs that track the last generation successfully applied by all consumers. The gap between Generation() and ObservedGeneration() indicates configuration drift (desired vs applied).
ref: kubernetes/kubernetes — generation/observedGeneration pattern: metadata.Generation = desired state version (bumped on spec change), status.ObservedGeneration = last version the controller reconciled.
type Reloader ¶
Reloader is an optional interface for configs that support hot-reloading. The concrete *config returned by Load implements this; NewFromMap does not.
type Snapshotter ¶
Snapshotter is an optional interface for configs that support atomic point-in-time snapshots. The concrete *config returned by Load implements this. Snapshot holds the read lock for the entire copy, ensuring the returned map is a consistent view — unlike iterating Keys()+Get() which acquires/releases the lock per call.
type WatchEvent ¶
type WatchEvent struct {
Path string // Path of the changed file (original, not resolved).
SymlinkPivot bool // True if triggered by a symlink target change.
}
WatchEvent carries information about a file change detected by the Watcher.
type Watcher ¶
type Watcher struct {
// contains filtered or unexported fields
}
Watcher monitors a file for changes by watching its parent directory. This correctly handles atomic replace (rename+create) and remove+recreate, where file-level inotify/kqueue watches would silently break due to inode rebinding.
The watcher detects Kubernetes ConfigMap updates via ..data symlink pivot (see WithDebounce for coalescing rapid events). Note: symlink pivot is a detection mechanism, not a security boundary. The watcher does not validate that the resolved symlink target stays within a trusted directory — any process with write access to the watched directory can redirect config content. Deploy-time filesystem permissions are the trust boundary.
func NewWatcher ¶
NewWatcher creates a Watcher for the given file path. The watcher monitors the parent directory and filters events for the target filename. The watcher does not start until Start is called.
func (*Watcher) Close ¶
Close stops the watcher and releases resources, bounded by ctx.
Phase 1: signals the internal done channel (stops the fsnotify loop and debounce goroutines), then waits for in-flight callbacks bounded by ctx. If ctx expires before all callbacks finish, Close returns ctx.Err() but still closes the underlying fsnotify.Watcher (OS file descriptor leak prevention takes priority over the caller's budget).
Phase 2: closes the underlying fsnotify.Watcher unconditionally.
Close is idempotent: concurrent and repeated calls are safe.
ref: uber-go/fx app.go StopTimeout — ctx as shared shutdown budget. ref: uber-go/fx lifecycle OnStop(ctx) — ContextCloser pattern.
func (*Watcher) Health ¶
Health reports watcher readiness for bootstrap /readyz integration. A watcher is healthy only after its event loop has started and before it has been closed.
func (*Watcher) KeyFilters ¶
KeyFilters returns the key prefixes registered via WithKeyFilter (sorted).
func (*Watcher) OnChange ¶
func (w *Watcher) OnChange(fn func(WatchEvent))
OnChange registers a callback that fires when the watched file changes.
func (*Watcher) Ready ¶
func (w *Watcher) Ready() <-chan struct{}
Ready returns a channel that is closed when the event loop has started and is ready to process file-system events. Useful in tests to avoid time.Sleep.
func (*Watcher) Start ¶
func (w *Watcher) Start()
Start begins watching for file changes in a goroutine. It blocks until Close is called or an unrecoverable error occurs.
func (*Watcher) StartWithContext ¶
StartWithContext begins watching using the provided context. When ctx is canceled, the watcher is closed automatically. This allows the watcher to be tied to a parent shutdown context.
type WatcherCollector ¶
type WatcherCollector interface {
// RecordEvent records a watcher event by type ("write", "create", "symlink_pivot").
RecordEvent(eventType string)
// RecordLastEventTimestamp records the time of the most recent event.
RecordLastEventTimestamp(t time.Time)
// RecordDebounceCoalesced increments the count of events absorbed by debounce.
RecordDebounceCoalesced()
}
WatcherCollector records watcher operational metrics. Implementations must be safe for concurrent use.
ref: prometheus/prometheus cmd/prometheus/main.go — 4-metric baseline: reload total, success gauge, last timestamp, event count.
type WatcherOption ¶
type WatcherOption func(*watcherConfig)
WatcherOption configures a Watcher. Pass to NewWatcher.
func WithDebounce ¶
func WithDebounce(d time.Duration) WatcherOption
WithDebounce sets the debounce interval for coalescing rapid file events. Events are held for this duration; if another event arrives, the timer resets. Set to 0 to fire callbacks immediately (no debounce).
func WithDrainTimeout ¶
func WithDrainTimeout(d time.Duration) WatcherOption
WithDrainTimeout sets how long Close waits for in-flight callbacks to finish. After this timeout, Close proceeds even if callbacks are still running.
func WithKeyFilter ¶
func WithKeyFilter(prefixes ...string) WatcherOption
WithKeyFilter stores key prefixes on the watcher. Bootstrap can read these via KeyFilters() to decide which cells to notify after a config reload. The watcher itself does not apply key-level filtering (it watches files, not config keys). Calling this option multiple times replaces previous prefixes (last-writer-wins).
Note: bootstrap does not yet consume key filters in its reload fanout. This is a building block for selective cell notification (planned: #11 OPS-5).
func WithMaxDebounce ¶
func WithMaxDebounce(d time.Duration) WatcherOption
WithMaxDebounce sets the maximum time events can be deferred. Even if events keep arriving, callbacks fire after this ceiling. Prevents infinite deferral. Only meaningful when debounce > 0.
func WithMetrics ¶
func WithMetrics(m WatcherCollector) WatcherOption
WithMetrics injects a WatcherCollector for recording operational metrics. Bootstrap does not yet pass a collector; inject via a custom configWatcherFactory or wait for bootstrap option wiring (#11 OPS-5).
func WithSymlinkPollInterval ¶
func WithSymlinkPollInterval(d time.Duration) WatcherOption
WithSymlinkPollInterval sets the polling interval used to detect symlink pivot changes. kqueue on macOS (fsnotify ≥ v1.9.0) does not reliably deliver Create/Remove events for symlinks inside a watched directory, so a periodic EvalSymlinks poll is used as a fallback. Set to 0 to disable polling.