Skip to content

Commit

Permalink
sq
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmasek committed Jan 2, 2025
1 parent 36b88a0 commit 60f7b0b
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 55 deletions.
142 changes: 90 additions & 52 deletions conf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,58 @@ const ENV_VAR_PREFIX = "BEACON_"

type Config struct {
envPrefix string
parents []string
settings map[string]interface{}
// manually set, should take precedence
overrides map[string]interface{}
}

func (config *Config) AllSettings() map[string]interface{} {
return config.settings
settings := config.settings
for _, parent := range config.parents {
if settings == nil {
return nil
}
settingsSub, ok := settings[parent].(map[string]interface{})
if ok {
settings = settingsSub
} else {
return nil
}
}
return settings
}

func (config *Config) keyToEnvVar(key string) string {
key = strings.ReplaceAll(key, ".", "_")
// todo: nested access
if strings.Contains(key, ".") {
panic("nested access with `.` not implemented")
}
// key = strings.ReplaceAll(key, ".", "_")
key = config.envPrefix + strings.Join(config.parents, "_") + "_" + key
key = strings.ToUpper(key)
return config.envPrefix + key
return key
}

func (config *Config) get(key string) interface{} {
// TODO: dot notation for nested access not implemented
val, ok := config.overrides[key]
if config == nil {
return nil
}
// todo: nested access
if strings.Contains(key, ".") {
panic("nested access with `.` not implemented")
}
overrides := config.overrides
for _, parent := range config.parents {
if overrides == nil {
break
}
overridesSub, ok := overrides[parent].(map[string]interface{})
if ok {
overrides = overridesSub
}
}
val, ok := overrides[key]
if ok {
return val
}
Expand All @@ -46,8 +80,20 @@ func (config *Config) get(key string) interface{} {
if isSet {
return envVal
}
val, ok = config.settings[key]
// TODO: do we want default or nil?
settings := config.settings
for _, parent := range config.parents {
if settings == nil {
return nil
}
settingsSub, ok := settings[parent].(map[string]interface{})
if ok {
settings = settingsSub
} else {
return nil
}
}
val, ok = settings[key]

if !ok {
return nil
}
Expand Down Expand Up @@ -90,7 +136,7 @@ var boolyStrings = map[string]bool{
}

func (config *Config) GetBool(key string) bool {
val := config.settings[key]
val := config.get(key)
boolVal, isBool := val.(bool)
if isBool {
return boolVal
Expand Down Expand Up @@ -120,55 +166,54 @@ func (config *Config) GetDuration(key string) time.Duration {
}

func (config *Config) Set(key string, value interface{}) {
// TODO: dot notation for nested access not implemented
// todo: nested access
if strings.Contains(key, ".") {
panic("nested access with `.` not implemented")
}
if len(config.parents) > 0 {
// todo: sub configs are read only for now
// not sure what to do with them atm
panic("Cannot set values for .Sub configs")
}
config.overrides[key] = value
}

func (config *Config) SetDefault(key string, value interface{}) {
if !config.IsSet(key) {
// todo: nested access
if strings.Contains(key, ".") {
panic("nested access with `.` not implemented")
}
if len(config.parents) > 0 {
// todo: sub configs are read only for now
// not sure what to do with them atm
panic("Cannot set values for .Sub configs")
}
val := config.get(key)
if val == nil {
config.settings[key] = value
}
}

func (config *Config) IsSet(key string) bool {
_, ok := config.overrides[key]
if ok {
return true
}
_, isSet := os.LookupEnv(config.keyToEnvVar(key))
if isSet {
return true
}
_, ok = config.settings[key]
return ok
// here if the key exists but has nil value we return false
// now this is kinda stupid but it kinda makes sense for our use-cases
// I don't have a solution that would be simple to do and work well atm
// todo: probably want to rethink the whole Config anyway
val := config.get(key)
return val != nil
}

func subSettings(parent map[string]interface{}, key string) map[string]interface{} {
val, ok := parent[key]
if !ok {
return map[string]interface{}{}
func (config *Config) Sub(key string) *Config {
// todo: kinda weird implementation, not sure how I want to use this yet
if !config.IsSet(key) {
return nil
}
child, ok := val.(map[string]interface{})
// if config[key] is not a dict, it is a scalar
// to keep the same structure we convert it to map
// with the scalar as key and no value
// todo: maybe better design could be pondered here
if !ok {
child = make(map[string]interface{})
child[val.(string)] = struct{}{}
return &Config{
envPrefix: config.envPrefix,
parents: append(config.parents, key),
settings: config.settings,
overrides: config.overrides,
}
return child
}

func (config *Config) Sub(key string) *Config {
// TODO: doing a quick implementation here
// either needs more testing or remove this method
sub := &Config{}
sub.envPrefix = config.envPrefix + strings.ToUpper(key) + "_"
sub.overrides = subSettings(config.overrides, key)
sub.settings = subSettings(config.settings, key)

return sub
}

//go:embed config.default.yaml
Expand Down Expand Up @@ -239,20 +284,13 @@ func DefaultConfigFrom(configFile string) (*Config, error) {
func NewConfig() *Config {
config := &Config{
envPrefix: ENV_VAR_PREFIX,
parents: []string{},
settings: make(map[string]interface{}),
overrides: make(map[string]interface{}),
}
return config
}

// Minimal working config
func BaseConfig() *Config {
config := NewConfig()
config.settings["services"] = []string{}
config.settings["email"] = map[string]string{}
return config
}

// Setup config to use ENV variables and read specified config file.
func setupConfig(configFile string) (*Config, error) {
log.Printf("reading config from %q\n", configFile)
Expand Down
51 changes: 51 additions & 0 deletions conf/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

func TestLoadConfigFrom(t *testing.T) {
Expand Down Expand Up @@ -38,3 +39,53 @@ func TestEnvVariablesOverwrite(t *testing.T) {
assert.Equal(t, "my-new-prefix", emailConfig.GetString("prefix"), emailConfig)
assert.Equal(t, 123, emailConfig.GetInt("smtp_port"), emailConfig)
}

func TestConfigGet(t *testing.T) {
config := NewConfig()
data := `
bedroom: bed
kitchen:
fruit: apple
vegetable: cucumber
table:
`
err := yaml.Unmarshal([]byte(data), config.settings)
require.NoError(t, err)
require.NotNil(t, config)
t.Log(config)

require.True(t, config.IsSet("bedroom"))
require.True(t, config.IsSet("kitchen"))
require.Equal(t, "bed", config.GetString("bedroom"))

settings := config.AllSettings()
require.Contains(t, settings, "bedroom")
require.Contains(t, settings, "kitchen")

kitchen := config.get("kitchen")
require.NotNil(t, kitchen)
t.Log(kitchen)

kitchenConfig := config.Sub("kitchen")
require.NotNil(t, kitchenConfig)
t.Log(kitchenConfig)

require.True(t, kitchenConfig.IsSet("fruit"))
require.True(t, kitchenConfig.IsSet("vegetable"))
require.Equal(t, "apple", kitchenConfig.GetString("fruit"))
// todo: Config rethink... the following will return false by design
// but the key exists!
// require.True(t, kitchenConfig.IsSet("table"))
// but for our use case it is more important that the following works:
settings = kitchenConfig.AllSettings()
require.Contains(t, settings, "fruit")
require.Contains(t, settings, "vegetable")
require.Contains(t, settings, "table")
}

func TestConfigSet(t *testing.T) {
config := NewConfig()
config.Set("foo", true)
foo := config.GetBool("foo")
require.True(t, foo)
}
6 changes: 4 additions & 2 deletions handlers/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ func (sm SMTPMailer) Send(reports []ServiceReport, emailConfig *conf.Config) err
return err
}

emailConfig.SetDefault("prefix", "")
prefix := emailConfig.GetString("prefix")
prefix := ""
if emailConfig.IsSet("prefix") {
prefix = emailConfig.GetString("prefix")
}
// add whitespace after prefix if it exists and is not included already
if prefix != "" && !strings.HasSuffix(prefix, " ") {
prefix = prefix + " "
Expand Down
2 changes: 1 addition & 1 deletion handlers/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func DoReportTask(db storage.Storage, config *conf.Config, now time.Time) error
// TODO: need better way so check if should send email
// currently the config.IsSet handles the "config-file path"
// and allows overwrite via config "send-mail" variable for CLI usage
shouldSendEmail := config.IsSet("email.smtp_password")
shouldSendEmail := config.Sub("email").IsSet("smtp_password")
if config.IsSet("send-mail") {
shouldSendEmail = config.GetBool("send-mail")
}
Expand Down

0 comments on commit 60f7b0b

Please sign in to comment.