Files
backrest/internal/config/config.go

147 lines
3.1 KiB
Go

package config
import (
"errors"
"fmt"
"sync"
v1 "github.com/garethgeorge/backrest/gen/go/v1"
"github.com/garethgeorge/backrest/internal/config/migrations"
"github.com/garethgeorge/backrest/internal/cryptoutil"
"github.com/garethgeorge/backrest/internal/eventemitter"
"go.uber.org/zap"
)
var ErrConfigNotFound = fmt.Errorf("config not found")
type ConfigManager struct {
Store ConfigStore
OnChange eventemitter.EventEmitter[struct{}]
migrateOnce sync.Once
migrateErr error
cachedMu sync.Mutex
cached *v1.Config
}
var _ ConfigStore = &ConfigManager{}
func (m *ConfigManager) migrate(config *v1.Config) error {
// Check if we need to migrate
mutated, err := PopulateRequiredFields(config)
if err != nil {
return fmt.Errorf("populate required fields: %w", err)
}
if config.Version < migrations.CurrentVersion {
zap.S().Infof("migrating config from version %d to %d", config.Version, migrations.CurrentVersion)
if err := migrations.ApplyMigrations(config); err != nil {
return err
}
mutated = true
}
if mutated {
// Check validations
if err := ValidateConfig(config); err != nil {
return fmt.Errorf("validation after migration: %v", err)
}
// Write back the migrated config.
if err := m.Store.Update(config); err != nil {
return err
}
}
return nil
}
func (m *ConfigManager) Get() (*v1.Config, error) {
m.cachedMu.Lock()
defer m.cachedMu.Unlock()
if m.cached != nil {
return m.cached, nil
}
config, err := m.Store.Get()
if err != nil {
if errors.Is(err, ErrConfigNotFound) {
m.cached = NewDefaultConfig()
return m.cached, nil
}
return nil, err
}
// Try to apply migrations
m.migrateOnce.Do(func() {
m.migrateErr = m.migrate(config)
})
if m.migrateErr != nil {
return nil, m.migrateErr
}
// Validate the config
if err := ValidateConfig(config); err != nil {
return nil, err
}
// Finally cache it for performance
m.cached = config
return config, err
}
func (m *ConfigManager) Update(config *v1.Config) error {
m.cachedMu.Lock()
defer m.cachedMu.Unlock()
if err := ValidateConfig(config); err != nil {
return err
}
err := m.Store.Update(config)
if err != nil {
return err
}
m.cached = config
m.OnChange.Emit(struct{}{})
return nil
}
type ConfigStore interface {
Get() (*v1.Config, error)
Update(config *v1.Config) error
}
func NewDefaultConfig() *v1.Config {
cfg := &v1.Config{
Version: migrations.CurrentVersion,
Instance: "",
Repos: []*v1.Repo{},
Plans: []*v1.Plan{},
Auth: &v1.Auth{
Disabled: true,
},
}
_, err := PopulateRequiredFields(cfg)
if err != nil {
zap.S().Fatalf("failed to populate required fields in default config: %v", err)
}
return cfg
}
func PopulateRequiredFields(config *v1.Config) (mutated bool, err error) {
if config.GetMultihost() == nil {
config.Multihost = &v1.Multihost{}
mutated = true
}
if config.GetMultihost().Identity == nil {
identity, err := cryptoutil.GeneratePrivateKey()
if err != nil {
return false, fmt.Errorf("generate private key: %w", err)
}
config.GetMultihost().Identity = identity
mutated = true
}
return
}