Skip to content

Commit

Permalink
replace template as substring
Browse files Browse the repository at this point in the history
  • Loading branch information
gmgigi96 committed Jun 28, 2023
1 parent 2c02e04 commit eafcb2c
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 45 deletions.
92 changes: 54 additions & 38 deletions cmd/revad/pkg/config/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
package config

import (
"fmt"
"reflect"
"regexp"
"strconv"
"strings"

"github.com/pkg/errors"
)

func applyTemplateStruct(l Lookuper, p setter, v reflect.Value) error {
Expand Down Expand Up @@ -53,8 +54,6 @@ func applyTemplateStruct(l Lookuper, p setter, v reflect.Value) error {

func applyTemplateByType(l Lookuper, p setter, v reflect.Value) error {
switch v.Kind() {
case reflect.String:
return applyTemplateString(l, p, v)
case reflect.Array, reflect.Slice:
return applyTemplateList(l, p, v)
case reflect.Struct:
Expand Down Expand Up @@ -109,57 +108,74 @@ func applyTemplateInterface(l Lookuper, p setter, v reflect.Value) error {
return applyTemplateByType(l, p, v.Elem())
}

if !isTemplate(s) {
tmpl, is := isTemplate(s)
if !is {
// nothing to do
return nil
}

key := keyFromTemplate(s)
key := keyFromTemplate(tmpl)
val, err := l.Lookup(key)
if err != nil {
return err
}

p.SetValue(val)
return nil
}

func applyTemplateString(l Lookuper, p setter, v reflect.Value) error {
if v.Kind() != reflect.String {
panic("called applyTemplateString on non string type")
}

s := v.Interface().(string)
if !isTemplate(s) {
// nothing to do
return nil
}

if !v.CanSet() {
panic("value is not addressable")
}

key := keyFromTemplate(s)
val, err := l.Lookup(key)
new, err := replaceTemplate(s, tmpl, val)
if err != nil {
return err
}
if val == nil {
return nil
}
p.SetValue(new)
return nil
}

str, ok := val.(string)
if ok {
p.SetValue(str)
return nil
func replaceTemplate(original, tmpl string, val any) (any, error) {
if strings.TrimSpace(original) == tmpl {
// the value was directly a template, i.e. "{{ grpc.services.gateway.address }}"
return val, nil
}
// the value is of something like "something {{ template }} something else"
// in this case we need to replace the template string with the value, converted
// as string in the original val
s, ok := convertToString(val)
if !ok {
return nil, fmt.Errorf("value %v cannot be converted as string in the template %s", val, original)
}
return strings.Replace(original, tmpl, s, 1), nil
}

return errors.New("value cannot be set on a non string type")
func convertToString(val any) (string, bool) {
switch v := val.(type) {
case string:
return v, true
case int:
return strconv.FormatInt(int64(v), 10), true
case int8:
return strconv.FormatInt(int64(v), 10), true
case int16:
return strconv.FormatInt(int64(v), 10), true
case int32:
return strconv.FormatInt(int64(v), 10), true
case uint:
return strconv.FormatUint(uint64(v), 10), true
case uint8:
return strconv.FormatUint(uint64(v), 10), true
case uint16:
return strconv.FormatUint(uint64(v), 10), true
case uint32:
return strconv.FormatUint(uint64(v), 10), true
case uint64:
return strconv.FormatUint(uint64(v), 10), true
case bool:
return strconv.FormatBool(v), true
}
return "", false
}

func isTemplate(s string) bool {
s = strings.TrimSpace(s)
return strings.HasPrefix(s, "{{") && strings.HasSuffix(s, "}}")
var templateRegex = regexp.MustCompile("{{.{1,}}}")

func isTemplate(s string) (string, bool) {
m := templateRegex.FindString(s)
return m, m != ""
}

func keyFromTemplate(s string) string {
Expand Down
44 changes: 37 additions & 7 deletions cmd/revad/pkg/config/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,31 @@ func TestApplyTemplate(t *testing.T) {
},
},
},
"other": {
{
Address: "localhost:1902",
Config: map[string]any{
"drivers": map[string]any{
"static": map[string]any{
"demo": "https://{{ grpc.services.authprovider.address }}/data",
},
},
},
},
},
},
},
}
err := cfg1.ApplyTemplates(cfg1)
assert.ErrorIs(t, err, nil)
assert.Equal(t, cfg1.GRPC.Services["authregistry"][0].Config["drivers"].(map[string]any)["static"].(map[string]any)["demo"], "localhost:1900")
assert.Equal(t, "localhost:1900", cfg1.GRPC.Services["authregistry"][0].Config["drivers"].(map[string]any)["static"].(map[string]any)["demo"])
assert.Equal(t, "https://localhost:1900/data", cfg1.GRPC.Services["other"][0].Config["drivers"].(map[string]any)["static"].(map[string]any)["demo"])

cfg2 := &Config{
Vars: Vars{
"db_username": "root",
"db_password": "secretpassword",
"integer": 10,
},
GRPC: &GRPC{
Services: map[string]ServicesConfig{
Expand All @@ -68,6 +82,19 @@ func TestApplyTemplate(t *testing.T) {
"db_username": "{{ vars.db_username }}",
"db_password": "{{ vars.db_password }}",
"key": "value",
"int": "{{ vars.integer }}",
},
},
},
},
},
"other": {
{
Address: "localhost:1902",
Config: map[string]any{
"drivers": map[string]any{
"sql": map[string]any{
"db_host": "http://localhost:{{ vars.integer }}",
},
},
},
Expand All @@ -79,10 +106,13 @@ func TestApplyTemplate(t *testing.T) {

err = cfg2.ApplyTemplates(cfg2)
assert.ErrorIs(t, err, nil)
assert.Equal(t, cfg2.GRPC.Services["authregistry"][0].Config["drivers"].(map[string]any)["sql"],
map[string]any{
"db_username": "root",
"db_password": "secretpassword",
"key": "value",
})
assert.Equal(t, map[string]any{
"db_username": "root",
"db_password": "secretpassword",
"key": "value",
"int": 10,
}, cfg2.GRPC.Services["authregistry"][0].Config["drivers"].(map[string]any)["sql"])
assert.Equal(t, map[string]any{
"db_host": "http://localhost:10",
}, cfg2.GRPC.Services["other"][0].Config["drivers"].(map[string]any)["sql"])
}

0 comments on commit eafcb2c

Please sign in to comment.