Add sprig for command templates (#9053)
Adds the sprig functions to the template funcmap prepended with `sprig_` to match the behavior in consul-template
This commit is contained in:
parent
8cc212167b
commit
31a289891d
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
cli: Added sprig function support for `-t` templates
|
||||
```
|
|
@ -3,9 +3,9 @@ package command
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/hashicorp/go-msgpack/codec"
|
||||
)
|
||||
|
||||
|
@ -36,14 +36,12 @@ func DataFormat(format, tmpl string) (DataFormatter, error) {
|
|||
return nil, fmt.Errorf("Unsupported format is specified.")
|
||||
}
|
||||
|
||||
type JSONFormat struct {
|
||||
}
|
||||
type JSONFormat struct{}
|
||||
|
||||
// TransformData returns JSON format string data.
|
||||
func (p *JSONFormat) TransformData(data interface{}) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
enc := codec.NewEncoder(&buf, jsonHandlePretty)
|
||||
err := enc.Encode(data)
|
||||
err := codec.NewEncoder(&buf, jsonHandlePretty).Encode(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -57,21 +55,21 @@ type TemplateFormat struct {
|
|||
|
||||
// TransformData returns template format string data.
|
||||
func (p *TemplateFormat) TransformData(data interface{}) (string, error) {
|
||||
var out io.Writer = new(bytes.Buffer)
|
||||
var out bytes.Buffer
|
||||
if len(p.tmpl) == 0 {
|
||||
return "", fmt.Errorf("template needs to be specified the golang templates.")
|
||||
return "", fmt.Errorf("template needs to be specified in golang's text/template format.")
|
||||
}
|
||||
|
||||
t, err := template.New("format").Parse(p.tmpl)
|
||||
t, err := template.New("").Funcs(makeFuncMap()).Parse(p.tmpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = t.Execute(out, data)
|
||||
err = t.Execute(&out, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprint(out), nil
|
||||
return out.String(), nil
|
||||
}
|
||||
|
||||
func Format(json bool, template string, data interface{}) (string, error) {
|
||||
|
@ -93,8 +91,21 @@ func Format(json bool, template string, data interface{}) (string, error) {
|
|||
|
||||
out, err := f.TransformData(data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error formatting the data: %s", err)
|
||||
return "", fmt.Errorf("Error formatting the data: %w", err)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func makeFuncMap() template.FuncMap {
|
||||
fm := template.FuncMap{}
|
||||
|
||||
// Add the Sprig functions to the funcmap. These functions are decorated
|
||||
// with `sprig_` to match how they are treated in consul-template
|
||||
for k, v := range sprig.FuncMap() {
|
||||
target := "sprig_" + k
|
||||
fm[target] = v
|
||||
}
|
||||
|
||||
return fm
|
||||
}
|
||||
|
|
|
@ -1,68 +1,78 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/ci"
|
||||
"github.com/shoenig/test/must"
|
||||
)
|
||||
|
||||
type testData struct {
|
||||
Region string
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
func TestDataFormat(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
type testData struct {
|
||||
Region string
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
|
||||
const expectJSON = `{
|
||||
var tData = testData{"global", "1", "example"}
|
||||
|
||||
// Note: this variable is space indented (4) and requires the final brace to
|
||||
// be at char 1
|
||||
const expectJSON = `{
|
||||
"ID": "1",
|
||||
"Name": "example",
|
||||
"Region": "global"
|
||||
}`
|
||||
|
||||
var (
|
||||
tData = testData{"global", "1", "example"}
|
||||
testFormat = map[string]string{"json": "", "template": "{{.Region}}"}
|
||||
expectOutput = map[string]string{"json": expectJSON, "template": "global"}
|
||||
)
|
||||
var tcs = map[string]struct {
|
||||
format string
|
||||
template string
|
||||
expect string
|
||||
isError bool
|
||||
}{
|
||||
"json_good": {
|
||||
format: "json",
|
||||
template: "",
|
||||
expect: expectJSON,
|
||||
},
|
||||
"template_good": {
|
||||
format: "template",
|
||||
template: "{{.Region}}",
|
||||
expect: "global",
|
||||
},
|
||||
"template_bad": {
|
||||
format: "template",
|
||||
template: "{{.foo}}",
|
||||
isError: true,
|
||||
expect: "can't evaluate field foo",
|
||||
},
|
||||
"template_empty": {
|
||||
format: "template",
|
||||
template: "",
|
||||
isError: true,
|
||||
expect: "template needs to be specified in golang's text/template format.",
|
||||
},
|
||||
"template_sprig": {
|
||||
format: "template",
|
||||
template: `{{$a := 1}}{{ $a | sprig_add 1 }}`,
|
||||
expect: "2",
|
||||
},
|
||||
}
|
||||
|
||||
func TestDataFormat(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
for k, v := range testFormat {
|
||||
fm, err := DataFormat(k, v)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
result, err := fm.TransformData(tData)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if result != expectOutput[k] {
|
||||
t.Fatalf("expected output:\n%s\nactual:\n%s", expectOutput[k], result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidJSONTemplate(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
// Invalid template {{.foo}}
|
||||
fm, err := DataFormat("template", "{{.foo}}")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
_, err = fm.TransformData(tData)
|
||||
if !strings.Contains(err.Error(), "can't evaluate field foo") {
|
||||
t.Fatalf("expected invalid template error, got: %s", err.Error())
|
||||
}
|
||||
|
||||
// No template is specified
|
||||
fm, err = DataFormat("template", "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
_, err = fm.TransformData(tData)
|
||||
if !strings.Contains(err.Error(), "template needs to be specified the golang templates.") {
|
||||
t.Fatalf("expected not specified template error, got: %s", err.Error())
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := tc
|
||||
ci.Parallel(t)
|
||||
fm, err := DataFormat(tc.format, tc.template)
|
||||
must.NoError(t, err)
|
||||
result, err := fm.TransformData(tData)
|
||||
if tc.isError {
|
||||
must.ErrorContains(t, err, tc.expect)
|
||||
return
|
||||
}
|
||||
must.NoError(t, err)
|
||||
must.Eq(t, tc.expect, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -155,7 +155,7 @@ require (
|
|||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.1
|
||||
github.com/Microsoft/hcsshim v0.9.5 // indirect
|
||||
github.com/VividCortex/ewma v1.1.1 // indirect
|
||||
github.com/agext/levenshtein v1.2.1 // indirect
|
||||
|
|
Loading…
Reference in New Issue