add viperconfig
This commit is contained in:
parent
6b39120f6d
commit
f9fc3be716
2 changed files with 134 additions and 0 deletions
108
viperconfig/configuration_manager.go
Normal file
108
viperconfig/configuration_manager.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package configuration_manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigurationManager[T any] manages Viper for the specified configuration struct
|
||||||
|
type ConfigurationManager[T any] struct {
|
||||||
|
name string
|
||||||
|
config T
|
||||||
|
defaults []byte
|
||||||
|
lock *sync.RWMutex
|
||||||
|
vc *viper.Viper
|
||||||
|
}
|
||||||
|
|
||||||
|
// New[T any] creates a new configuration manager for the specified configuration struct
|
||||||
|
func New[T any](name string, defaults []byte) *ConfigurationManager[T] {
|
||||||
|
// Initialize
|
||||||
|
p := &ConfigurationManager[T]{
|
||||||
|
name: name,
|
||||||
|
defaults: defaults,
|
||||||
|
lock: &sync.RWMutex{},
|
||||||
|
vc: viper.New(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load defaults
|
||||||
|
vd := viper.New()
|
||||||
|
buf := bytes.NewBuffer(p.defaults)
|
||||||
|
if err := vd.ReadConfig(buf); err != nil {
|
||||||
|
slog.Error("Error loading default configuration", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Viper instance
|
||||||
|
p.vc = viper.New()
|
||||||
|
p.vc.AddConfigPath("/secrets")
|
||||||
|
p.vc.AddConfigPath("/etc/joco")
|
||||||
|
p.vc.AddConfigPath("/etc")
|
||||||
|
p.vc.AddConfigPath(".")
|
||||||
|
p.vc.SetConfigName(p.name)
|
||||||
|
p.vc.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
p.vc.SetEnvPrefix(p.name)
|
||||||
|
p.vc.AutomaticEnv()
|
||||||
|
p.vc.OnConfigChange(p.onConfigChange)
|
||||||
|
|
||||||
|
// Set defaults from embedded configuration file
|
||||||
|
for k, v := range vd.AllSettings() {
|
||||||
|
p.vc.SetDefault(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the configuration (ignore file not found)
|
||||||
|
if err := p.load(); err != nil {
|
||||||
|
slog.Error("Error loading configuration", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.vc.BindPFlags(pflag.CommandLine)
|
||||||
|
|
||||||
|
// Monitor changes to the configuration
|
||||||
|
p.vc.WatchConfig()
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConfigurationManager[T]) Get() T {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
|
|
||||||
|
return p.config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConfigurationManager[T]) load() error {
|
||||||
|
if err := p.vc.ReadInConfig(); err != nil {
|
||||||
|
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg T
|
||||||
|
if err := viper.Unmarshal(&cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.lock.Lock()
|
||||||
|
p.config = cfg
|
||||||
|
p.lock.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConfigurationManager[T]) onConfigChange(in fsnotify.Event) {
|
||||||
|
if err := p.load(); err != nil {
|
||||||
|
slog.Error("Error re-loading configuration", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveToFile saves the embedded default configuration to the specified file
|
||||||
|
func (p *ConfigurationManager[T]) SaveToFile(path string) error {
|
||||||
|
return os.WriteFile(path, p.defaults, os.ModeExclusive)
|
||||||
|
}
|
26
viperconfig/context.go
Normal file
26
viperconfig/context.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package configuration_manager
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type contextKey struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ConfigurationKey = contextKey{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetConfigFromContext[T any] returns the current configuration extracted from the passed context
|
||||||
|
func GetConfigFromContext[T any](ctx context.Context) T {
|
||||||
|
cm := GetConfigMgrFromContext[T](ctx)
|
||||||
|
|
||||||
|
return cm.Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigMgrFromContext[T any] returns the configuration manager stored in the passed context
|
||||||
|
func GetConfigMgrFromContext[T any](ctx context.Context) ConfigurationManager[T] {
|
||||||
|
return ctx.Value(ConfigurationKey).(ConfigurationManager[T])
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfigMgrOnContext[T any] stores the passed configuration manager on the passed context
|
||||||
|
func SetConfigMgrOnContext[T any](ctx context.Context, value T) context.Context {
|
||||||
|
return context.WithValue(ctx, ConfigurationKey, value)
|
||||||
|
}
|
Loading…
Reference in a new issue