Skip to content

Commit

Permalink
[#38]: feat: Allow to include nested configurations
Browse files Browse the repository at this point in the history
  • Loading branch information
rustatian authored Nov 8, 2023
2 parents 0c0c6c5 + f022c93 commit f03bb49
Show file tree
Hide file tree
Showing 10 changed files with 761 additions and 605 deletions.
31 changes: 31 additions & 0 deletions expand.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package config

import (
"strings"

"github.com/spf13/viper"
)

// ExpandVal replaces ${var} or $var in the string based on the mapping function.
Expand Down Expand Up @@ -83,6 +85,35 @@ func getShellName(s string) (string, int) {
return s[:i], i
}

func expandEnvViper(v *viper.Viper) {
for _, key := range v.AllKeys() {
val := v.Get(key)
switch t := val.(type) {
case string:
// for string expand it
v.Set(key, parseEnvDefault(t))
case []any:
// for slice -> check if it's a slice of strings
strArr := make([]string, 0, len(t))
for i := 0; i < len(t); i++ {
if valStr, ok := t[i].(string); ok {
strArr = append(strArr, parseEnvDefault(valStr))
continue
}

v.Set(key, val)
}

// we should set the whole array
if len(strArr) > 0 {
v.Set(key, strArr)
}
default:
v.Set(key, val)
}
}
}

// isShellSpecialVar reports whether the character identifies a special
// shell variable such as $*.
func isShellSpecialVar(c uint8) bool {
Expand Down
65 changes: 65 additions & 0 deletions include.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package config

import (
"path/filepath"
"strings"

"github.com/roadrunner-server/errors"
"github.com/spf13/viper"
)

func getConfiguration(path, prefix string) (map[string]any, string, error) {
v := viper.New()
v.AutomaticEnv()
v.SetEnvPrefix(prefix)
v.SetConfigFile(path)
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
err := v.ReadInConfig()
if err != nil {
return nil, "", err
}

// get configuration version
ver := v.Get(versionKey)
if ver == nil {
return nil, "", errors.Str("rr configuration file should contain a version e.g: version: 2.7")
}

if _, ok := ver.(string); !ok {
return nil, "", errors.Errorf("type of version should be string, actual: %T", ver)
}

// automatically inject ENV variables using ${ENV} pattern
expandEnvViper(v)

return v.AllSettings(), ver.(string), nil
}

func (p *Plugin) handleInclude(rootVersion string) error {
ifiles := p.viper.Get(includeKey)
if ifiles != nil {
if _, ok := ifiles.([]string); !ok {
return errors.Str("include should be an array of strings")
}

includeFiles := ifiles.([]string)
for _, file := range includeFiles {
dir, _ := filepath.Split(p.Path)
config, version, err := getConfiguration(filepath.Join(dir, file), p.Prefix)
if err != nil {
return err
}

if version != rootVersion {
return errors.Str("version in included file must be the same as in root")
}

// overriding configuration
for key, val := range config {
p.viper.Set(key, val)
}
}
}

return nil
}
49 changes: 18 additions & 31 deletions plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
const (
PluginName string = "config"
versionKey string = "version"
includeKey string = "include"

defaultConfigVersion string = "3"
prevConfigVersion string = "2.7"
Expand All @@ -29,7 +30,7 @@ type Plugin struct {
Type string
ReadInCfg []byte
// user defined Flags in the form of <option>.<key> = <value>
// which overwrites initial config key
// which overwrites initial a config key
Flags []string
// ExperimentalFeatures enables experimental features
ExperimentalFeatures bool
Expand Down Expand Up @@ -69,32 +70,7 @@ func (p *Plugin) Init() error {
}

// automatically inject ENV variables using ${ENV} pattern
for _, key := range p.viper.AllKeys() {
val := p.viper.Get(key)
switch t := val.(type) {
case string:
// for string just expand it
p.viper.Set(key, parseEnvDefault(t))
case []any:
// for slice -> check if it's slice of strings
strArr := make([]string, 0, len(t))
for i := 0; i < len(t); i++ {
if valStr, ok := t[i].(string); ok {
strArr = append(strArr, parseEnvDefault(valStr))
continue
}

p.viper.Set(key, val)
}

// we should set the whole array
if len(strArr) > 0 {
p.viper.Set(key, strArr)
}
default:
p.viper.Set(key, val)
}
}
expandEnvViper(p.viper)

// override config Flags
if len(p.Flags) > 0 {
Expand All @@ -107,14 +83,25 @@ func (p *Plugin) Init() error {
}
}

// get configuration version
// get a configuration version
// we should perform this check after all overrides
ver := p.viper.Get(versionKey)
if ver == nil {
return errors.Str("rr configuration file should contain a version e.g: version: 2.7")
return errors.Str("rr configuration file should contain a version e.g: version: 3")
}

if _, ok := ver.(string); !ok {
return errors.E(op, errors.Errorf("version should be a string, actual type is: %T", ver))
return errors.E(op, errors.Errorf("version should be a string: `version: \"3\"`, actual type is: %T", ver))
}

// hide includes under the experimental flag
// 'include' is an experimental feature
// should be here because we need to perform all overrides before
if p.Experimental() {
err = p.handleInclude(ver.(string))
if err != nil {
return errors.E(op, err)
}
}

// RR includes the config feature by default starting from v2.7.
Expand All @@ -131,7 +118,7 @@ func (p *Plugin) Init() error {
return nil
}

// Overwrite overwrites existing config with provided values
// Overwrite overwriting existing config with provided values
func (p *Plugin) Overwrite(values map[string]any) error {
for key, value := range values {
p.viper.Set(key, value)
Expand Down
Loading

0 comments on commit f03bb49

Please sign in to comment.