Skip to content

Commit

Permalink
Support multiple templates in config entries (#4282)
Browse files Browse the repository at this point in the history
* Support multiple templates in config entries

* Fixed docker-compose-related rules

* Partially handle case of not found tokens

* Revert "Fixed docker-compose-related rules"

This reverts commit bd13e11.
  • Loading branch information
glpatcern authored Oct 30, 2023
1 parent 5b0a827 commit 783c0c5
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 48 deletions.
8 changes: 8 additions & 0 deletions changelog/unreleased/config-templ.md
Original file line number Diff line number Diff line change
@@ -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
99 changes: 62 additions & 37 deletions cmd/revad/pkg/config/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
}

Expand All @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}

Expand Down
27 changes: 16 additions & 11 deletions cmd/revad/pkg/config/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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}}",
},
},
},
Expand All @@ -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 }}",
},
},
},
Expand All @@ -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"])
}

0 comments on commit 783c0c5

Please sign in to comment.