Files
backrest/internal/config/config.go

153 lines
3.0 KiB
Go

package config
import (
"errors"
"fmt"
"slices"
"sync"
v1 "github.com/garethgeorge/backrest/gen/go/v1"
"github.com/garethgeorge/backrest/internal/config/migrations"
"go.uber.org/zap"
)
var ErrConfigNotFound = fmt.Errorf("config not found")
type ConfigManager struct {
Store ConfigStore
callbacksMu sync.Mutex
changeNotifyCh []chan struct{}
}
var _ ConfigStore = &ConfigManager{}
func (m *ConfigManager) Get() (*v1.Config, error) {
return m.Store.Get()
}
func (m *ConfigManager) Update(config *v1.Config) error {
err := m.Store.Update(config)
if err != nil {
return err
}
m.callbacksMu.Lock()
changeNotifyCh := slices.Clone(m.changeNotifyCh)
m.callbacksMu.Unlock()
for _, ch := range changeNotifyCh {
select {
case ch <- struct{}{}:
default:
}
}
return nil
}
func (m *ConfigManager) Watch() <-chan struct{} {
m.callbacksMu.Lock()
ch := make(chan struct{}, 1)
m.changeNotifyCh = append(m.changeNotifyCh, ch)
m.callbacksMu.Unlock()
return ch
}
func (m *ConfigManager) StopWatching(ch <-chan struct{}) bool {
m.callbacksMu.Lock()
origLen := len(m.changeNotifyCh)
for i := range m.changeNotifyCh {
if m.changeNotifyCh[i] != ch {
continue
}
close(m.changeNotifyCh[i])
m.changeNotifyCh[i] = m.changeNotifyCh[len(m.changeNotifyCh)-1]
m.changeNotifyCh = m.changeNotifyCh[:len(m.changeNotifyCh)-1]
break
}
defer m.callbacksMu.Unlock()
return len(m.changeNotifyCh) != origLen
}
type ConfigStore interface {
Get() (*v1.Config, error)
Update(config *v1.Config) error
}
func NewDefaultConfig() *v1.Config {
return &v1.Config{
Version: migrations.CurrentVersion,
Instance: "",
Repos: []*v1.Repo{},
Plans: []*v1.Plan{},
Auth: &v1.Auth{
Disabled: true,
},
}
}
// TODO: merge caching validating store functions into config manager
type CachingValidatingStore struct {
ConfigStore
mu sync.Mutex
config *v1.Config
}
func (c *CachingValidatingStore) Get() (*v1.Config, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.config != nil {
return c.config, nil
}
config, err := c.ConfigStore.Get()
if err != nil {
if errors.Is(err, ErrConfigNotFound) {
c.config = NewDefaultConfig()
return c.config, nil
}
return c.config, err
}
// Check if we need to migrate
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 nil, err
}
// Write back the migrated config.
if err := c.ConfigStore.Update(config); err != nil {
return nil, err
}
}
// Validate the config
if err := ValidateConfig(config); err != nil {
return nil, err
}
c.config = config
return config, nil
}
func (c *CachingValidatingStore) Update(config *v1.Config) error {
c.mu.Lock()
defer c.mu.Unlock()
if err := ValidateConfig(config); err != nil {
return err
}
if err := c.ConfigStore.Update(config); err != nil {
return err
}
c.config = config
return nil
}