Skip to content
This repository has been archived by the owner on Sep 9, 2020. It is now read-only.

Commit

Permalink
Merge pull request #521 from bryanl/reintroduce-native-funcs
Browse files Browse the repository at this point in the history
Make native funcs available for components
  • Loading branch information
bryanl committed May 8, 2018
2 parents c904092 + 0bfb8d4 commit 25395ed
Show file tree
Hide file tree
Showing 2 changed files with 290 additions and 0 deletions.
90 changes: 90 additions & 0 deletions pkg/util/jsonnet/vm.go
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
200 changes: 200 additions & 0 deletions pkg/util/jsonnet/vm_test.go
Expand Up @@ -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 25395ed

Please sign in to comment.