feat(config): interpolate recursively in interpolated map
Cadoles/bouncer/pipeline/head This commit looks good Details

This commit is contained in:
wpetit 2024-05-24 12:31:09 +02:00
parent 544326a4b7
commit 83b42a04c7
5 changed files with 123 additions and 19 deletions

View File

@ -65,7 +65,7 @@ func (s *Server) bootstrapProxies(ctx context.Context) error {
for layerName, layerConfig := range proxyConfig.Layers {
layerType := store.LayerType(layerConfig.Type)
layerOptions := store.LayerOptions(layerConfig.Options)
layerOptions := store.LayerOptions(layerConfig.Options.Data)
if _, err := layerRepo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions); err != nil {
return errors.WithStack(err)
@ -109,7 +109,7 @@ func (s *Server) validateBootstrap(ctx context.Context) error {
}
rawOptions := func(opts config.InterpolatedMap) map[string]any {
return opts
return opts.Data
}(layerConf.Options)
if err := schema.Validate(ctx, layerOptionsSchema, rawOptions); err != nil {

View File

@ -101,7 +101,10 @@ func (ib *InterpolatedBool) UnmarshalYAML(value *yaml.Node) error {
return nil
}
type InterpolatedMap map[string]interface{}
type InterpolatedMap struct {
Data map[string]interface{}
env map[string]string
}
func (im *InterpolatedMap) UnmarshalYAML(value *yaml.Node) error {
var data map[string]interface{}
@ -110,24 +113,50 @@ func (im *InterpolatedMap) UnmarshalYAML(value *yaml.Node) error {
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into map", value.Value, value.Line)
}
for key, value := range data {
strVal, ok := value.(string)
if !ok {
continue
}
if match := reVar.FindStringSubmatch(strVal); len(match) > 0 {
strVal = os.Getenv(match[1])
}
data[key] = strVal
}
*im = data
im.Data = im.interpolateRecursive(data).(map[string]any)
return nil
}
func (im *InterpolatedMap) interpolateRecursive(data any) any {
if im.env == nil {
im.env = osEnvironment()
}
switch typ := data.(type) {
case map[string]any:
for key, value := range typ {
typ[key] = im.interpolateRecursive(value)
}
case string:
if match := reVar.FindStringSubmatch(typ); len(match) > 0 {
value, exists := im.env[match[1]]
if !exists {
data = ""
}
data = value
}
case []any:
for idx := range typ {
typ[idx] = im.interpolateRecursive(typ[idx])
}
}
return data
}
func osEnvironment() map[string]string {
environ := os.Environ()
env := make(map[string]string, len(environ))
for _, key := range environ {
env[key] = os.Getenv(key)
}
return env
}
type InterpolatedStringSlice []string
func (iss *InterpolatedStringSlice) UnmarshalYAML(value *yaml.Node) error {

View File

@ -0,0 +1,69 @@
package config
import (
"fmt"
"os"
"testing"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
)
func TestInterpolatedMap(t *testing.T) {
type testCase struct {
Path string
Env map[string]string
Assert func(t *testing.T, parsed InterpolatedMap)
}
testCases := []testCase{
{
Path: "testdata/environment/interpolated-map-1.yml",
Env: map[string]string{
"TEST_PROP1": "foo",
"TEST_SUB_PROP1": "bar",
"TEST_SUB2_PROP1": "baz",
},
Assert: func(t *testing.T, parsed InterpolatedMap) {
if e, g := "foo", parsed.Data["prop1"]; e != g {
t.Errorf("parsed.Data[\"prop1\"]: expected '%v', got '%v'", e, g)
}
if e, g := "bar", parsed.Data["sub"].(map[string]any)["subProp1"]; e != g {
t.Errorf("parsed.Data[\"sub\"][\"subProp1\"]: expected '%v', got '%v'", e, g)
}
if e, g := "baz", parsed.Data["sub2"].(map[string]any)["sub2Prop1"].([]any)[0]; e != g {
t.Errorf("parsed.Data[\"sub2\"][\"sub2Prop1\"][0]: expected '%v', got '%v'", e, g)
}
if e, g := "test", parsed.Data["sub2"].(map[string]any)["sub2Prop1"].([]any)[1]; e != g {
t.Errorf("parsed.Data[\"sub2\"][\"sub2Prop1\"][1]: expected '%v', got '%v'", e, g)
}
},
},
}
for idx, tc := range testCases {
t.Run(fmt.Sprintf("Case #%d", idx), func(t *testing.T) {
data, err := os.ReadFile(tc.Path)
if err != nil {
t.Fatalf("%+v", errors.WithStack(err))
}
var interpolatedMap InterpolatedMap
if tc.Env != nil {
interpolatedMap.env = tc.Env
}
if err := yaml.Unmarshal(data, &interpolatedMap); err != nil {
t.Fatalf("%+v", errors.WithStack(err))
}
if tc.Assert != nil {
tc.Assert(t, interpolatedMap)
}
})
}
}

View File

@ -17,9 +17,9 @@ func (c *BasicAuthConfig) CredentialsMap() map[string]string {
return map[string]string{}
}
credentials := make(map[string]string, len(*c.Credentials))
credentials := make(map[string]string, len(c.Credentials.Data))
for k, v := range *c.Credentials {
for k, v := range c.Credentials.Data {
credentials[k] = fmt.Sprintf("%v", v)
}

View File

@ -0,0 +1,6 @@
prop1: "${TEST_PROP1}"
prop2: 1
sub:
subProp1: "${TEST_SUB_PROP1}"
sub2:
sub2Prop1: ["${TEST_SUB2_PROP1}", "test"]