diff --git a/changelog/unreleased/config-templ.md b/changelog/unreleased/config-templ.md new file mode 100644 index 0000000000..1a687795be --- /dev/null +++ b/changelog/unreleased/config-templ.md @@ -0,0 +1,8 @@ +Enhancement: support multiple templates in config entries + +This PR introduces support for config entries with multiple templates, +such as `parameter = "{{ vars.v1 }} foo {{ vars.v2 }}"`. +Previously, only one `{{ template }}` was allowed in a given +configuration entry. + +https://github.com/cs3org/reva/pull/4282 diff --git a/cmd/revad/pkg/config/templates.go b/cmd/revad/pkg/config/templates.go index d4d3e4ae52..559dc2a024 100644 --- a/cmd/revad/pkg/config/templates.go +++ b/cmd/revad/pkg/config/templates.go @@ -57,7 +57,7 @@ func applyTemplateStruct(l Lookuper, p setter, v reflect.Value) error { return nil } -// applyTemplateByType applies the template string to a generic type. +// applyTemplateByType (recursively) applies the template string to a generic type. func applyTemplateByType(l Lookuper, p setter, v reflect.Value) error { switch v.Kind() { case reflect.Array, reflect.Slice: @@ -119,31 +119,35 @@ func applyTemplateString(l Lookuper, p setter, v reflect.Value) error { panic("called applyTemplateString on non string type") } s := v.String() - tmpl, is := isTemplate(s) - if !is { + tmpl, more := isTemplate(s) + if !more { // nothing to do return nil } - key := keyFromTemplate(tmpl) - val, err := l.Lookup(key) - if err != nil { - return err - } - if val == nil { - return nil - } + for more { + key := keyFromTemplate(tmpl) + val, err := l.Lookup(key) + if err != nil { + return err + } + if val == nil { + // key was not found, just keep it but stop further parsing + break + } - new, err := replaceTemplate(s, tmpl, val) - if err != nil { - return err - } - str, ok := convertToString(new) - if !ok { - return fmt.Errorf("value %v cannot be converted as string in the template %s", val, new) + new, err := replaceTemplate(s, tmpl, val) + if err != nil { + return err + } + str, ok := convertToString(new) + if !ok { + return fmt.Errorf("value %v cannot be converted as string in the template %s", val, new) + } + s = str + tmpl, more = isTemplate(s) } - - p.SetValue(str) + p.SetValue(s) return nil } @@ -159,32 +163,50 @@ func applyTemplateInterface(l Lookuper, p setter, v reflect.Value) error { return applyTemplateByType(l, p, v.Elem()) } - tmpl, is := isTemplate(s) - if !is { + tmpl, more := isTemplate(s) + if !more { // nothing to do return nil } + replaced := 0 + for more { + key := keyFromTemplate(tmpl) + val, err := l.Lookup(key) + if err != nil { + return err + } + if val == nil { + // key was not found, just keep it but stop further parsing + break + } - key := keyFromTemplate(tmpl) - val, err := l.Lookup(key) - if err != nil { - return err - } - if val == nil { - return nil - } + new, err := replaceTemplate(s, tmpl, val) + if err != nil { + return err + } + + replaced++ + str, ok := convertToString(new) + tmpl, more = isTemplate(str) - new, err := replaceTemplate(s, tmpl, val) - if err != nil { - return err + if !more && replaced == 1 { + // a single template was to be replaced, preserve type + p.SetValue(new) + return nil + } + // if more than one template is to be replaced, use the string representation + if !ok { + return fmt.Errorf("value %v cannot be converted as string in the template %s", val, new) + } + s = str } - p.SetValue(new) + p.SetValue(s) 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 }}" + // the value was directly a template, e.g. "{{ grpc.services.gateway.address }}" return val, nil } // the value is of something like "something {{ template }} something else" @@ -227,7 +249,7 @@ func convertToString(val any) (string, bool) { return "", false } -var templateRegex = regexp.MustCompile("{{.{1,}}}") +var templateRegex = regexp.MustCompile("{{[^{}]+}}") func isTemplate(s string) (string, bool) { m := templateRegex.FindString(s) @@ -237,7 +259,10 @@ func isTemplate(s string) (string, bool) { func keyFromTemplate(s string) string { s = strings.TrimSpace(s) s = strings.TrimPrefix(s, "{{") - s = strings.TrimSuffix(s, "}}") + for strings.Index(s, "}}") > 0 { + //nolint: gocritic + s = s[:strings.Index(s, "}}")] // this is not offBy1 + } return "." + strings.TrimSpace(s) } diff --git a/cmd/revad/pkg/config/templates_test.go b/cmd/revad/pkg/config/templates_test.go index 9db0167bec..942301bb47 100644 --- a/cmd/revad/pkg/config/templates_test.go +++ b/cmd/revad/pkg/config/templates_test.go @@ -85,7 +85,8 @@ func TestApplyTemplate(t *testing.T) { Vars: Vars{ "db_username": "root", "db_password": "secretpassword", - "integer": 10, + "proto": "http", + "port": 1000, }, GRPC: &GRPC{ Services: map[string]ServicesConfig{ @@ -95,10 +96,12 @@ func TestApplyTemplate(t *testing.T) { Config: map[string]any{ "drivers": map[string]any{ "sql": map[string]any{ - "db_username": "{{ vars.db_username }}", - "db_password": "{{ vars.db_password }}", - "key": "value", - "int": "{{ vars.integer }}", + "db_username": "{{ vars.db_username }}", + "db_password": "{{ vars.db_password }}", + "key": "value", + "port": "{{ vars.port }}", + "user_and_token": "{{ vars.db_username }} and {{.Token}}", + "templated_path": "/path/{{.Token}}", }, }, }, @@ -110,7 +113,7 @@ func TestApplyTemplate(t *testing.T) { Config: map[string]any{ "drivers": map[string]any{ "sql": map[string]any{ - "db_host": "http://localhost:{{ vars.integer }}", + "db_host": "{{ vars.proto }}://localhost:{{ vars.port }}", }, }, }, @@ -124,12 +127,14 @@ func TestApplyTemplate(t *testing.T) { assert.ErrorIs(t, err, nil) assert.Equal(t, "localhost:1901", cfg2.Shared.GatewaySVC) assert.Equal(t, map[string]any{ - "db_username": "root", - "db_password": "secretpassword", - "key": "value", - "int": 10, + "db_username": "root", + "db_password": "secretpassword", + "key": "value", + "port": 1000, + "user_and_token": "root and {{.Token}}", + "templated_path": "/path/{{.Token}}", }, cfg2.GRPC.Services["authregistry"][0].Config["drivers"].(map[string]any)["sql"]) assert.Equal(t, map[string]any{ - "db_host": "http://localhost:10", + "db_host": "http://localhost:1000", }, cfg2.GRPC.Services["other"][0].Config["drivers"].(map[string]any)["sql"]) }