Documentation
¶
Overview ¶
Package fastconf provides a strongly typed, lock-free, Kustomize-style configuration loader built for Go 1.22+.
Start here ¶
A typical application reads FastConf in this order:
- Build a Manager[T] with New (or MustNew for one-line main / init).
- Read the live typed snapshot with Manager.Get.
- React to successful commits with Subscribe and failed reloads with Manager.Errors.
- Preview a future commit with Manager.Plan before calling Manager.Reload.
- Inspect provenance through Manager.Snapshot and recover retained states through Manager.Replay when WithHistory was enabled.
The package examples mirror that path: ExampleNew, ExampleMustNew, ExampleSubscribe, ExampleManager_Errors, ExampleManager_Plan, and ExampleReplay_Rollback.
Core ideas ¶
- Manager[T] takes the business config struct T as a type parameter; the hot read path returns *T with no reflection or allocations.
- State[T] is published through atomic.Pointer: one serialized writer, many lock-free readers.
- A reload first assembles file, generator, and provider layers, then runs the canonical stages Merge → Migration → Transform → Secret → TypedHooks → Decode → FieldMeta → Validate → Policy before atomically publishing. Any failure preserves the previous *State[T].
Reading by need ¶
- Constructors: New, MustNew.
- Preset bundles: PresetK8s, PresetSidecar, PresetTesting, PresetHierarchical.
- Loading and overlays: Option, WithProvider, WithProfile, WithWatch, WithCoalesce, WithMultiAxisOverlays, WithDir, WithFS.
- Runtime reaction: Subscribe, Manager.Errors, Manager.Watcher, DiffReporter.
- Inspection and recovery: Manager.Snapshot, State.Introspect, State.Explain, State.Dump, Manager.Plan, Manager.Replay.
- Extension points: Transformer, WithTypedHook, WithSecretResolver, WithValidator, WithPolicy, AuditSink, MetricsSink, Tracer.
Module layout ¶
The main API package lives at the repository root (github.com/fastabc/fastconf). Independent modules with their own go.mod files are:
cmd/fastconfctl, cmd/fastconfgen cue (unified: cue/cuelang + cue/policy) integrations/cli/pflag, integrations/log/phuslu, integrations/log/zerolog observability/metrics/prometheus, observability/otel policy/opa providers/s3 validate/playground
Subpackages that share the root module version include: contracts, integrations/{bus,openfeature,render}, providers/{consul,http,nats,redisstream,vault,k8s}, providers/s3/s3events, pkg/*, policy/ (root), cmd/fastconfd, and cmd/internal/cli.
Key files ¶
manager.go — Manager[T] facade + New + Subscribe + Eval state.go — State[T], ReloadCause, Origins/Explain/Lookup facades options.go — WithXxx option builders aliases.go — codec, secret, field-meta, and replay public facades errors.go — public sentinel errors and ReloadError stream obs.go — metrics, tracer, audit-sink facades defaults.go — WithStructDefaults + DefaulterFunc feature.go — FeatureRule extraction + Eval[T,V] presets.go — PresetK8s, PresetSidecar, PresetTesting registry.go — RegisterProviderFactory + WithProviderByName bind.go — WithSource content-type helpers doc.go — package-level godoc
Index ¶
- Constants
- Variables
- func Bind(src contracts.Source, p contracts.Parser) contracts.Provider
- func DefaultSecretRedactor(path string, value any) any
- func Eval[T any, V any](m *Manager[T], key string, ctx feature.EvalContext, def V) V
- func Extract[T any, M any](s *State[T], extract func(*T) *M) *M
- func FormatDiff(entries []DiffEntry) []string
- func LookupCodec(name string) (contracts.Codec, bool)
- func RegisterCodec(name string, c contracts.Codec)
- func RegisterCodecExt(ext, codec string)
- func RegisterProviderFactory(name string, f ProviderFactory)
- func RegisteredProviderNames() []string
- func SourcePriorityBand(s SourceRef) string
- func Subscribe[T any, M any](m *Manager[T], extract func(*T) *M, fn func(old, new *M)) (cancel func())
- type AuditSink
- type AuditSinkFunc
- type CoalesceOptions
- type CoalesceProfile
- type CodecBridge
- type Defaulter
- type DiffChange
- type DiffEntry
- type DiffEvent
- type DiffReporter
- type DiffReporterFunc
- type DiffReporterMetricsSink
- type DumpFormat
- type EvalContext
- type FeatureRule
- type FieldSpec
- type HierarchicalOpts
- type Introspection
- type JSONAuditSink
- type K8sOpts
- type LayerKind
- type Manager
- func (m *Manager[T]) Close() error
- func (m *Manager[T]) Errors() <-chan ReloadError
- func (m *Manager[T]) Get() *T
- func (m *Manager[T]) Plan() *PlanBuilder[T]
- func (m *Manager[T]) Reload(ctx context.Context, opts ...ReloadOption) error
- func (m *Manager[T]) Replay() *Replay[T]
- func (m *Manager[T]) Snapshot() *State[T]
- func (m *Manager[T]) Watcher() *Watcher[T]
- type MetricsSink
- type MigrationApplier
- type MigrationFunc
- type Option
- func PresetHierarchical(p HierarchicalOpts) Option
- func PresetK8s(p K8sOpts) Option
- func PresetSidecar(p SidecarOpts) Option
- func PresetTesting(p TestingOpts) Option
- func WithAuditSink(sink AuditSink) Option
- func WithCoalesce(c CoalesceOptions) Option
- func WithCodecBridge(b CodecBridge) Option
- func WithDefaults[T any](fn func(*T)) Option
- func WithDiffReporter(r DiffReporter) Option
- func WithDiffReporterQueueCap(n int) Option
- func WithDir(dir string) Option
- func WithDotEnvAuto(prefix string) Option
- func WithFS(f fs.FS) Option
- func WithFeatureRules[T any](extract func(*T) map[string]feature.Rule) Option
- func WithGenerator(g contracts.Generator) Option
- func WithHistory(n int) Option
- func WithLogger(l *slog.Logger) Option
- func WithMergeKeys(keys map[string]string) Option
- func WithMetrics(m MetricsSink) Option
- func WithMigrations(run func(map[string]any) error) Option
- func WithMultiAxisOverlays(axes ...OverlayAxis) Option
- func WithPolicy[T any](p policy.Policy[T]) Option
- func WithProfile(p ProfileOptions) Option
- func WithProvenance(level ProvenanceLevel) Option
- func WithProvider(p contracts.Provider) Option
- func WithProviderByName(name string, cfg map[string]any) Option
- func WithProviderOrdered(ps ...contracts.Provider) Option
- func WithProviderRegistry(r *ProviderRegistry) Option
- func WithRawMapAccess(fn func(root map[string]any)) Option
- func WithSecretRedactor(r SecretRedactor) Option
- func WithSecretResolver(r SecretResolver) Option
- func WithSource(src contracts.Source, p contracts.Parser) Option
- func WithStrict(strict bool) Option
- func WithStructDefaults[T any]() Option
- func WithTracer(t Tracer) Option
- func WithTransformers(t ...Transformer) Option
- func WithTypedHook(h decoder.TypedHook) Option
- func WithValidator[T any](v func(*T) error) Option
- func WithWatch(w WatchOptions) Option
- func WithoutDefaultTypedHooks() Option
- type Origin
- type OriginIndex
- type OverlayAxis
- type PlanBuilder
- type PlanResult
- type PolicyError
- type ProfileOptions
- type ProvenanceLevel
- type ProviderFactory
- type ProviderMetricsSink
- type ProviderRegistry
- type ReloadCause
- type ReloadError
- type ReloadOption
- type RenderMetricsSink
- type Replay
- type SecretRedactor
- type SecretRef
- type SecretResolver
- type SecretResolverFunc
- type SidecarOpts
- type SourceRef
- type Span
- type StageMetricsSink
- type State
- func (s *State[T]) Diff(other *State[T]) []DiffEntry
- func (s *State[T]) Dump(format DumpFormat, redactor SecretRedactor) ([]byte, error)
- func (s *State[T]) Explain(path string) []provenance.Origin
- func (s *State[T]) FeatureRules() map[string]feature.Rule
- func (s *State[T]) Introspect() *Introspection
- func (s *State[T]) Lookup(path string) []provenance.Origin
- func (s *State[T]) LookupStrict(path string) ([]provenance.Origin, error)
- func (s *State[T]) Origins() *provenance.Index
- func (s *State[T]) Redact(redactor SecretRedactor) map[string]any
- func (s *State[T]) Redacted() map[string]any
- type TenantManager
- func (tm *TenantManager[T]) Add(ctx context.Context, id string, opts ...Option) (*Manager[T], error)
- func (tm *TenantManager[T]) Close() error
- func (tm *TenantManager[T]) Get(id string) (*Manager[T], error)
- func (tm *TenantManager[T]) Has(id string) bool
- func (tm *TenantManager[T]) Remove(id string) error
- func (tm *TenantManager[T]) Tenants() []string
- type TestingOpts
- type Tracer
- type Transformer
- type ValidatorReport
- type WatchOptions
- type Watcher
Examples ¶
Constants ¶
const ( // DefaultDir is the configuration root directory used when WithDir is // not supplied. It follows the conf.d convention from /etc/conf.d. DefaultDir = iopts.DefaultDir // DefaultProfileEnv is the environment variable FastConf reads when // neither WithProfile(ProfileOptions{Single}) nor // WithProfile(ProfileOptions{EnvVar}) sets one explicitly. DefaultProfileEnv = iopts.DefaultProfileEnv )
Default configuration values. These constants define the out-of-the-box behaviour of FastConf. All WithXxx options override these values on a per-Manager basis. See the individual option docs for semantics.
const ( DefaultCoalesceQuiet = iopts.DefaultCoalesceQuiet DefaultCoalesceMaxLag = iopts.DefaultCoalesceMaxLag DefaultCoalesceSwapHint = iopts.DefaultCoalesceSwapHint )
Default coalescer windows for the file-system watcher. Events on a single watched parent directory are collapsed into a single reload using these timings. See the internal/coalesce package; runtime overrides go through WithCoalesce(CoalesceOptions{...}).
const ( ProfileK8s = coalesce.ProfileK8s ProfileLocalDev = coalesce.ProfileLocalDev )
CoalesceProfile values mirroring the internal/coalesce constants.
const ( // DumpYAML emits deterministic YAML with map keys sorted // lexicographically. Two snapshots whose merged values are equal // produce byte-identical output, so YAML diffs do not flake. DumpYAML = istate.DumpYAML // DumpJSON emits indented JSON (two-space indent). Use when piping // to jq or another structured-data tool. DumpJSON = istate.DumpJSON // DumpTOML emits canonical TOML via BurntSushi/toml. Top-level // values must be representable as TOML — strings, numbers, bools, // tables, and arrays — or the encoder returns an error. DumpTOML = istate.DumpTOML )
const ( LayerUnknown = istate.LayerUnknown LayerMerge = istate.LayerMerge LayerPatch = istate.LayerPatch LayerProvider = istate.LayerProvider LayerSecret = istate.LayerSecret LayerGenerator = istate.LayerGenerator )
const ( ProvenanceOff = provenance.Off ProvenanceTopLevel = provenance.TopLevel ProvenanceFull = provenance.Full )
const ( DiffAdded = istate.DiffAdded DiffRemoved = istate.DiffRemoved DiffModified = istate.DiffModified )
const DefaultSidecarHistoryCap = iopts.DefaultSidecarHistoryCap
DefaultSidecarHistoryCap is the history ring capacity used by PresetSidecar when SidecarOpts.HistoryN is not set.
Variables ¶
var ( ErrNoSources = fcerr.ErrNoSources ErrValidation = fcerr.ErrValidation ErrDecode = fcerr.ErrDecode ErrMerge = fcerr.ErrMerge ErrPatch = fcerr.ErrPatch ErrClosed = fcerr.ErrClosed ErrValidator = fcerr.ErrValidator ErrTransform = fcerr.ErrTransform ErrNoOrigin = fcerr.ErrNoOrigin )
var ErrFastConf = fcerr.ErrFastConf
var ErrHistoryDisabled = imanager.ErrHistoryDisabled
var ErrParserUnknown = errors.New("fastconf: no parser for source content-type")
ErrParserUnknown is returned by a bound Source/Parser composite when no Parser was supplied to Bind and the Source's content-type hint did not match any registered Parser. The error is observed at the first Load() call, not at Bind time, because content-types may be runtime-discovered (e.g. HTTP Content-Type header).
var ErrPolicyDenied = fcerr.ErrPolicyDenied
var ErrTenantExists = itenant.ErrTenantExists
var ErrUnknownGeneration = imanager.ErrUnknownGeneration
var ErrUnknownTenant = itenant.ErrUnknownTenant
Functions ¶
func Bind ¶ added in v0.16.0
Bind composes a byte-stream Source with a Parser into a contracts.Provider that the reload pipeline can consume. The returned Provider forwards Name/Priority/Watch to the Source and runs Source.Read + Parser.Decode on Load.
If parser is nil, the framework attempts to resolve a Parser from the parser registry using the content-type hint returned by Source.Read. The lookup is deferred to Load() so that Sources whose content-type is only known at runtime (HTTP Content-Type response header, magic-byte detection, ...) work without ceremony. If neither an explicit Parser nor a registry match resolves a Parser by Load time, Load returns ErrParserUnknown.
func DefaultSecretRedactor ¶
func Extract ¶ added in v0.18.0
Extract returns the sub-tree of s.Value selected by extract. It is the one-shot, type-safe counterpart to Subscribe:
- Extract is a synchronous view of the current snapshot.
- Subscribe streams (oldView, newView) pairs to a callback after every commit and returns a cancel func.
Extract is nil-safe: when any of s, s.Value, or extract is nil it returns the zero value of *M (nil) without invoking the extractor.
dbView := fastconf.Extract(mgr.Snapshot(), func(c *Cfg) *DBSection {
return &c.Database
})
func FormatDiff ¶ added in v0.18.0
FormatDiff renders a DiffEntry sequence as the human-readable line list earlier FastConf revisions returned from State.Diff. The output format is intended for operator-facing surfaces (logs, fastconfctl, PR-bot summaries) and is not covered by the SemVer contract — machine consumers should walk DiffEntry fields directly.
func RegisterCodec ¶
func RegisterCodecExt ¶
func RegisterCodecExt(ext, codec string)
func RegisterProviderFactory ¶
func RegisterProviderFactory(name string, f ProviderFactory)
func RegisteredProviderNames ¶
func RegisteredProviderNames() []string
func SourcePriorityBand ¶ added in v0.18.0
SourcePriorityBand translates a SourceRef's framework-internal Priority into a human-readable band label suitable for audit sinks, fastconfctl explain, or other operator-facing surfaces.
The returned label has one of these shapes:
"file:base" base file layer (1000–1999) "file:overlay:<prof>" single/multi-axis (2000–3999) "generator:<name>" generator (7000 + RawLayer.Priority; default 7070) "provider:<name>" provider (8000 + Provider.Priority()) "unknown:<n>" any value not in a known band
func Subscribe ¶
func Subscribe[T any, M any](m *Manager[T], extract func(*T) *M, fn func(old, new *M)) (cancel func())
Example ¶
ExampleSubscribe demonstrates reacting to a typed subtree after a successful commit while keeping the caller in charge of what counts as "changed".
package main
import (
"context"
"fmt"
"testing/fstest"
"github.com/fastabc/fastconf"
)
type apiExampleConfig struct {
Server struct {
Addr string `json:"addr" yaml:"addr"`
} `json:"server" yaml:"server"`
}
func main() {
mgr, err := fastconf.New[apiExampleConfig](context.Background(),
fastconf.PresetTesting(fastconf.TestingOpts{
FS: fstest.MapFS{
"conf.d/base/00-app.yaml": &fstest.MapFile{
Data: []byte("server:\n addr: \":8080\"\n"),
},
},
}),
)
if err != nil {
fmt.Println(err)
return
}
defer mgr.Close()
cancel := fastconf.Subscribe(mgr,
func(c *apiExampleConfig) *string { return &c.Server.Addr },
func(old, next *string) {
if old != nil && next != nil && *old != *next {
fmt.Printf("%s -> %s\n", *old, *next)
}
},
)
defer cancel()
_ = mgr.Reload(context.Background(), fastconf.WithSourceOverride(map[string]any{
"server": map[string]any{"addr": ":9090"},
}))
}
Output: :8080 -> :9090
Types ¶
type AuditSinkFunc ¶
type AuditSinkFunc = iobs.AuditSinkFunc
type CoalesceOptions ¶ added in v0.18.0
type CoalesceOptions struct {
// Quiet is the no-event silence window after which a burst of
// fsnotify events is delivered as a single reload.
Quiet time.Duration
// MaxLag is the upper bound on how long a reload may be deferred
// regardless of Quiet — protects against pathological streams.
MaxLag time.Duration
// SwapHint accelerates the ConfigMap atomic-rename detection so
// Kubernetes deployments do not need to wait the full Quiet window
// to publish.
SwapHint time.Duration
}
CoalesceOptions tunes the file-watcher event coalescer. All three fields are optional — zero means "use the default for that field". See DefaultCoalesceQuiet / DefaultCoalesceMaxLag / DefaultCoalesceSwapHint.
type CoalesceProfile ¶ added in v0.16.0
CoalesceProfile is the public re-export of the coalescer preset selector. Use ProfileK8s in production and ProfileLocalDev when iterating against editors that write via unlink-rename cascades.
type CodecBridge ¶ added in v0.18.0
type CodecBridge uint8
CodecBridge selects the bytes-to-struct decoder used in the typed pipeline stage. BridgeJSON (default) round-trips through encoding/json so canonical-hash caching can reuse the marshalled bytes; BridgeYAML honours yaml struct tags directly. See WithCodecBridge for the user trap when *T has only yaml tags.
const ( BridgeJSON CodecBridge = iota BridgeYAML )
type Defaulter ¶
type Defaulter interface {
Defaults()
}
Defaulter is an optional interface for strongly-typed config structs. When *T implements Defaulter, FastConf calls Defaults() once per reload AFTER decoding the merged map into *T and AFTER applying struct-tag defaults (WithStructDefaults), but BEFORE running validators. This allows computed defaults, path normalization, and any logic that cannot be expressed in struct tags.
Example:
type AppConfig struct { Port int; DataDir string }
func (c *AppConfig) Defaults() {
if c.Port == 0 { c.Port = 8080 }
if c.DataDir == "" { c.DataDir = "/var/lib/myapp" }
}
type DiffChange ¶ added in v0.18.0
type DiffChange = istate.DiffChange
DiffChange classifies one DiffEntry as an add, removal, or in-place modification. See State.Diff.
type DiffEntry ¶ added in v0.18.0
DiffEntry is one structured per-path difference between two State snapshots, returned by State.Diff and embedded in DiffEvent and PlanResult. Use FormatDiff to render a human-readable line list.
type DiffReporter ¶
type DiffReporter = istate.DiffReporter
type DiffReporterFunc ¶
type DiffReporterFunc = istate.DiffReporterFunc
type DiffReporterMetricsSink ¶
type DiffReporterMetricsSink = iobs.DiffReporterMetricsSink
type DumpFormat ¶ added in v0.18.0
type DumpFormat = istate.DumpFormat
DumpFormat selects the encoding used by State.Dump. The zero value is DumpYAML.
type EvalContext ¶
type EvalContext = feature.EvalContext
type FeatureRule ¶
type HierarchicalOpts ¶
type HierarchicalOpts struct {
Dir string // config root directory (default DefaultDir)
RegionEnv string // env var for region axis (default "REGION")
ZoneEnv string // env var for zone axis (default "ZONE")
HostEnv string // env var for host axis (default "HOST")
Watch bool // enable fsnotify hot-reload
// CoalesceProfile selects watcher event-burst windows. Zero value
// is ProfileK8s.
CoalesceProfile CoalesceProfile
}
HierarchicalOpts captures the common knobs for deployments that use the base + regions/<r> + zones/<z> + hosts/<h> directory layout driven by environment variables.
type Introspection ¶
type Introspection = istate.Introspection
type JSONAuditSink ¶
type JSONAuditSink = iobs.JSONAuditSink
func NewJSONAuditSink ¶
func NewJSONAuditSink(w io.Writer) *JSONAuditSink
type K8sOpts ¶
type K8sOpts struct {
Dir string // ConfigMap mount path (default "/etc/config")
ProfileEnv string // env var to read profile from (default DefaultProfileEnv)
Default string // default profile if env empty (default "default")
Watch bool // enable fsnotify (recommended)
// CoalesceProfile selects watcher event-burst windows. Zero value
// is ProfileK8s, which matches ConfigMap atomic-swap latencies.
CoalesceProfile CoalesceProfile
}
K8sOpts captures the common knobs for a Kubernetes deployment that reads ConfigMaps mounted at a known directory and selects a profile from an environment variable populated by the Pod spec.
type Manager ¶
type Manager[T any] struct { // contains filtered or unexported fields }
Manager is the strongly-typed, lock-free configuration manager.
func MustNew ¶ added in v0.18.0
MustNew is the panic variant of New. It is intended for top-level program initialisation (main / init), where the only sensible response to a configuration-load failure is to abort startup with a loud, deterministic panic:
var Config = fastconf.MustNew[AppConfig](context.Background(),
fastconf.WithDir("conf.d"),
fastconf.WithProvider(provider.NewEnv("APP_")),
)
Long-running servers / daemons should continue to use New so they can decide whether to fall back to built-in defaults or keep serving the previous snapshot. MustNew deliberately omits MustGet / MustReload variants:
- Manager.Get on a successfully constructed manager never returns nil — New runs the initial reload before returning, so the snapshot is always populated.
- Manager.Reload failures are runtime events; panicking on a network blip would violate the framework's failure-safe contract.
- Extract is nil-safe by design and cannot fail.
The panic message wraps the underlying error so `recover` / panic reporters surface the original cause.
Example ¶
ExampleMustNew demonstrates the one-line top-level initialisation pattern. MustNew panics when the initial reload fails, so it is intended for main / init in command-line tools and tests — not for long-running daemons that should degrade gracefully.
package main
import (
"context"
"fmt"
"testing/fstest"
"github.com/fastabc/fastconf"
)
type apiExampleConfig struct {
Server struct {
Addr string `json:"addr" yaml:"addr"`
} `json:"server" yaml:"server"`
}
func main() {
mgr := fastconf.MustNew[apiExampleConfig](context.Background(),
fastconf.PresetTesting(fastconf.TestingOpts{
FS: fstest.MapFS{
"conf.d/base/00-app.yaml": &fstest.MapFile{
Data: []byte("server:\n addr: \":8080\"\n"),
},
},
}),
)
defer mgr.Close()
fmt.Println(mgr.Get().Server.Addr)
}
Output: :8080
func New ¶
New constructs a Manager and runs the first reload synchronously.
For one-line initialisation in main / init see MustNew.
Example ¶
ExampleNew demonstrates the shortest typed entry path: construct a manager, read the live value, and close it when the owner shuts down.
package main
import (
"context"
"fmt"
"testing/fstest"
"github.com/fastabc/fastconf"
)
type apiExampleConfig struct {
Server struct {
Addr string `json:"addr" yaml:"addr"`
} `json:"server" yaml:"server"`
}
func main() {
mgr, err := fastconf.New[apiExampleConfig](context.Background(),
fastconf.PresetTesting(fastconf.TestingOpts{
FS: fstest.MapFS{
"conf.d/base/00-app.yaml": &fstest.MapFile{
Data: []byte("server:\n addr: \":8080\"\n"),
},
},
}),
)
if err != nil {
fmt.Println(err)
return
}
defer mgr.Close()
fmt.Println(mgr.Get().Server.Addr)
}
Output: :8080
func (*Manager[T]) Errors ¶
func (m *Manager[T]) Errors() <-chan ReloadError
Example ¶
ExampleManager_Errors demonstrates the asynchronous failure stream that lets services centralize reload error handling without blocking the writer.
package main
import (
"context"
"fmt"
"testing/fstest"
"github.com/fastabc/fastconf"
)
type apiExampleConfig struct {
Server struct {
Addr string `json:"addr" yaml:"addr"`
} `json:"server" yaml:"server"`
}
func main() {
mgr, err := fastconf.New[apiExampleConfig](context.Background(),
fastconf.PresetTesting(fastconf.TestingOpts{
FS: fstest.MapFS{
"conf.d/base/00-app.yaml": &fstest.MapFile{
Data: []byte("server:\n addr: \":8080\"\n"),
},
},
}),
fastconf.WithValidator(func(c *apiExampleConfig) error {
if c.Server.Addr == "" {
return fmt.Errorf("server.addr is required")
}
return nil
}),
)
if err != nil {
fmt.Println(err)
return
}
defer mgr.Close()
_ = mgr.Reload(context.Background(), fastconf.WithSourceOverride(map[string]any{
"server": map[string]any{"addr": ""},
}))
re := <-mgr.Errors()
fmt.Println(re.Reason, re.Err != nil)
}
Output: override true
func (*Manager[T]) Plan ¶
func (m *Manager[T]) Plan() *PlanBuilder[T]
Example ¶
ExampleManager_Plan demonstrates previewing a file-backed change before it becomes the live snapshot.
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/fastabc/fastconf"
)
type apiExampleConfig struct {
Server struct {
Addr string `json:"addr" yaml:"addr"`
} `json:"server" yaml:"server"`
}
func main() {
root := mustExampleTempDir("example-plan-")
defer os.RemoveAll(root)
confDir := filepath.Join(root, "conf.d")
configPath := filepath.Join(confDir, "base", "00-app.yaml")
mustWriteExampleFile(configPath, "server:\n addr: \":8080\"\n")
mgr, err := fastconf.New[apiExampleConfig](context.Background(),
fastconf.WithDir(confDir),
)
if err != nil {
fmt.Println(err)
return
}
defer mgr.Close()
mustWriteExampleFile(configPath, "server:\n addr: \":9090\"\n")
plan, err := mgr.Plan().Run(context.Background())
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(plan.Diff), plan.Proposed.Value.Server.Addr, mgr.Get().Server.Addr)
}
func mustExampleTempDir(pattern string) string {
dir, err := os.MkdirTemp(".", pattern)
if err != nil {
panic(err)
}
abs, err := filepath.Abs(dir)
if err != nil {
panic(err)
}
return abs
}
func mustWriteExampleFile(path, content string) {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
panic(err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
panic(err)
}
}
Output: 1 :9090 :8080
type MetricsSink ¶
type MetricsSink = iobs.MetricsSink
type MigrationApplier ¶
MigrationApplier is the root-facade contract for the version migration stage. Migrate is invoked once per reload on the merged raw map, before any transformer and before decoding into *T. Use MigrationFunc to adapt a plain `func(map[string]any) error` value.
type MigrationFunc ¶
MigrationFunc adapts a plain function value to the MigrationApplier contract.
type Option ¶
func PresetHierarchical ¶
func PresetHierarchical(p HierarchicalOpts) Option
PresetHierarchical returns options for the standard multi-axis deployment pattern: base layer always loaded, then regions (if $REGION is set), then zones (if $ZONE is set), then hosts (if $HOST is set or hostname matches a subdirectory). Providers still override all file layers.
The hosts axis uses DefaultFromHostname: true, so it automatically activates based on os.Hostname() when the host env var is not set. Set the env var explicitly to an empty string to disable host-specific overlays.
Example directory layout:
config/
├── base/ <- always loaded (priority 1000-1999)
├── regions/
│ └── eu-west/ <- loaded when $REGION=eu-west (priority 3000-3099)
├── zones/
│ └── az1/ <- loaded when $ZONE=az1 (priority 3100-3199)
└── hosts/
└── web-01/ <- loaded when $HOST=web-01 or hostname=web-01 (priority 3200-3299)
func PresetK8s ¶
PresetK8s returns the canonical option bundle for K8s side-by-side ConfigMap deployments: directory load, profile from env, watch on, strict mode (fail loud on unknown fields).
func PresetSidecar ¶
func PresetSidecar(p SidecarOpts) Option
PresetSidecar returns options tuned for a sidecar daemon: bigger history ring (so /events SSE consumers can replay), watch on by default, less strict so unknown fields warn instead of fail.
func PresetTesting ¶
func PresetTesting(p TestingOpts) Option
PresetTesting returns options tuned for hermetic tests. Watch is always disabled; strict is always on.
func WithAuditSink ¶
func WithCoalesce ¶ added in v0.18.0
func WithCoalesce(c CoalesceOptions) Option
WithCoalesce overrides just the coalescer windows without touching the Watch enabled flag or paths. Useful when a Preset already enabled Watch with a profile-based timing set and the caller wants to fine- tune one knob:
fastconf.PresetK8s(K8sOpts{Watch: true, CoalesceProfile: ProfileK8s}),
fastconf.WithCoalesce(CoalesceOptions{Quiet: 75*time.Millisecond}),
func WithCodecBridge ¶
func WithCodecBridge(b CodecBridge) Option
WithCodecBridge selects the bytes-to-struct decoder for the typed stage. See CodecBridge for the BridgeJSON / BridgeYAML semantics.
Troubleshooting ¶
The default BridgeJSON round-trips through encoding/json so the canonical-hash cache can reuse the marshalled bytes. It honours `json:` and `fastconf:` struct tags only. Symptoms that indicate the default is mis-matched to your struct:
- snake_case keys in your YAML are silently dropped — the field is left at its zero value (e.g. `db_pool: 50` ignored when *T only declares `yaml:"db_pool"`). FastConf emits a one-time warn log at New() to surface this; switch to BridgeYAML or add `json:` tags.
- nested structs deserialize as nil — yaml's anchor / merge keys are normalized to map[string]any by the decoder but json's struct decoder reads field names, not tags, when no `json:` tag is present.
- time.Time fields fail to parse — yaml's native time type encodes as `2006-01-02T15:04:05Z` strings; the json bridge accepts those only with a `time.Time`-aware typed hook.
When in doubt, set BridgeYAML for YAML-tagged configs.
func WithDefaults ¶ added in v0.18.0
WithDefaults installs a post-decode defaults function for cases where *T cannot implement the Defaulter interface (e.g., third-party types or when modifying the struct definition is not possible). It is the function-form counterpart to Defaulter and runs at the same point in the pipeline.
Defaults precedence (each step only fills zero / unset fields left by the previous step):
- WithStructDefaults — `fastconf:"default=..."` struct tags
- Defaulter interface — *T.Defaults() if implemented
- WithDefaults — explicit fn, last to run
All three run BEFORE validators, so WithValidator sees the populated value.
func WithDiffReporter ¶
func WithDiffReporter(r DiffReporter) Option
func WithDotEnvAuto ¶
func WithFeatureRules ¶
func WithGenerator ¶
func WithHistory ¶
func WithLogger ¶
WithLogger overrides the default slog logger. Passing nil records a deferred error so a misconfigured logger fails loudly at New(), rather than silently routing every log line into the default backend.
func WithMergeKeys ¶
func WithMetrics ¶
func WithMetrics(m MetricsSink) Option
WithMetrics installs a MetricsSink. Passing nil records a deferred error so a missing sink fails loudly at New() rather than silently dropping every metric.
func WithMultiAxisOverlays ¶
func WithMultiAxisOverlays(axes ...OverlayAxis) Option
WithMultiAxisOverlays adds multi-axis overlay layers (region, tier, hostname, ...). Each OverlayAxis resolves at assemble time to a concrete extra overlay directory via its EnvVar / DefaultFromHostname rules. Append-only across calls.
func WithProfile ¶
func WithProfile(p ProfileOptions) Option
WithProfile installs the supplied ProfileOptions. A zero value is valid (loads base only).
func WithProvenance ¶
func WithProvenance(level ProvenanceLevel) Option
func WithProvider ¶
func WithProviderOrdered ¶
func WithProviderRegistry ¶
func WithProviderRegistry(r *ProviderRegistry) Option
func WithRawMapAccess ¶
func WithSecretRedactor ¶
func WithSecretRedactor(r SecretRedactor) Option
func WithSecretResolver ¶
func WithSecretResolver(r SecretResolver) Option
func WithStrict ¶
func WithStructDefaults ¶
WithStructDefaults installs a transformer that populates zero-valued fields of *T from `fastconf:"default=..."` struct tags. It runs once per reload, immediately before validation, so user-supplied YAML / patch / provider values always win over the tag default.
func WithTracer ¶
WithTracer installs a Tracer. Passing nil records a deferred error so a missing tracer fails loudly at New() rather than silently dropping every span.
func WithTransformers ¶
func WithTransformers(t ...Transformer) Option
WithTransformers appends raw-map transformers that run in declared order after merge and before the typed decoder. Implementations satisfy the root Transformer interface (Name + Transform).
func WithTypedHook ¶
func WithValidator ¶
func WithWatch ¶
func WithWatch(w WatchOptions) Option
WithWatch installs the file-watcher with the supplied WatchOptions. A zero WatchOptions{} disables the watcher (same as omitting this Option). The CoalesceProfile selector applies before the per-field Coalesce values, so Coalesce overrides anything the profile set.
func WithoutDefaultTypedHooks ¶
func WithoutDefaultTypedHooks() Option
type Origin ¶
type Origin = provenance.Origin
type OriginIndex ¶
type OriginIndex = provenance.Index
type OverlayAxis ¶
OverlayAxis describes one multi-axis overlay layer. Resolution order:
- EnvVar present + non-empty → use that value
- EnvVar present + empty → skip axis (operator opt-out)
- EnvVar absent + DefaultFromHostname → fall back to os.Hostname()
- otherwise → skip axis
Priority should be a value in or above the contracts.BandExtraOverlay (3000) range to win over file-base / single-profile overlays. The Generator (7000) and Provider (8000) bands stay higher.
type PlanBuilder ¶
type PlanBuilder[T any] struct { // contains filtered or unexported fields }
func (*PlanBuilder[T]) Run ¶
func (b *PlanBuilder[T]) Run(ctx context.Context) (*PlanResult[T], error)
func (*PlanBuilder[T]) WithHostname ¶
func (b *PlanBuilder[T]) WithHostname(host string) *PlanBuilder[T]
type PlanResult ¶
type PolicyError ¶
type PolicyError = fcerr.PolicyError
type ProfileOptions ¶ added in v0.18.0
type ProfileOptions struct {
// Single is the active profile name for the legacy single-profile
// path. Set this when you want one overlay subdirectory selected
// by name. Mutually exclusive with Multi.
Single string
// Multi enables expression-based overlay matching by populating
// the active profile set. Each overlay's `_meta.yaml.match`
// predicate (or, lacking _meta.yaml, the subdirectory name) is
// evaluated against this set.
Multi []string
// Expr is an additional global expression that must hold for any
// overlay to be selected. AND-ed with each overlay's per-meta
// match. Empty disables the global filter.
Expr string
// EnvVar names the environment variable read when Single / Multi
// are both empty. Empty falls back to DefaultProfileEnv
// ("APP_PROFILE").
EnvVar string
// Default is the profile name used when EnvVar is unset / empty.
Default string
}
ProfileOptions bundles the profile-selection knobs. Single and Multi are mutually exclusive: when Multi is non-empty it takes precedence and Single is ignored. Expr is the global expression AND-ed with each overlay's `_meta.yaml.match` predicate. EnvVar / Default control the fallback chain when neither Single nor Multi is set:
- ProfileOptions.Single (when non-empty)
- ProfileOptions.Multi (when non-empty; turns on expression matching)
- $EnvVar / $DefaultProfileEnv
- ProfileOptions.Default
- _meta.yaml's spec.defaultProfile
type ProvenanceLevel ¶
type ProvenanceLevel = provenance.Level
type ProviderFactory ¶
func LookupProviderFactory ¶
func LookupProviderFactory(name string) (ProviderFactory, bool)
type ProviderMetricsSink ¶
type ProviderMetricsSink = iobs.ProviderMetricsSink
type ProviderRegistry ¶
func NewProviderRegistry ¶
func NewProviderRegistry() *ProviderRegistry
type ReloadCause ¶
type ReloadCause = istate.ReloadCause
type ReloadError ¶
type ReloadError = fcerr.ReloadError
type ReloadOption ¶
type ReloadOption = imanager.ReloadOption
func WithReloadReason ¶
func WithReloadReason(reason string) ReloadOption
func WithSourceOverride ¶
func WithSourceOverride(override map[string]any) ReloadOption
type RenderMetricsSink ¶
type RenderMetricsSink = iobs.RenderMetricsSink
type Replay ¶
type Replay[T any] struct { // contains filtered or unexported fields }
func (*Replay[T]) Rollback ¶
Example ¶
ExampleReplay_Rollback demonstrates recovering a retained prior snapshot without rerunning the reload pipeline.
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/fastabc/fastconf"
)
type apiExampleConfig struct {
Server struct {
Addr string `json:"addr" yaml:"addr"`
} `json:"server" yaml:"server"`
}
func main() {
root := mustExampleTempDir("example-replay-")
defer os.RemoveAll(root)
confDir := filepath.Join(root, "conf.d")
configPath := filepath.Join(confDir, "base", "00-app.yaml")
mustWriteExampleFile(configPath, "server:\n addr: \":8080\"\n")
mgr, err := fastconf.New[apiExampleConfig](context.Background(),
fastconf.WithDir(confDir),
fastconf.WithHistory(2),
)
if err != nil {
fmt.Println(err)
return
}
defer mgr.Close()
mustWriteExampleFile(configPath, "server:\n addr: \":9090\"\n")
if err := mgr.Reload(context.Background()); err != nil {
fmt.Println(err)
return
}
liveAfterReload := mgr.Get().Server.Addr
history := mgr.Replay().List()
if err := mgr.Replay().Rollback(history[0]); err != nil {
fmt.Println(err)
return
}
fmt.Println(liveAfterReload, mgr.Get().Server.Addr)
}
func mustExampleTempDir(pattern string) string {
dir, err := os.MkdirTemp(".", pattern)
if err != nil {
panic(err)
}
abs, err := filepath.Abs(dir)
if err != nil {
panic(err)
}
return abs
}
func mustWriteExampleFile(path, content string) {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
panic(err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
panic(err)
}
}
Output: :9090 :8080
type SecretRedactor ¶
type SecretResolver ¶
type SecretResolverFunc ¶
type SecretResolverFunc = secret.ResolverFunc
type SidecarOpts ¶
type SidecarOpts struct {
Dir string
HistoryN int // history ring capacity (default DefaultSidecarHistoryCap)
Watch bool // typically true for sidecars
Strict bool
}
SidecarOpts captures the common knobs for cmd/fastconfd-style deployments where the manager is hosted by an in-cluster process that exposes the config over HTTP/SSE.
type StageMetricsSink ¶
type StageMetricsSink = iobs.StageMetricsSink
type State ¶
func (*State[T]) Dump ¶ added in v0.18.0
func (s *State[T]) Dump(format DumpFormat, redactor SecretRedactor) ([]byte, error)
func (*State[T]) Introspect ¶
func (s *State[T]) Introspect() *Introspection
func (*State[T]) LookupStrict ¶
func (s *State[T]) LookupStrict(path string) ([]provenance.Origin, error)
func (*State[T]) Origins ¶
func (s *State[T]) Origins() *provenance.Index
type TenantManager ¶
type TenantManager[T any] struct { // contains filtered or unexported fields }
func NewTenantManager ¶
func NewTenantManager[T any]() *TenantManager[T]
func (*TenantManager[T]) Close ¶
func (tm *TenantManager[T]) Close() error
func (*TenantManager[T]) Has ¶
func (tm *TenantManager[T]) Has(id string) bool
func (*TenantManager[T]) Remove ¶
func (tm *TenantManager[T]) Remove(id string) error
func (*TenantManager[T]) Tenants ¶
func (tm *TenantManager[T]) Tenants() []string
type TestingOpts ¶
TestingOpts captures the common knobs for hermetic unit/integration tests: pass an fs.FS (often testing/fstest.MapFS), pin a profile, disable watch, and force strict so tests catch typos eagerly.
type Transformer ¶
Transformer is the root-facade contract for the pre-decode raw-map transformation stage. Implementations get the merged map[string]any AFTER all source layers fold together and BEFORE the typed decoder runs, so they can rewrite keys, inject computed values, or normalise vendor-specific layouts without touching *T.
The same shape is reused inside pkg/transform for the built-in transformers (Aliases, KeyMap, DropPrefix, EnvReplacer, …); third parties only need to satisfy this root interface.
type ValidatorReport ¶
type ValidatorReport = imanager.ValidatorReport
type WatchOptions ¶ added in v0.18.0
type WatchOptions struct {
Enabled bool
Paths []string
Coalesce CoalesceOptions
CoalesceProfile coalesce.Profile
}
WatchOptions bundles the file-watcher knobs. Enabled defaults to false; set it explicitly to opt into reload-on-change. Paths and Coalesce / CoalesceProfile only apply when Enabled is true.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
fastconfctl
command
Command fastconfctl is a CLI companion to FastConf for CI / ops:
|
Command fastconfctl is a CLI companion to FastConf for CI / ops: |
|
fastconfd
command
fastconfd is the sidecar daemon.
|
fastconfd is the sidecar daemon. |
|
fastconfgen
command
fastconfgen reads a YAML or JSON configuration sample and emits an equivalent Go struct definition.
|
fastconfgen reads a YAML or JSON configuration sample and emits an equivalent Go struct definition. |
|
internal/cli
Package cli centralises the FastConf command-line flag set so every cmd/* binary registers -dir / -profile / -strict / -watch with identical defaults and semantics, and constructs the Manager via a single canonical path.
|
Package cli centralises the FastConf command-line flag set so every cmd/* binary registers -dir / -profile / -strict / -watch with identical defaults and semantics, and constructs the Manager via a single canonical path. |
|
Package contracts is the **public, stable** surface of FastConf interfaces.
|
Package contracts is the **public, stable** surface of FastConf interfaces. |
|
examples
|
|
|
fastconf
|
|
|
contracts
module
|
|
|
integrations
|
|
|
bus
Package bus provides a small message-bus abstraction for FastConf.
|
Package bus provides a small message-bus abstraction for FastConf. |
|
openfeature
Package openfeature adapts a FastConf Manager into an OpenFeature-shaped provider.
|
Package openfeature adapts a FastConf Manager into an OpenFeature-shaped provider. |
|
render
Package render plugs FastConf into the long tail of legacy daemons that only consume on-disk configuration files (nginx.conf, envoy.yaml, postgresql.conf, ...).
|
Package render plugs FastConf into the long tail of legacy daemons that only consume on-disk configuration files (nginx.conf, envoy.yaml, postgresql.conf, ...). |
|
internal
|
|
|
coalesce
Package coalesce collapses bursty fsnotify events into a single trigger per (key, burst) — where "key" is typically the parent directory of the events.
|
Package coalesce collapses bursty fsnotify events into a single trigger per (key, burst) — where "key" is typically the parent directory of the events. |
|
diffreport
Package diffreport is the bounded-queue worker pool that fans post-reload diff events out to user-installed Reporter implementations.
|
Package diffreport is the bounded-queue worker pool that fans post-reload diff events out to user-installed Reporter implementations. |
|
pipeline
Package pipeline holds reload-pipeline helpers that are pure functions of *T or the merged map[string]any.
|
Package pipeline holds reload-pipeline helpers that are pure functions of *T or the merged map[string]any. |
|
provenance
Package provenance owns the per-field "where did this value come from" index that the merger feeds during reload.
|
Package provenance owns the per-field "where did this value come from" index that the merger feeds during reload. |
|
registry
Package registry holds the provider-factory registry that backs fastconf.WithProviderByName.
|
Package registry holds the provider-factory registry that backs fastconf.WithProviderByName. |
|
secret
Package secret implements the redaction and resolver primitives that back fastconf's `fastconf:"secret"` tag and WithSecretResolver hook.
|
Package secret implements the redaction and resolver primitives that back fastconf's `fastconf:"secret"` tag and WithSecretResolver hook. |
|
state
Package state holds snapshot, source, and reload-cause primitives used by the public fastconf facade and internal manager implementation.
|
Package state holds snapshot, source, and reload-cause primitives used by the public fastconf facade and internal manager implementation. |
|
testutil
Package testutil centralises test helpers shared across the fastconf module.
|
Package testutil centralises test helpers shared across the fastconf module. |
|
typeinfo
Package typeinfo provides a single, cached reflect.Type walker for FastConf's per-T metadata extraction (secret paths, default tags, top-level field hashers).
|
Package typeinfo provides a single, cached reflect.Type walker for FastConf's per-T metadata extraction (secret paths, default tags, top-level field hashers). |
|
watcher
Package watcher subscribes to filesystem changes and feeds them into a coalescer.
|
Package watcher subscribes to filesystem changes and feeds them into a coalescer. |
|
pkg
|
|
|
cliadapter
Package cliadapter converts CLI flag state into a fastconf-compatible nested map containing only the flags the user explicitly set on the command line.
|
Package cliadapter converts CLI flag state into a fastconf-compatible nested map containing only the flags the user explicitly set on the command line. |
|
decoder
Package decoder turns bytes of various encodings (yaml/json/...) into a uniform map[string]any intermediate representation.
|
Package decoder turns bytes of various encodings (yaml/json/...) into a uniform map[string]any intermediate representation. |
|
discovery
Package discovery scans a configuration root and produces a stream of priority-ordered layers (base, overlays, extra overlay axes).
|
Package discovery scans a configuration root and produces a stream of priority-ordered layers (base, overlays, extra overlay axes). |
|
feature
Package feature provides a tiny, allocation-light feature-flag / rollout evaluator that piggybacks on FastConf's strongly-typed configuration.
|
Package feature provides a tiny, allocation-light feature-flag / rollout evaluator that piggybacks on FastConf's strongly-typed configuration. |
|
flog
Package flog wraps *slog.Logger with a zerolog-style fluent API while preserving slog's handler ecosystem.
|
Package flog wraps *slog.Logger with a zerolog-style fluent API while preserving slog's handler ecosystem. |
|
generator
Package generator hosts FastConf's built-in contracts.Generator implementations.
|
Package generator hosts FastConf's built-in contracts.Generator implementations. |
|
mappath
Package mappath provides dotted-path helpers for map[string]any trees.
|
Package mappath provides dotted-path helpers for map[string]any trees. |
|
merger
Package merger implements Kustomize-style deep merge for map[string]any trees.
|
Package merger implements Kustomize-style deep merge for map[string]any trees. |
|
migration
Package migration lets FastConf rewrite the merged map from one schema version to another before it is decoded into the strongly typed snapshot.
|
Package migration lets FastConf rewrite the merged map from one schema version to another before it is decoded into the strongly typed snapshot. |
|
parser
Package parser exposes the koanf-style Parser slot used at the Manager call site (WithSource(file.New(path), yaml.Parser())).
|
Package parser exposes the koanf-style Parser slot used at the Manager call site (WithSource(file.New(path), yaml.Parser())). |
|
profile
Package profile implements FastConf's tiny boolean profile-expression language.
|
Package profile implements FastConf's tiny boolean profile-expression language. |
|
provider
Package provider abstracts external configuration sources (env, CLI, KV, Vault, ...).
|
Package provider abstracts external configuration sources (env, CLI, KV, Vault, ...). |
|
source
Package source provides built-in contracts.Source implementations for the koanf-style WithSource(file/http/bytes, parser) call shape.
|
Package source provides built-in contracts.Source implementations for the koanf-style WithSource(file/http/bytes, parser) call shape. |
|
transform
Package transform provides composable, post-merge / pre-decode transformations on the merged configuration tree.
|
Package transform provides composable, post-merge / pre-decode transformations on the merged configuration tree. |
|
typed
Package typed contains scalar-value helpers shared by pkg/provider and pkg/mappath.
|
Package typed contains scalar-value helpers shared by pkg/provider and pkg/mappath. |
|
validate
Package validate hosts reusable validation primitives for FastConf.
|
Package validate hosts reusable validation primitives for FastConf. |
|
Package policy defines the policy interface.
|
Package policy defines the policy interface. |
|
providers
|
|
|
consul
Package consul is a first-party Consul KV provider for FastConf.
|
Package consul is a first-party Consul KV provider for FastConf. |
|
http
Package http is a first-party HTTP/HTTPS provider for FastConf.
|
Package http is a first-party HTTP/HTTPS provider for FastConf. |
|
k8s
Package k8s implements first-party FastConf providers for Kubernetes integration.
|
Package k8s implements first-party FastConf providers for Kubernetes integration. |
|
nats
Package nats implements a FastConf Provider backed by a NATS-style publish/subscribe connection.
|
Package nats implements a FastConf Provider backed by a NATS-style publish/subscribe connection. |
|
redisstream
Package redisstream implements a FastConf Provider backed by a Redis Streams compatible client.
|
Package redisstream implements a FastConf Provider backed by a Redis Streams compatible client. |
|
vault
Package vault is a first-party HashiCorp Vault KV v2 provider for FastConf.
|
Package vault is a first-party HashiCorp Vault KV v2 provider for FastConf. |