add viperconfig

This commit is contained in:
Søren Rasmussen 2024-08-29 12:36:48 +02:00
parent 6b39120f6d
commit f9fc3be716
Signed by: snr
SSH key fingerprint: SHA256:wwGTdUDKh+zxCdbhOjJ5jt5/zF36kbg44gQPN6D9t1w
2 changed files with 134 additions and 0 deletions

View 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
View 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)
}