Skip to content

Commit

Permalink
add template function asJson
Browse files Browse the repository at this point in the history
  • Loading branch information
lucastheisen committed Sep 9, 2023
1 parent 912d795 commit 0475086
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 37 deletions.
56 changes: 28 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,8 @@ EOF
Templates allow you to apply your configuration to golang template plus some
additional [custom functions](docs/templates.md)

Note that when used in conjunction with the `--template` options,
`getv` templates see a one-level key-value map, not the map
represented by the yaml. For example, this yaml (`foo.yml`):
Note that when used in conjunction with the `--output go-template*` options, `getv` templates see a one-level key-value map, not the map represented by the yaml.
For example, this yaml (`foo.yml`):

```yaml
applications:
Expand All @@ -235,32 +234,33 @@ Would be seen by inside the templates as:
/credentials/password: bar
```
A simple bash program to utilize this might look like:
A simple bash program to utilizes this might look something like:
```bash
#!/bin/bash

set -e

function applications {
run_clconf \
getv '/' \
--output go-template \
--template '{{range getvs "/applications/*"}}{{.}}{{end}}'
}

function getv {
local path=$1
run_clconf getv "${path}"
}

function run_clconf {
./clconf --ignore-env --yaml 'foo.yml' "$@"
}

user="$(getv "/credentials/username")"
pass="$(getv "/credentials/password")"
applications | xargs -I {} {} --user "${user}" --pass "${pass}"
(
clconf --pipe \
getv / \
--output go-template-file \
--template <(cat <<'EOF'
{{- range (getvs "/applications/*")}}
echo {{.}} --user {{getv "/credentials/username"}} --pass {{getv "/credentials/password"}}
{{- end}}
EOF
) \
<<'EOF'
---
applications:
- command_a
- command_b
- command_c
credentials:
username: foo
password: bar
EOF
) | bash
# command_a --user foo --pass bar
# command_b --user foo --pass bar
# command_c --user foo --pass bar
```

### Kubernetes/OpenShift
Expand Down Expand Up @@ -390,7 +390,7 @@ yaml as a value store. It uses command line arguments in place of `confd`'s
[toml files](https://github.com/kelseyhightower/confd/blob/master/docs/template-resources.md)
to determine where templates are found and output placed.

`clconf` supports [additional functions](templates.md) above what `confd`
`clconf` supports [additional functions](docs/templates.md) above what `confd`
provides.

All of the options for `getv` are available for specifying yaml sources,
Expand Down
15 changes: 13 additions & 2 deletions docs/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

Templates are written in Go's [`text/template`](http://golang.org/pkg/text/template/).

[[_TOC_]]

## Flat key/value caveats and considerations

Because the [templates only see a flat list of key/value pairs](../README.md#getv-templates), certain operations will behave differently than the CLI (notably `getv` itself).
Expand Down Expand Up @@ -43,6 +41,19 @@ $ clconf getv / --output go-template --template '{{add 1 3}}'
4
```

### asJson

Converts the supplied value to properly encoded JSON.

```console
$ clconf --pipe getv /foo --output go-template --template '{{asJson (getvs "/*")}}' <<EOF
foo:
- bar
- baz
EOF
["bar","baz"]
```

### asJsonString

Converts the supplied value to a properly encoded JSON string.
Expand Down
19 changes: 15 additions & 4 deletions pkg/cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package cmd
import (
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"path/filepath"

Expand Down Expand Up @@ -374,8 +373,20 @@ func Example_getvArrayOfObjectsAsBashArray() {
// ([0]="{\"foo\":\"bar\"}" [1]="{\"hip\":\"hop\"}")
}

func Example_getvTemplateArrayAsJson() {
_ = newCmd(
"getv",
"--var", `/foo=["bar","baz"]`,
"/foo",
"--output", "go-template",
"--template", `{{asJson (getvs "/*")}}`,
).Execute()
// Output:
// ["bar","baz"]
}

func Example_mergeOverridesBooleanToFalse() {
footrue, err := ioutil.TempFile("", "")
footrue, err := os.CreateTemp("", "")
if err != nil {
return
}
Expand All @@ -385,7 +396,7 @@ func Example_mergeOverridesBooleanToFalse() {
return
}

foofalse, err := ioutil.TempFile("", "")
foofalse, err := os.CreateTemp("", "")
if err != nil {
return
}
Expand All @@ -401,7 +412,7 @@ func Example_mergeOverridesBooleanToFalse() {
}

func Example_patch() {
patch, err := ioutil.TempFile("", "")
patch, err := os.CreateTemp("", "")
if err != nil {
return
}
Expand Down
5 changes: 3 additions & 2 deletions pkg/memkv/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ func (s Store) Set(key string, value string) {
s.kv[key] = value
}

// ToKvMap will return a one-level map of key value pairs where the key is
// a / separated path of subkeys.
func (s Store) ToKvMap() map[string]string {
result := make(map[string]string, len(s.kv))
for k, v := range s.kv {
Expand All @@ -175,8 +177,7 @@ func (s Store) ToKvMap() map[string]string {
return result
}

// ToKvMap will return a one-level map of key value pairs where the key is
// a / separated path of subkeys.
// FillKvMap will fill the supplied kvMap with values from data.
func FillKvMap(kvMap map[string]string, data interface{}) {
Walk(func(keyStack []string, value interface{}) {
key := "/" + strings.Join(keyStack, "/")
Expand Down
11 changes: 11 additions & 0 deletions pkg/template/template_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,16 @@ func LookupSRV(service, proto, name string) []*net.SRV {
return addrs
}

// MarshalJSON will return the JSON encoded representation of the supplied
// data.
func MarshalJSON(data interface{}) (string, error) {
v, err := json.Marshal(data)
if err != nil {
return "", fmt.Errorf("json marshal: %w", err)
}
return string(v), nil
}

// MarshalJSONString will return the JSON encoded string representation of the
// supplied value. If data is not of type string, the fmt.Sprintf('%v', data)
// will be used prior to serialization to ensure a string type.
Expand All @@ -236,6 +246,7 @@ func MarshalJSONString(data interface{}) (string, error) {

func NewFuncMap(s *memkv.Store) map[string]interface{} {
m := make(map[string]interface{})
m["asJson"] = MarshalJSON
m["asJsonString"] = MarshalJSONString
m["atoi"] = strconv.Atoi
m["add"] = func(a, b int) int { return a + b }
Expand Down
27 changes: 26 additions & 1 deletion pkg/template/template_funcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,32 @@ func TestFqdn(t *testing.T) {
}
}

func TestMarshalJsonString(t *testing.T) {
func TestMarshalJSON(t *testing.T) {
tester := func(test string, data interface{}, expected string) {
t.Run(test, func(t *testing.T) {
actual, err := template.MarshalJSON(data)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
})
}

tester("simple", "foo", "\"foo\"")
tester(
"two lines",
`foo
bar`,
"\"foo\\nbar\"")
tester("with quotes", "foo\"bar", "\"foo\\\"bar\"")
tester("number", 1, "1")
tester("number", false, "false")
tester("array", []string{"foo", "bar"}, `["foo","bar"]`)
tester(
"object",
map[string]interface{}{"foo": []string{"bar", "baz"}},
`{"foo":["bar","baz"]}`)
}

func TestMarshalJSONString(t *testing.T) {
tester := func(test string, data interface{}, expected string) {
t.Run(test, func(t *testing.T) {
actual, err := template.MarshalJSONString(data)
Expand Down

0 comments on commit 0475086

Please sign in to comment.