From eafcb2c8c36a5fac8216497329cf2c86c4e0eb8f Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Wed, 28 Jun 2023 20:06:34 +0200 Subject: [PATCH] replace template as substring --- cmd/revad/pkg/config/templates.go | 92 +++++++++++++++----------- cmd/revad/pkg/config/templates_test.go | 44 ++++++++++-- 2 files changed, 91 insertions(+), 45 deletions(-) diff --git a/cmd/revad/pkg/config/templates.go b/cmd/revad/pkg/config/templates.go index 38014d3f11..38e06e0cec 100644 --- a/cmd/revad/pkg/config/templates.go +++ b/cmd/revad/pkg/config/templates.go @@ -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 { @@ -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: @@ -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 { diff --git a/cmd/revad/pkg/config/templates_test.go b/cmd/revad/pkg/config/templates_test.go index a4b0b34ba7..b0f9b3f46f 100644 --- a/cmd/revad/pkg/config/templates_test.go +++ b/cmd/revad/pkg/config/templates_test.go @@ -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{ @@ -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 }}", }, }, }, @@ -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"]) }