From c3fcf41420450df959f44ab24c001e24b0445e89 Mon Sep 17 00:00:00 2001
From: Alex Clemmer <clemmer.alexander@gmail.com>
Date: Wed, 25 Oct 2017 16:51:00 -0700
Subject: [PATCH] Transition away from cgo, and towards go-jsonnet

---
 cmd/version.go                       |   2 +-
 prototype/snippet/jsonnet/snippet.go |  81 +++++++++----
 template/expander.go                 |  20 ++--
 utils/acquire.go                     |   9 +-
 utils/nativefuncs.go                 | 164 +++++++++++++++++----------
 utils/nativefuncs_test.go            |  32 +++---
 6 files changed, 199 insertions(+), 109 deletions(-)

diff --git a/cmd/version.go b/cmd/version.go
index eaafbb7f..22da0736 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -18,8 +18,8 @@ package cmd
 import (
 	"fmt"
 
+	jsonnet "github.com/google/go-jsonnet"
 	"github.com/spf13/cobra"
-	jsonnet "github.com/strickyak/jsonnet_cgo"
 	"k8s.io/client-go/pkg/version"
 )
 
diff --git a/prototype/snippet/jsonnet/snippet.go b/prototype/snippet/jsonnet/snippet.go
index a4abda6d..8e672d1b 100644
--- a/prototype/snippet/jsonnet/snippet.go
+++ b/prototype/snippet/jsonnet/snippet.go
@@ -72,20 +72,27 @@ func visit(node ast.Node, imports *[]ast.Import) error {
 	switch n := node.(type) {
 	case *ast.Import:
 		// Add parameter-type imports to the list of replacements.
-		if strings.HasPrefix(n.File, paramPrefix) {
-			param := strings.TrimPrefix(n.File, paramPrefix)
+		if strings.HasPrefix(n.File.Value, paramPrefix) {
+			param := strings.TrimPrefix(n.File.Value, paramPrefix)
 			if len(param) < 1 {
 				return errors.New("There must be a parameter following import param://")
 			}
 			*imports = append(*imports, *n)
 		}
 	case *ast.Apply:
-		for _, arg := range n.Arguments {
+		for _, arg := range n.Arguments.Positional {
 			err := visit(arg, imports)
 			if err != nil {
 				return err
 			}
 		}
+
+		for _, arg := range n.Arguments.Named {
+			err := visit(arg.Arg, imports)
+			if err != nil {
+				return err
+			}
+		}
 		return visit(n.Target, imports)
 	case *ast.ApplyBrace:
 		err := visit(n.Left, imports)
@@ -101,11 +108,9 @@ func visit(node ast.Node, imports *[]ast.Import) error {
 			}
 		}
 	case *ast.ArrayComp:
-		for _, spec := range n.Specs {
-			err := visitCompSpec(spec, imports)
-			if err != nil {
-				return err
-			}
+		err := visitCompSpec(n.Spec, imports)
+		if err != nil {
+			return err
 		}
 		return visit(n.Body, imports)
 	case *ast.Assert:
@@ -137,6 +142,13 @@ func visit(node ast.Node, imports *[]ast.Import) error {
 	case *ast.Error:
 		return visit(n.Expr, imports)
 	case *ast.Function:
+		for _, p := range n.Parameters.Optional {
+			err := visit(p.DefaultArg, imports)
+			if err != nil {
+				return err
+			}
+		}
+
 		return visit(n.Body, imports)
 	case *ast.Index:
 		err := visit(n.Target, imports)
@@ -193,22 +205,10 @@ func visit(node ast.Node, imports *[]ast.Import) error {
 				return err
 			}
 		}
-		for _, spec := range n.Specs {
-			err := visitCompSpec(spec, imports)
-			if err != nil {
-				return err
-			}
-		}
-	case *ast.ObjectComprehensionSimple:
-		err := visit(n.Field, imports)
+		err := visitCompSpec(n.Spec, imports)
 		if err != nil {
 			return err
 		}
-		err = visit(n.Value, imports)
-		if err != nil {
-			return err
-		}
-		return visit(n.Array, imports)
 	case *ast.SuperIndex:
 		return visit(n.Index, imports)
 	case *ast.InSuper:
@@ -235,11 +235,40 @@ func visit(node ast.Node, imports *[]ast.Import) error {
 	return nil
 }
 
-func visitCompSpec(node ast.CompSpec, imports *[]ast.Import) error {
+func visitCompSpec(node ast.ForSpec, imports *[]ast.Import) error {
+	if node.Outer != nil {
+		err := visitCompSpec(*node.Outer, imports)
+		if err != nil {
+			return err
+		}
+	}
+
+	for _, ifspec := range node.Conditions {
+		err := visit(ifspec.Expr, imports)
+		if err != nil {
+			return err
+		}
+	}
 	return visit(node.Expr, imports)
 }
 
 func visitObjectField(node ast.ObjectField, imports *[]ast.Import) error {
+	if node.Method != nil {
+		err := visit(node.Method, imports)
+		if err != nil {
+			return err
+		}
+	}
+
+	if node.Params != nil {
+		for _, p := range node.Params.Optional {
+			err := visit(p.DefaultArg, imports)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
 	err := visit(node.Expr1, imports)
 	if err != nil {
 		return err
@@ -260,6 +289,12 @@ func visitDesugaredObjectField(node ast.DesugaredObjectField, imports *[]ast.Imp
 }
 
 func visitLocalBind(node ast.LocalBind, imports *[]ast.Import) error {
+	if node.Fun != nil {
+		err := visit(node.Fun, imports)
+		if err != nil {
+			return err
+		}
+	}
 	return visit(node.Body, imports)
 }
 
@@ -280,7 +315,7 @@ func replace(jsonnet string, imports []ast.Import) string {
 	})
 
 	for _, im := range imports {
-		param := paramReplacementPrefix + strings.TrimPrefix(im.File, paramPrefix) + paramReplacementSuffix
+		param := paramReplacementPrefix + strings.TrimPrefix(im.File.Value, paramPrefix) + paramReplacementSuffix
 
 		lineStart := im.Loc().Begin.Line
 		lineEnd := im.Loc().End.Line
diff --git a/template/expander.go b/template/expander.go
index 961a5274..93ea47b6 100644
--- a/template/expander.go
+++ b/template/expander.go
@@ -8,9 +8,9 @@ import (
 
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 
+	jsonnet "github.com/google/go-jsonnet"
 	"github.com/ksonnet/ksonnet/utils"
 	log "github.com/sirupsen/logrus"
-	jsonnet "github.com/strickyak/jsonnet_cgo"
 )
 
 type Expander struct {
@@ -31,7 +31,6 @@ func (spec *Expander) Expand(paths []string) ([]*unstructured.Unstructured, erro
 	if err != nil {
 		return nil, err
 	}
-	defer vm.Destroy()
 
 	res := []*unstructured.Unstructured{}
 	for _, path := range paths {
@@ -47,18 +46,23 @@ func (spec *Expander) Expand(paths []string) ([]*unstructured.Unstructured, erro
 // JsonnetVM constructs a new jsonnet.VM, according to command line
 // flags
 func (spec *Expander) jsonnetVM() (*jsonnet.VM, error) {
-	vm := jsonnet.Make()
+	vm := jsonnet.MakeVM()
+	importer := jsonnet.FileImporter{
+		JPaths: []string{},
+	}
 
 	for _, p := range spec.EnvJPath {
 		log.Debugln("Adding jsonnet search path", p)
-		vm.JpathAdd(p)
+		importer.JPaths = append(importer.JPaths, p)
 	}
 
 	for _, p := range spec.FlagJpath {
 		log.Debugln("Adding jsonnet search path", p)
-		vm.JpathAdd(p)
+		importer.JPaths = append(importer.JPaths, p)
 	}
 
+	vm.Importer(&importer)
+
 	for _, extvar := range spec.ExtVars {
 		kv := strings.SplitN(extvar, "=", 2)
 		switch len(kv) {
@@ -92,12 +96,12 @@ func (spec *Expander) jsonnetVM() (*jsonnet.VM, error) {
 		case 1:
 			v, present := os.LookupEnv(kv[0])
 			if present {
-				vm.TlaVar(kv[0], v)
+				vm.TLAVar(kv[0], v)
 			} else {
 				return nil, fmt.Errorf("Missing environment variable: %s", kv[0])
 			}
 		case 2:
-			vm.TlaVar(kv[0], kv[1])
+			vm.TLAVar(kv[0], kv[1])
 		}
 	}
 
@@ -110,7 +114,7 @@ func (spec *Expander) jsonnetVM() (*jsonnet.VM, error) {
 		if err != nil {
 			return nil, err
 		}
-		vm.TlaVar(kv[0], string(v))
+		vm.TLAVar(kv[0], string(v))
 	}
 
 	for _, extcode := range spec.ExtCodes {
diff --git a/utils/acquire.go b/utils/acquire.go
index 7d983fee..dd93e1f7 100644
--- a/utils/acquire.go
+++ b/utils/acquire.go
@@ -24,8 +24,8 @@ import (
 	"os"
 	"path/filepath"
 
+	jsonnet "github.com/google/go-jsonnet"
 	log "github.com/sirupsen/logrus"
-	jsonnet "github.com/strickyak/jsonnet_cgo"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/util/yaml"
@@ -126,7 +126,12 @@ func jsonWalk(obj interface{}) ([]interface{}, error) {
 }
 
 func jsonnetReader(vm *jsonnet.VM, path string) ([]runtime.Object, error) {
-	jsonstr, err := vm.EvaluateFile(path)
+	jsonnetBytes, err := ioutil.ReadFile(path)
+	if err != nil {
+		return nil, err
+	}
+
+	jsonstr, err := vm.EvaluateSnippet(path, string(jsonnetBytes))
 	if err != nil {
 		return nil, err
 	}
diff --git a/utils/nativefuncs.go b/utils/nativefuncs.go
index 30fe7cc8..4db4a67c 100644
--- a/utils/nativefuncs.go
+++ b/utils/nativefuncs.go
@@ -24,7 +24,8 @@ import (
 
 	goyaml "github.com/ghodss/yaml"
 
-	jsonnet "github.com/strickyak/jsonnet_cgo"
+	jsonnet "github.com/google/go-jsonnet"
+	"github.com/google/go-jsonnet/ast"
 	"k8s.io/apimachinery/pkg/util/yaml"
 )
 
@@ -48,61 +49,110 @@ func RegisterNativeFuncs(vm *jsonnet.VM, resolver Resolver) {
 	// "*FromJson" functions will be replaced by regular native
 	// version when libjsonnet is able to support this.
 
-	vm.NativeCallback("parseJson", []string{"json"}, func(data []byte) (res interface{}, err error) {
-		err = json.Unmarshal(data, &res)
-		return
-	})
-
-	vm.NativeCallback("parseYaml", []string{"yaml"}, func(data []byte) ([]interface{}, error) {
-		ret := []interface{}{}
-		d := yaml.NewYAMLToJSONDecoder(bytes.NewReader(data))
-		for {
-			var doc interface{}
-			if err := d.Decode(&doc); err != nil {
-				if err == io.EOF {
-					break
+	vm.NativeFunction(
+		&jsonnet.NativeFunction{
+			Name:   "parseJson",
+			Params: ast.Identifiers{"json"},
+			Func: func(dataString []interface{}) (res interface{}, err error) {
+				data := []byte(dataString[0].(string))
+				err = json.Unmarshal(data, &res)
+				return
+			},
+		})
+
+	vm.NativeFunction(
+		&jsonnet.NativeFunction{
+			Name:   "parseYaml",
+			Params: ast.Identifiers{"yaml"},
+			Func: func(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 nil, err
-			}
-			ret = append(ret, doc)
-		}
-		return ret, nil
-	})
-
-	vm.NativeCallback("manifestJsonFromJson", []string{"json", "indent"}, func(data []byte, indent int) (string, error) {
-		data = bytes.TrimSpace(data)
-		buf := bytes.Buffer{}
-		if err := json.Indent(&buf, data, "", strings.Repeat(" ", indent)); err != nil {
-			return "", err
-		}
-		buf.WriteString("\n")
-		return buf.String(), nil
-	})
-
-	vm.NativeCallback("manifestYamlFromJson", []string{"json"}, func(data []byte) (string, error) {
-		var input interface{}
-		if err := json.Unmarshal(data, &input); err != nil {
-			return "", err
-		}
-		output, err := goyaml.Marshal(input)
-		return string(output), err
-	})
-
-	vm.NativeCallback("resolveImage", []string{"image"}, func(image string) (string, error) {
-		return resolveImage(resolver, image)
-	})
-
-	vm.NativeCallback("escapeStringRegex", []string{"str"}, func(s string) (string, error) {
-		return regexp.QuoteMeta(s), nil
-	})
-
-	vm.NativeCallback("regexMatch", []string{"regex", "string"}, regexp.MatchString)
-
-	vm.NativeCallback("regexSubst", []string{"regex", "src", "repl"}, func(regex, src, repl string) (string, error) {
-		r, err := regexp.Compile(regex)
-		if err != nil {
-			return "", err
-		}
-		return r.ReplaceAllString(src, repl), nil
-	})
+				return ret, nil
+			},
+		})
+
+	vm.NativeFunction(
+		&jsonnet.NativeFunction{
+			Name:   "manifestJsonFromJson",
+			Params: ast.Identifiers{"json", "indent"},
+			Func: func(data []interface{}) (interface{}, error) {
+				indent := int(data[1].(float64))
+				dataBytes := []byte(data[0].(string))
+				dataBytes = bytes.TrimSpace(dataBytes)
+				buf := bytes.Buffer{}
+				if err := json.Indent(&buf, dataBytes, "", strings.Repeat(" ", indent)); err != nil {
+					return "", err
+				}
+				buf.WriteString("\n")
+				return buf.String(), nil
+			},
+		})
+
+	vm.NativeFunction(
+		&jsonnet.NativeFunction{
+			Name:   "manifestYamlFromJson",
+			Params: ast.Identifiers{"json"},
+			Func: func(data []interface{}) (interface{}, error) {
+				var input interface{}
+				dataBytes := []byte(data[0].(string))
+				if err := json.Unmarshal(dataBytes, &input); err != nil {
+					return "", err
+				}
+				output, err := goyaml.Marshal(input)
+				return string(output), err
+			},
+		})
+
+	vm.NativeFunction(
+		&jsonnet.NativeFunction{
+			Name:   "resolveImage",
+			Params: ast.Identifiers{"image"},
+			Func: func(image []interface{}) (interface{}, error) {
+				return resolveImage(resolver, image[0].(string))
+			},
+		})
+
+	vm.NativeFunction(
+		&jsonnet.NativeFunction{
+			Name:   "escapeStringRegex",
+			Params: ast.Identifiers{"str"},
+			Func: func(s []interface{}) (interface{}, error) {
+				return regexp.QuoteMeta(s[0].(string)), nil
+			},
+		})
+
+	vm.NativeFunction(
+		&jsonnet.NativeFunction{
+			Name:   "regexMatch",
+			Params: ast.Identifiers{"regex", "string"},
+			Func: func(s []interface{}) (interface{}, error) {
+				return regexp.MatchString(s[0].(string), s[1].(string))
+			},
+		})
+
+	vm.NativeFunction(
+		&jsonnet.NativeFunction{
+			Name:   "regexSubst",
+			Params: ast.Identifiers{"regex", "src", "repl"},
+			Func: func(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
+			},
+		})
 }
diff --git a/utils/nativefuncs_test.go b/utils/nativefuncs_test.go
index 8e6d7a97..62485012 100644
--- a/utils/nativefuncs_test.go
+++ b/utils/nativefuncs_test.go
@@ -18,7 +18,7 @@ package utils
 import (
 	"testing"
 
-	jsonnet "github.com/strickyak/jsonnet_cgo"
+	jsonnet "github.com/google/go-jsonnet"
 )
 
 // check there is no err, and a == b.
@@ -31,8 +31,7 @@ func check(t *testing.T, err error, actual, expected string) {
 }
 
 func TestParseJson(t *testing.T) {
-	vm := jsonnet.Make()
-	defer vm.Destroy()
+	vm := jsonnet.MakeVM()
 	RegisterNativeFuncs(vm, NewIdentityResolver())
 
 	_, err := vm.EvaluateSnippet("failtest", `std.native("parseJson")("barf{")`)
@@ -41,17 +40,16 @@ func TestParseJson(t *testing.T) {
 	}
 
 	x, err := vm.EvaluateSnippet("test", `std.native("parseJson")("null")`)
-	check(t, err, x, "null\n")
+	check(t, err, x, "null")
 
 	x, err = vm.EvaluateSnippet("test", `
     local a = std.native("parseJson")('{"foo": 3, "bar": 4}');
     a.foo + a.bar`)
-	check(t, err, x, "7\n")
+	check(t, err, x, "7")
 }
 
 func TestParseYaml(t *testing.T) {
-	vm := jsonnet.Make()
-	defer vm.Destroy()
+	vm := jsonnet.MakeVM()
 	RegisterNativeFuncs(vm, NewIdentityResolver())
 
 	_, err := vm.EvaluateSnippet("failtest", `std.native("parseYaml")("[barf")`)
@@ -60,22 +58,21 @@ func TestParseYaml(t *testing.T) {
 	}
 
 	x, err := vm.EvaluateSnippet("test", `std.native("parseYaml")("")`)
-	check(t, err, x, "[ ]\n")
+	check(t, err, x, "[ ]")
 
 	x, err = vm.EvaluateSnippet("test", `
     local a = std.native("parseYaml")("foo:\n- 3\n- 4\n")[0];
     a.foo[0] + a.foo[1]`)
-	check(t, err, x, "7\n")
+	check(t, err, x, "7")
 
 	x, err = vm.EvaluateSnippet("test", `
     local a = std.native("parseYaml")("---\nhello\n---\nworld");
     a[0] + a[1]`)
-	check(t, err, x, "\"helloworld\"\n")
+	check(t, err, x, "\"helloworld\"")
 }
 
 func TestRegexMatch(t *testing.T) {
-	vm := jsonnet.Make()
-	defer vm.Destroy()
+	vm := jsonnet.MakeVM()
 	RegisterNativeFuncs(vm, NewIdentityResolver())
 
 	_, err := vm.EvaluateSnippet("failtest", `std.native("regexMatch")("[f", "foo")`)
@@ -84,15 +81,14 @@ func TestRegexMatch(t *testing.T) {
 	}
 
 	x, err := vm.EvaluateSnippet("test", `std.native("regexMatch")("foo.*", "seafood")`)
-	check(t, err, x, "true\n")
+	check(t, err, x, "true")
 
 	x, err = vm.EvaluateSnippet("test", `std.native("regexMatch")("bar.*", "seafood")`)
-	check(t, err, x, "false\n")
+	check(t, err, x, "false")
 }
 
 func TestRegexSubst(t *testing.T) {
-	vm := jsonnet.Make()
-	defer vm.Destroy()
+	vm := jsonnet.MakeVM()
 	RegisterNativeFuncs(vm, NewIdentityResolver())
 
 	_, err := vm.EvaluateSnippet("failtest", `std.native("regexSubst")("[f",s "foo", "bar")`)
@@ -101,8 +97,8 @@ func TestRegexSubst(t *testing.T) {
 	}
 
 	x, err := vm.EvaluateSnippet("test", `std.native("regexSubst")("a(x*)b", "-ab-axxb-", "T")`)
-	check(t, err, x, "\"-T-T-\"\n")
+	check(t, err, x, "\"-T-T-\"")
 
 	x, err = vm.EvaluateSnippet("test", `std.native("regexSubst")("a(x*)b", "-ab-axxb-", "${1}W")`)
-	check(t, err, x, "\"-W-xxW-\"\n")
+	check(t, err, x, "\"-W-xxW-\"")
 }
-- 
GitLab