config: replace defaultConfig with a thread-safe in-memory implementation

This commit is contained in:
Chris Macklin 2021-05-03 14:01:06 -07:00 committed by Nick Craig-Wood
parent 6ef7178ee4
commit 732bc08ced
3 changed files with 132 additions and 37 deletions

View File

@ -121,7 +121,7 @@ func init() {
fs.ConfigFileGet = FileGetFlag fs.ConfigFileGet = FileGetFlag
fs.ConfigFileSet = SetValueAndSave fs.ConfigFileSet = SetValueAndSave
configPath = makeConfigPath() configPath = makeConfigPath()
data = defaultStorage{} data = newDefaultStorage()
} }
// Join directory with filename, and check if exists // Join directory with filename, and check if exists

View File

@ -1,61 +1,114 @@
package config package config
// Default config.Storage which panics with a useful error when used import (
type defaultStorage struct{} "encoding/json"
"sync"
)
var noConfigStorage = "internal error: no config file system found. Did you call configfile.Install()?" // defaultStorage implements config.Storage, providing in-memory config.
// Indexed by section, then key.
// GetSectionList returns a slice of strings with names for all the type defaultStorage struct {
// sections mu sync.RWMutex
func (defaultStorage) GetSectionList() []string { sections map[string]map[string]string
panic(noConfigStorage)
} }
// HasSection returns true if section exists in the config file func newDefaultStorage() *defaultStorage {
func (defaultStorage) HasSection(section string) bool { return &defaultStorage{
panic(noConfigStorage) sections: map[string]map[string]string{},
}
} }
// DeleteSection removes the named section and all config from the // GetSectionList returns a slice of strings with names for all the sections.
// config file func (s *defaultStorage) GetSectionList() []string {
func (defaultStorage) DeleteSection(section string) { s.mu.RLock()
panic(noConfigStorage) defer s.mu.RUnlock()
sections := make([]string, 0, len(s.sections))
for section := range s.sections {
sections = append(sections, section)
}
return sections
} }
// GetKeyList returns the keys in this section // HasSection returns true if section exists in the config.
func (defaultStorage) GetKeyList(section string) []string { func (s *defaultStorage) HasSection(section string) bool {
panic(noConfigStorage) s.mu.RLock()
defer s.mu.RUnlock()
_, hasSection := s.sections[section]
return hasSection
} }
// GetValue returns the key in section with a found flag // DeleteSection deletes the specified section.
func (defaultStorage) GetValue(section string, key string) (value string, found bool) { func (s *defaultStorage) DeleteSection(section string) {
panic(noConfigStorage) s.mu.Lock()
defer s.mu.Unlock()
delete(s.sections, section)
} }
// SetValue sets the value under key in section // GetKeyList returns the keys in this section.
func (defaultStorage) SetValue(section string, key string, value string) { func (s *defaultStorage) GetKeyList(section string) []string {
panic(noConfigStorage) s.mu.RLock()
defer s.mu.RUnlock()
theSection := s.sections[section]
keys := make([]string, 0, len(theSection))
for key := range theSection {
keys = append(keys, key)
}
return keys
} }
// DeleteKey removes the key under section // GetValue returns the key in section with a found flag.
func (defaultStorage) DeleteKey(section string, key string) bool { func (s *defaultStorage) GetValue(section string, key string) (value string, found bool) {
panic(noConfigStorage) s.mu.RLock()
defer s.mu.RUnlock()
theSection, hasSection := s.sections[section]
if !hasSection {
return "", false
}
value, hasValue := theSection[key]
return value, hasValue
} }
// Load the config from permanent storage func (s *defaultStorage) SetValue(section string, key string, value string) {
func (defaultStorage) Load() error { s.mu.Lock()
panic(noConfigStorage) defer s.mu.Unlock()
theSection, hasSection := s.sections[section]
if !hasSection {
theSection = map[string]string{}
s.sections[section] = theSection
}
theSection[key] = value
} }
// Save the config to permanent storage func (s *defaultStorage) DeleteKey(section string, key string) bool {
func (defaultStorage) Save() error { s.mu.Lock()
panic(noConfigStorage) defer s.mu.Unlock()
theSection, hasSection := s.sections[section]
if !hasSection {
return false
}
_, hasKey := theSection[key]
if !hasKey {
return false
}
delete(s.sections[section], key)
return true
}
func (s *defaultStorage) Load() error {
return nil
}
func (s *defaultStorage) Save() error {
return nil
} }
// Serialize the config into a string // Serialize the config into a string
func (defaultStorage) Serialize() (string, error) { func (s *defaultStorage) Serialize() (string, error) {
panic(noConfigStorage) s.mu.RLock()
defer s.mu.RUnlock()
j, err := json.Marshal(s.sections)
return string(j), err
} }
// Check the interface is satisfied // Check the interface is satisfied
var _ Storage = defaultStorage{} var _ Storage = newDefaultStorage()

View File

@ -0,0 +1,42 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDefaultStorage(t *testing.T) {
a := assert.New(t)
ds := newDefaultStorage()
section := "test"
key := "key"
val := "something"
ds.SetValue(section, key, val)
ds.SetValue("some other section", key, val)
v, hasVal := ds.GetValue(section, key)
a.True(hasVal)
a.Equal(val, v)
a.ElementsMatch([]string{section, "some other section"}, ds.GetSectionList())
a.True(ds.HasSection(section))
a.False(ds.HasSection("nope"))
a.Equal([]string{key}, ds.GetKeyList(section))
_, err := ds.Serialize()
a.NoError(err)
a.True(ds.DeleteKey(section, key))
a.False(ds.DeleteKey(section, key))
a.False(ds.DeleteKey("not there", key))
_, hasVal = ds.GetValue(section, key)
a.False(hasVal)
ds.DeleteSection(section)
a.False(ds.HasSection(section))
}