Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add treeYAML method #1847

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/templating-language.md
Original file line number Diff line number Diff line change
@@ -809,6 +809,36 @@ nested/config/value "value"
Unlike [`ls`](#ls), [`tree`](#tree) returns **all** keys under the prefix, just like the Unix
[`tree`](#tree) command.

### `treeYAML`

Similarly to [`tree`](#tree) query [Consul][consul] for all kv pairs at the given key path. It will interpret loaded kv pairs as YAML values inferring a type or parsing value as YAML structure and return data as a structure suitable for other YAML related methods processing

```golang
{{ treeYAML "<PATH>" }}
```

For example with following KV data in Consul:

```
config/applications/debug = false
config/applications/ports = "- 8080\n- 8480"
```

the template:

```golang
{{ treeYAML "config/applications" | toYAML }}
```

will render:

```yaml
debug: false
ports:
- 8080
- 8480
```

### `safeTree`

Same as [`tree`](#tree), but refuse to render template, if the KV prefix query return blank/empty data.
77 changes: 77 additions & 0 deletions template/funcs.go
Original file line number Diff line number Diff line change
@@ -673,6 +673,83 @@ func treeFunc(b *Brain, used, missing *dep.Set, emptyIsSafe bool) func(string) (
}
}

// treeYAML returns tree as YAML data performing type inference or yaml unmarshalling of the data.
func treeYAML(b *Brain, used, missing *dep.Set, emptyIsSafe bool) func(string) (map[interface{}]interface{}, error) {
return func(s string) (map[interface{}]interface{}, error) {
s = strings.Trim(s, "/")

result := make(map[interface{}]interface{})

d, err := dep.NewKVListQuery(s)
if err != nil {
return result, err
}

used.Add(d)

// Only return non-empty top-level keys
if value, ok := b.Recall(d); ok {
for _, pair := range value.([]*dep.KeyPair) {
parts := strings.Split(pair.Key, "/")
if parts[len(parts)-1] != "" {
//result = append(result, pair)
var v interface{}
if err := yaml.Unmarshal([]byte(pair.Value), &v); err != nil {
return nil, errors.Wrap(err, "treeYAML")
}

result, err = mapInjectHelper(result, parts, v)
if err != nil {
return nil, err
}
}
}

if len(result) == 0 {
if emptyIsSafe {
// Operator used potentially unsafe tree function in the template instead of the safeTree
return result, nil
}
} else {
// non empty result is good so we just return the data
return result, nil
}

// If we reach this part of the code result is completely empty as value returned no KV pairs
// Operator selected to use safeTree on the specific KV prefix so we will refuse to render template
// by marking d as missing
}

// b.Recall either returned an error or safeTree entered unsafe case
missing.Add(d)

return result, nil
}
}

// mapInjectHelper takes a path of YAML leaf node, its value and recursively traverses destination YAML structure to inject the value.
func mapInjectHelper(dst map[interface{}]interface{}, path []string, v interface{}) (map[interface{}]interface{}, error) {
if len(path) == 0 {
return nil, fmt.Errorf("ran out of key parts before finished injection into map")
}

childNode, ok := dst[path[0]]
if !ok {
if len(path) > 1 {
var err error
childNode, err = mapInjectHelper(make(map[interface{}]interface{}), path[1:], v)
if err != nil {
return nil, err
}
} else {
childNode = v
}
dst[path[0]] = childNode
}

return dst, nil
}

// base64Decode decodes the given string as a base64 string, returning an error
// if it fails.
func base64Decode(s string) (string, error) {
1 change: 1 addition & 0 deletions template/template.go
Original file line number Diff line number Diff line change
@@ -334,6 +334,7 @@ func funcMap(i *funcMapInput) template.FuncMap {
"connect": connectFunc(i.brain, i.used, i.missing),
"services": servicesFunc(i.brain, i.used, i.missing),
"tree": treeFunc(i.brain, i.used, i.missing, true),
"treeYAML": treeYAML(i.brain, i.used, i.missing, true),
"safeTree": safeTreeFunc(i.brain, i.used, i.missing),
"caRoots": connectCARootsFunc(i.brain, i.used, i.missing),
"caLeaf": connectLeafFunc(i.brain, i.used, i.missing),