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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/Masterminds/sprig/v3"
|
||||||
"github.com/hashicorp/go-msgpack/codec"
|
"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.")
|
return nil, fmt.Errorf("Unsupported format is specified.")
|
||||||
}
|
}
|
||||||
|
|
||||||
type JSONFormat struct {
|
type JSONFormat struct{}
|
||||||
}
|
|
||||||
|
|
||||||
// TransformData returns JSON format string data.
|
// TransformData returns JSON format string data.
|
||||||
func (p *JSONFormat) TransformData(data interface{}) (string, error) {
|
func (p *JSONFormat) TransformData(data interface{}) (string, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
enc := codec.NewEncoder(&buf, jsonHandlePretty)
|
err := codec.NewEncoder(&buf, jsonHandlePretty).Encode(data)
|
||||||
err := enc.Encode(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -57,21 +55,21 @@ type TemplateFormat struct {
|
||||||
|
|
||||||
// TransformData returns template format string data.
|
// TransformData returns template format string data.
|
||||||
func (p *TemplateFormat) TransformData(data interface{}) (string, error) {
|
func (p *TemplateFormat) TransformData(data interface{}) (string, error) {
|
||||||
var out io.Writer = new(bytes.Buffer)
|
var out bytes.Buffer
|
||||||
if len(p.tmpl) == 0 {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.Execute(out, data)
|
err = t.Execute(&out, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return fmt.Sprint(out), nil
|
return out.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Format(json bool, template string, data interface{}) (string, error) {
|
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)
|
out, err := f.TransformData(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Error formatting the data: %s", err)
|
return "", fmt.Errorf("Error formatting the data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return out, nil
|
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
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testData struct {
|
func TestDataFormat(t *testing.T) {
|
||||||
Region string
|
ci.Parallel(t)
|
||||||
ID string
|
type testData struct {
|
||||||
Name string
|
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",
|
"ID": "1",
|
||||||
"Name": "example",
|
"Name": "example",
|
||||||
"Region": "global"
|
"Region": "global"
|
||||||
}`
|
}`
|
||||||
|
|
||||||
var (
|
var tcs = map[string]struct {
|
||||||
tData = testData{"global", "1", "example"}
|
format string
|
||||||
testFormat = map[string]string{"json": "", "template": "{{.Region}}"}
|
template string
|
||||||
expectOutput = map[string]string{"json": expectJSON, "template": "global"}
|
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) {
|
for name, tc := range tcs {
|
||||||
ci.Parallel(t)
|
t.Run(name, func(t *testing.T) {
|
||||||
for k, v := range testFormat {
|
tc := tc
|
||||||
fm, err := DataFormat(k, v)
|
ci.Parallel(t)
|
||||||
if err != nil {
|
fm, err := DataFormat(tc.format, tc.template)
|
||||||
t.Fatalf("err: %v", err)
|
must.NoError(t, err)
|
||||||
}
|
result, err := fm.TransformData(tData)
|
||||||
|
if tc.isError {
|
||||||
result, err := fm.TransformData(tData)
|
must.ErrorContains(t, err, tc.expect)
|
||||||
if err != nil {
|
return
|
||||||
t.Fatalf("err: %v", err)
|
}
|
||||||
}
|
must.NoError(t, err)
|
||||||
|
must.Eq(t, tc.expect, result)
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -155,7 +155,7 @@ require (
|
||||||
github.com/Masterminds/semver v1.5.0 // indirect
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible // 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/Microsoft/hcsshim v0.9.5 // indirect
|
||||||
github.com/VividCortex/ewma v1.1.1 // indirect
|
github.com/VividCortex/ewma v1.1.1 // indirect
|
||||||
github.com/agext/levenshtein v1.2.1 // indirect
|
github.com/agext/levenshtein v1.2.1 // indirect
|
||||||
|
|
Loading…
Reference in New Issue