Config system

(see also issue #21)
Removes synchronizing set config keys to freezelog,
since the system is the preferred way to persist configuration.
This commit is contained in:
rubenseyer 2018-02-12 22:19:42 +01:00
parent 6f8c2bf2f5
commit 40ef449f6e
11 changed files with 263 additions and 88 deletions

View file

@ -5,6 +5,7 @@
package serverconf
import (
"path/filepath"
"strconv"
"sync"
)
@ -20,29 +21,34 @@ var defaultCfg = map[string]string{
"RememberChannel": "true",
"WelcomeText": "Welcome to this server running <b>Grumble</b>.",
"SendVersion": "true",
"LogPath": "grumble.log",
"CertPath": "cert.pem",
"KeyPath": "key.pem",
}
type Config struct {
cfgMap map[string]string
mutex sync.RWMutex
fallbackMap map[string]string
persistentMap map[string]string
mutex sync.RWMutex
}
// Create a new Config using cfgMap as the intial internal config map.
// If cfgMap is nil, ConfigWithMap will create a new config map.
func New(cfgMap map[string]string) *Config {
if cfgMap == nil {
cfgMap = make(map[string]string)
// New returns a new Config using persistentMap as the initial internal config map.
// The map persistentMap may not be reused. If set to nil, a new map is created.
// Optionally, defaults may be passed in fallbackMap. This map is only read, not written.
func New(persistentMap, fallbackMap map[string]string) *Config {
if persistentMap == nil {
persistentMap = make(map[string]string)
}
return &Config{cfgMap: cfgMap}
return &Config{persistentMap: persistentMap, fallbackMap: fallbackMap}
}
// GetAll gets a copy of the Config's internal config map
func (cfg *Config) GetAll() (all map[string]string) {
// GetAllPersistent returns a copy of the internal persistent key-value map.
func (cfg *Config) GetAllPersistent() (all map[string]string) {
cfg.mutex.RLock()
defer cfg.mutex.RUnlock()
all = make(map[string]string)
for k, v := range cfg.cfgMap {
for k, v := range cfg.persistentMap {
all[k] = v
}
return
@ -52,14 +58,14 @@ func (cfg *Config) GetAll() (all map[string]string) {
func (cfg *Config) Set(key string, value string) {
cfg.mutex.Lock()
defer cfg.mutex.Unlock()
cfg.cfgMap[key] = value
cfg.persistentMap[key] = value
}
// Reset the value of a config key
func (cfg *Config) Reset(key string) {
cfg.mutex.Lock()
defer cfg.mutex.Unlock()
delete(cfg.cfgMap, key)
delete(cfg.persistentMap, key)
}
// StringValue gets the value of a specific config key encoded as a string
@ -67,7 +73,12 @@ func (cfg *Config) StringValue(key string) (value string) {
cfg.mutex.RLock()
defer cfg.mutex.RUnlock()
value, exists := cfg.cfgMap[key]
value, exists := cfg.persistentMap[key]
if exists {
return value
}
value, exists = cfg.fallbackMap[key]
if exists {
return value
}
@ -80,7 +91,7 @@ func (cfg *Config) StringValue(key string) (value string) {
return ""
}
// IntValue gets the value of a speific config key as an int
// Get the value of a specific config key as an int
func (cfg *Config) IntValue(key string) (intval int) {
str := cfg.StringValue(key)
intval, _ = strconv.Atoi(str)
@ -94,9 +105,19 @@ func (cfg *Config) Uint32Value(key string) (uint32val uint32) {
return uint32(uintval)
}
// BoolValue gets the value fo a sepcific config key as a bool
// Get the value of a specific config key as a bool
func (cfg *Config) BoolValue(key string) (boolval bool) {
str := cfg.StringValue(key)
boolval, _ = strconv.ParseBool(str)
return
}
// Get the value of a specific config key as a path,
// joined with the path in rel if not absolute.
func (cfg *Config) PathValue(key string, rel string) (path string) {
str := cfg.StringValue(key)
if filepath.IsAbs(str) {
return filepath.Clean(str)
}
return filepath.Join(rel, str)
}

View file

@ -9,7 +9,7 @@ import (
)
func TestIntValue(t *testing.T) {
cfg := New(nil)
cfg := New(nil, nil)
cfg.Set("Test", "13")
if cfg.IntValue("Test") != 13 {
t.Errorf("Expected 13")
@ -17,7 +17,7 @@ func TestIntValue(t *testing.T) {
}
func TestFloatAsInt(t *testing.T) {
cfg := New(nil)
cfg := New(nil, nil)
cfg.Set("Test", "13.4")
if cfg.IntValue("Test") != 0 {
t.Errorf("Expected 0")
@ -25,14 +25,14 @@ func TestFloatAsInt(t *testing.T) {
}
func TestDefaultValue(t *testing.T) {
cfg := New(nil)
cfg := New(nil, nil)
if cfg.IntValue("MaxBandwidth") != 72000 {
t.Errorf("Expected 72000")
}
}
func TestBoolValue(t *testing.T) {
cfg := New(nil)
cfg := New(nil, nil)
cfg.Set("DoStuffOnStartup", "true")
if cfg.BoolValue("DoStuffOnStartup") != true {
t.Errorf("Expected true")

54
pkg/serverconf/file.go Normal file
View file

@ -0,0 +1,54 @@
package serverconf
import (
"strconv"
)
type cfg interface {
// GlobalMap returns a copy of the top-level (global) configuration map.
GlobalMap() map[string]string
}
type ConfigFile struct {
cfg
}
func NewConfigFile(path string) (*ConfigFile, error) {
var f cfg
f, err := newinicfg(path)
if err != nil {
return nil, err
}
return &ConfigFile{f}, nil
}
// GlobalConfig returns a new *serverconf.Config representing the top-level
// (global) configuration.
func (c *ConfigFile) GlobalConfig() *Config {
return New(nil, c.GlobalMap())
}
// ServerConfig returns a new *serverconf.Config with the fallback representing
// the global configuration with server-specific values incremented by id.
// Optionally a persistent map which has priority may be passed. This map
// is consumed and cannot be reused.
func (c *ConfigFile) ServerConfig(id int64, persistentMap map[string]string) *Config {
m := c.GlobalMap()
// Some server specific values from the global config must be offset.
// These are read differently by the server as well.
if v, ok := m["Port"]; ok {
i, err := strconv.ParseInt(v, 10, 64)
if err == nil {
m["Port"] = strconv.FormatInt(i+id-1, 10)
}
}
if v, ok := m["WebPort"]; ok {
i, err := strconv.ParseInt(v, 10, 64)
if err == nil {
m["WebPort"] = strconv.FormatInt(i+id-1, 10)
}
}
return New(persistentMap, m)
}

View file

@ -0,0 +1,83 @@
package serverconf
import (
"gopkg.in/ini.v1"
)
type inicfg struct {
file *ini.File
}
func newinicfg(path string) (*inicfg, error) {
file, err := ini.LoadSources(ini.LoadOptions{AllowBooleanKeys: true, UnescapeValueDoubleQuotes: true}, path)
if err != nil {
return nil, err
}
file.BlockMode = false // read only, avoid locking
return &inicfg{file}, nil
}
func (f *inicfg) GlobalMap() map[string]string {
return f.file.Section("").KeysHash()
}
// todo(rubenseyer): not all of these even work.. make sure to implement them
var DefaultConfigFile = `# Grumble configuration file.
#
# The commented out settings represent the defaults.
# Options here may be overridden by virtual server specific configuration.
# Make sure to enclose values containing # or ; in double quotes or backticks.
# Address to bind the listeners to.
#Address = 0.0.0.0
# Port is the port to bind the native Mumble protocol to.
# WebPort is the port to bind the WebSocket Mumble protocol to.
# They are incremented for each virtual server.
#Port = 64738
#WebPort = 443
# "Message of the day" HTML string sent to connecting clients.
#WelcomeText = "Welcome to this server running <b>Grumble</b>."
# Maximum bandwidth (in bits per second) per client for voice.
# Grumble does not yet enforce this limit, but some clients nicely follow it.
#MaxBandwidth = 72000
# Maximum number of concurrent clients.
#MaxUsers = 1000
#MaxUsersPerChannel = 0
#MaxTextMessageLength = 5000
#MaxImageMessageLength = 131072
#AllowHTML
# DefaultChannel is the channel (by ID) new users join.
# The root channel is the default.
#DefaultChannel = 0
# Whether users will rejoin the last channel they were in.
#RememberChannel
# Whether to include server version and server os in ping response.
#SendVersion
#SendOSInfo
# Path to the log file (relative to the data directory).
#LogPath = grumble.log
# Path to TLS certificate and key (relative to the data directory).
# The certificate needs to have the entire chain concatenated to be validate.
# If these paths do not exist, Grumble will autogenerate a certificate.
#CertPath = cert.pem
#KeyPath = key.pem
# Options for public server registration.
# All of these have to be set to make the server public.
# RegisterName additionally sets the name of the root channel.
# RegisterPassword is a simple, arbitrary secret to guard your registration. Don't lose it.
#RegisterName =
#RegisterHost =
#RegisterPassword =
#RegisterWebUrl =
`