Permalink
Browse files

Make native funcs available for components

re-enables native funcs for components:
* parseYaml
* parseJson
* regexMatch
* regexSubst
* escapeStringRegex

Fixes #508

Signed-off-by: bryanl <bryanliles@gmail.com>
  • Loading branch information...
bryanl committed May 8, 2018
1 parent c904092 commit 0bfb8d40e69cd4ab466ac7c9e956a21e48a3e8dc
Showing with 290 additions and 0 deletions.
  1. +90 −0 pkg/util/jsonnet/vm.go
  2. +200 −0 pkg/util/jsonnet/vm_test.go
@@ -16,15 +16,21 @@
package jsonnet
import (
"bytes"
"encoding/json"
"fmt"
"io"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/google/go-jsonnet"
"github.com/google/go-jsonnet/ast"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/afero"
"k8s.io/apimachinery/pkg/util/yaml"
)
type makeVMFn func() *jsonnet.VM
@@ -111,6 +117,7 @@ func (vm *VM) EvaluateSnippet(name, snippet string) (string, error) {
jvm := jsonnet.MakeVM()
jvm.ErrorFormatter.SetMaxStackTraceSize(40)
registerNativeFuncs(jvm)
importer, err := vm.createImporter()
if err != nil {
return "", errors.Wrap(err, "create jsonnet importer")
@@ -197,3 +204,86 @@ func (vm *VM) readString(path string) (string, error) {
return string(b), nil
}
func registerNativeFuncs(vm *jsonnet.VM) {
// NOTE: jsonnet native functions can only pass primitive
// types, so some functions json-encode the arg. These
// "*FromJson" functions will be replaced by regular native
// version when jsonnet is able to support this.
vm.NativeFunction(
&jsonnet.NativeFunction{
Name: "parseJson",
Params: ast.Identifiers{"json"},
Func: parseJSON,
})
vm.NativeFunction(
&jsonnet.NativeFunction{
Name: "parseYaml",
Params: ast.Identifiers{"yaml"},
Func: parseYAML,
})
vm.NativeFunction(
&jsonnet.NativeFunction{
Name: "escapeStringRegex",
Params: ast.Identifiers{"str"},
Func: escapeStringRegex,
})
vm.NativeFunction(
&jsonnet.NativeFunction{
Name: "regexMatch",
Params: ast.Identifiers{"regex", "string"},
Func: regexMatch,
})
vm.NativeFunction(
&jsonnet.NativeFunction{
Name: "regexSubst",
Params: ast.Identifiers{"regex", "src", "repl"},
Func: regexSubst,
})
}
func regexSubst(data []interface{}) (interface{}, error) {
regex, src, repl := data[0].(string), data[1].(string), data[2].(string)
r, err := regexp.Compile(regex)
if err != nil {
return "", err
}
return r.ReplaceAllString(src, repl), nil
}
func regexMatch(s []interface{}) (interface{}, error) {
return regexp.MatchString(s[0].(string), s[1].(string))
}
func escapeStringRegex(s []interface{}) (interface{}, error) {
return regexp.QuoteMeta(s[0].(string)), nil
}
func parseYAML(dataString []interface{}) (interface{}, error) {
data := []byte(dataString[0].(string))
ret := []interface{}{}
d := yaml.NewYAMLToJSONDecoder(bytes.NewReader(data))
for {
var doc interface{}
if err := d.Decode(&doc); err != nil {
if err == io.EOF {
break
}
return nil, err
}
ret = append(ret, doc)
}
return ret, nil
}
func parseJSON(dataString []interface{}) (res interface{}, err error) {
data := []byte(dataString[0].(string))
err = json.Unmarshal(data, &res)
return
}
@@ -101,3 +101,203 @@ func TestVM_EvaluateSnippet_memory_importer(t *testing.T) {
require.Equal(t, "evaluated", out)
}
func Test_regexSubst(t *testing.T) {
cases := []struct {
name string
in []interface{}
expected string
isErr bool
}{
{
name: "valid regex",
in: []interface{}{
"ee",
"tree",
"oll",
},
expected: "troll",
},
{
name: "invalid regex",
in: []interface{}{
"[",
"tree",
"oll",
},
isErr: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
out, err := regexSubst(tc.in)
if tc.isErr {
require.Error(t, err)
return
}
require.NoError(t, err)
s, ok := out.(string)
require.True(t, ok)
require.Equal(t, tc.expected, s)
})
}
}
func Test_regexMatch(t *testing.T) {
in := []interface{}{"ee", "tree"}
out, err := regexMatch(in)
require.NoError(t, err)
tf, ok := out.(bool)
require.True(t, ok)
require.True(t, tf)
}
func Test_escapeStringRegex(t *testing.T) {
in := []interface{}{"[foo]"}
out, err := escapeStringRegex(in)
require.NoError(t, err)
s, ok := out.(string)
require.True(t, ok)
require.Equal(t, `\[foo\]`, s)
}
func Test_parseYAML(t *testing.T) {
cases := []struct {
name string
in []interface{}
expected interface{}
isErr bool
}{
{
name: "valid yaml",
in: []interface{}{"---\nfoo: bar"},
expected: []interface{}{
map[string]interface{}{
"foo": "bar",
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
out, err := parseYAML(tc.in)
if tc.isErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.expected, out)
})
}
}
func Test_parseJSON(t *testing.T) {
cases := []struct {
name string
in []interface{}
expected interface{}
isErr bool
}{
{
name: "valid JSON",
in: []interface{}{`{ "foo": "bar" }`},
expected: map[string]interface{}{
"foo": "bar",
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
out, err := parseJSON(tc.in)
if tc.isErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.expected, out)
})
}
}
func TestParseJson(t *testing.T) {
vm := NewVM()
_, err := vm.EvaluateSnippet("failtest", `std.native("parseJson")("barf{")`)
require.Error(t, err)
x, err := vm.EvaluateSnippet("test", `std.native("parseJson")("null")`)
require.NoError(t, err)
assert.Equal(t, "null\n", x)
x, err = vm.EvaluateSnippet("test", `
local a = std.native("parseJson")('{"foo": 3, "bar": 4}');
a.foo + a.bar`)
require.NoError(t, err)
assert.Equal(t, "7\n", x)
}
func TestParseYaml(t *testing.T) {
vm := NewVM()
_, err := vm.EvaluateSnippet("failtest", `std.native("parseYaml")("[barf")`)
require.Error(t, err)
x, err := vm.EvaluateSnippet("test", `std.native("parseYaml")("")`)
require.NoError(t, err)
assert.Equal(t, "[ ]\n", x)
x, err = vm.EvaluateSnippet("test", `
local a = std.native("parseYaml")("foo:\n- 3\n- 4\n")[0];
a.foo[0] + a.foo[1]`)
require.NoError(t, err)
assert.Equal(t, "7\n", x)
x, err = vm.EvaluateSnippet("test", `
local a = std.native("parseYaml")("---\nhello\n---\nworld");
a[0] + a[1]`)
require.NoError(t, err)
assert.Equal(t, "\"helloworld\"\n", x)
}
func Test_regexMatch_fun(t *testing.T) {
vm := NewVM()
_, err := vm.EvaluateSnippet("failtest", `std.native("regexMatch")("[f", "foo")`)
require.Error(t, err)
x, err := vm.EvaluateSnippet("test", `std.native("regexMatch")("foo.*", "seafood")`)
require.NoError(t, err)
assert.Equal(t, "true\n", x)
x, err = vm.EvaluateSnippet("test", `std.native("regexMatch")("bar.*", "seafood")`)
require.NoError(t, err)
assert.Equal(t, "false\n", x)
}
func TestRegexSubst(t *testing.T) {
vm := NewVM()
_, err := vm.EvaluateSnippet("failtest", `std.native("regexSubst")("[f",s "foo", "bar")`)
require.Error(t, err)
x, err := vm.EvaluateSnippet("test", `std.native("regexSubst")("a(x*)b", "-ab-axxb-", "T")`)
require.NoError(t, err)
assert.Equal(t, "\"-T-T-\"\n", x)
x, err = vm.EvaluateSnippet("test", `std.native("regexSubst")("a(x*)b", "-ab-axxb-", "${1}W")`)
require.NoError(t, err)
assert.Equal(t, "\"-W-xxW-\"\n", x)
}

0 comments on commit 0bfb8d4

Please sign in to comment.