106 lines
2.2 KiB
Go
106 lines
2.2 KiB
Go
package viperconfig
|
|
|
|
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 {
|
|
panic(err)
|
|
}
|
|
|
|
// 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 {
|
|
panic(err)
|
|
}
|
|
|
|
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(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)
|
|
}
|