Repository: grafana/tanka
Pull request #189 (2020-01-29 19:30:28)

Changelog for 0.7.1 release


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 123c8f60..803ba044 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,50 @@
 # Changelog
 
+## 0.7.1 (2020-02-06)
+
+This is a smaller release focused on critical bug fixes and some other minor
+enhancements. While features are included, none of them are significant, meaning
+they are part of a patch release.
+
+#### Critical: `parseYaml` works now
+
+Before, `std.native('parseYaml')` did not work at all, a line of code got lost
+during merge/rebase, resulting in `parseYaml` returning invalid data, that
+Jsonnet could not process. This issue has been fixed in
+**([#195](https://github.com/grafana/tanka/pull/195))**.
+
+#### Jsonnet update
+
+The built-in Jsonnet compiler has been upgraded to the lastest master
+[`07fa4c0`](https://github.com/google/go-jsonnet/commit/07fa4c037b4ff8b5e601546cb5de4abecaf2651d).
+In some cases, this should provide up to 50% more speed, especially when
+`base64` is involved, which is now natively implemented.
+**([#196](https://github.com/grafana/tanka/pull/196))**
+
+### Features
+
+- **cli**: `tk env set|add` has been extended by `--server-from-context`, which
+  allows to parse `$KUBECONFIG` to find the apiServer's IP directly from that
+  file, instead of having to manually specify it by hand.
+  **([#184](https://github.com/grafana/tanka/pull/184))**
+- **jsonnet**: `vendor` overrides:  
+  It is now possible to have a `vendor/` directory per environment, so that
+  updating upstream libraries can be done gradually.
+  **([#185](https://github.com/grafana/tanka/pull/185))**
+- **kubernetes**: disable `kubectl` validation:
+  `tk apply` now takes `--validate=false` to pass that exact flag to `kubectl`
+  as well, for disabling the integrated schema validation.
+  **([#186](https://github.com/grafana/tanka/pull/186))**
+
+### Bug Fixes
+
+- **jsonnet, cli**: Stable environment name: The value of `(import "tk").env.name`
+  does not anymore depend on how Tanka was invoked, but will
+  always be the relative path from `<rootDir>` to the environment's directory.
+  **([#182](https://github.com/grafana/tanka/pull/182))**
+- **jsonnet**: The nativeFunc `parseYaml` has been fixed to actually return a
+  valid result **([#195](https://github.com/grafana/tanka/pull/195))**
+
 ## 0.7.0 (2020-01-21)
 
 The promised big update is here! In the last couple of weeks a lot has happened.
diff --git a/go.mod b/go.mod
index 179a8641..cf454949 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/alecthomas/chroma v0.6.6
 	github.com/fatih/color v1.7.0
 	github.com/fatih/structs v1.1.0
-	github.com/google/go-jsonnet v0.14.1-0.20191006203837-42cb19ef24fb
+	github.com/google/go-jsonnet v0.14.1-0.20200202165252-07fa4c037b4f
 	github.com/kr/pretty v0.1.0 // indirect
 	github.com/pkg/errors v0.8.1
 	github.com/posener/complete v1.2.1
diff --git a/go.sum b/go.sum
index ad16a4f8..3ab31cb9 100644
--- a/go.sum
+++ b/go.sum
@@ -38,6 +38,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/google/go-jsonnet v0.14.1-0.20191006203837-42cb19ef24fb h1:6GyG7yy0z1foZBWyfRa9CZYdcLbSPHR19PW70fJBedI=
 github.com/google/go-jsonnet v0.14.1-0.20191006203837-42cb19ef24fb/go.mod h1:zPGC9lj/TbjkBtUACIvYR/ILHrFqKRhxeEA+bLyeMnY=
+github.com/google/go-jsonnet v0.14.1-0.20200202165252-07fa4c037b4f h1:BS5AMVQJCCK6rwihuPgODMbrJ8q7T9O4VRJGRGmRqxc=
+github.com/google/go-jsonnet v0.14.1-0.20200202165252-07fa4c037b4f/go.mod h1:ex9QcU8vzXQUDeNe4gaN1uhGQbTYpOeZ6AbWdy6JbX4=
 github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
 github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
diff --git a/pkg/jsonnet/native/funcs.go b/pkg/jsonnet/native/funcs.go
index dbdaa876..291f80df 100644
--- a/pkg/jsonnet/native/funcs.go
+++ b/pkg/jsonnet/native/funcs.go
@@ -9,7 +9,8 @@ import (
 
 	jsonnet "github.com/google/go-jsonnet"
 	"github.com/google/go-jsonnet/ast"
-	yaml "gopkg.in/yaml.v2"
+	"github.com/pkg/errors"
+	yaml "gopkg.in/yaml.v3"
 )
 
 // Funcs returns a slice of native Go functions that shall be available
@@ -52,19 +53,26 @@ var parseYAML = &jsonnet.NativeFunction{
 
 		d := yaml.NewDecoder(bytes.NewReader(data))
 		for {
-			var doc interface{}
+			var doc, jsonDoc interface{}
 			if err := d.Decode(&doc); err != nil {
 				if err == io.EOF {
 					break
 				}
-				return nil, err
+				return nil, errors.Wrap(err, "parsing yaml")
 			}
-			jsonDoc, err := json.Marshal(doc)
+
+			jsonRaw, err := json.Marshal(doc)
 			if err != nil {
-				return nil, err
+				return nil, errors.Wrap(err, "converting yaml to json")
+			}
+
+			if err := json.Unmarshal(jsonRaw, &jsonDoc); err != nil {
+				return nil, errors.Wrap(err, "converting yaml to json")
 			}
+
 			ret = append(ret, jsonDoc)
 		}
+
 		return ret, nil
 	},
 }
diff --git a/pkg/tanka/parse.go b/pkg/tanka/parse.go
index 118b6e44..c1d48c99 100644
--- a/pkg/tanka/parse.go
+++ b/pkg/tanka/parse.go
@@ -122,6 +122,9 @@ func evalJsonnet(baseDir string, env *v1alpha1.Config, extCode map[string]string
 		filepath.Join(baseDir, "main.jsonnet"),
 		ext...,
 	)
+	if err != nil {
+		return nil, err
+	}
 
 	var dict map[string]interface{}
 	if err := json.Unmarshal([]byte(raw), &dict); err != nil {


Test patch:
diff --git a/pkg/jsonnet/native/funcs_test.go b/pkg/jsonnet/native/funcs_test.go
new file mode 100644
index 00000000..d6cefbdf
--- /dev/null
+++ b/pkg/jsonnet/native/funcs_test.go
@@ -0,0 +1,190 @@
+package native
+
+import (
+	"encoding/json"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+// callNative calls a native function used by jsonnet VM.
+func callNative(name string, data []interface{}) (res interface{}, err error, callerr error) {
+	for _, fun := range Funcs() {
+		if fun.Name == name {
+			// Call the function
+			ret, err := fun.Func(data)
+			return ret, err, nil
+		}
+	}
+
+	return nil, nil, fmt.Errorf("could not find native function %s", name)
+}
+
+func TestParseJSONEmptyDict(t *testing.T) {
+	ret, err, callerr := callNative("parseJson", []interface{}{"{}"})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, map[string]interface{}{}, ret)
+	assert.Empty(t, err)
+}
+
+func TestParseJSONkeyValuet(t *testing.T) {
+	ret, err, callerr := callNative("parseJson", []interface{}{"{\"a\": 47}"})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, map[string]interface{}{"a": 47.0}, ret)
+	assert.Empty(t, err)
+}
+
+func TestParseJSONInvalid(t *testing.T) {
+	ret, err, callerr := callNative("parseJson", []interface{}{""})
+
+	assert.Empty(t, callerr)
+	assert.Empty(t, ret)
+	assert.IsType(t, &json.SyntaxError{}, err)
+}
+
+func TestParseYAMLEmpty(t *testing.T) {
+	ret, err, callerr := callNative("parseYaml", []interface{}{""})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, []interface{}{}, ret)
+	assert.Empty(t, err)
+}
+
+func TestParseYAMLKeyValue(t *testing.T) {
+	ret, err, callerr := callNative("parseYaml", []interface{}{"a: 47"})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, []interface{}{map[string]interface{}{"a": 47.0}}, ret)
+	assert.Empty(t, err)
+}
+
+func TestParseYAMLInvalid(t *testing.T) {
+	ret, err, callerr := callNative("parseYaml", []interface{}{"'"})
+
+	assert.Empty(t, callerr)
+	assert.Empty(t, ret)
+	assert.NotEmpty(t, err)
+}
+
+func TestManifestJSONFromJSON(t *testing.T) {
+	ret, err, callerr := callNative("manifestJsonFromJson", []interface{}{"{}", float64(4)})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, "{}\n", ret)
+	assert.Empty(t, err)
+}
+
+func TestManifestJSONFromJSONReindent(t *testing.T) {
+	ret, err, callerr := callNative("manifestJsonFromJson", []interface{}{"{ \"a\": 47}", float64(4)})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, "{\n    \"a\": 47\n}\n", ret)
+	assert.Empty(t, err)
+}
+
+func TestManifestJSONFromJSONInvalid(t *testing.T) {
+	ret, err, callerr := callNative("manifestJsonFromJson", []interface{}{"", float64(4)})
+
+	assert.Empty(t, callerr)
+	assert.Empty(t, ret)
+	assert.NotEmpty(t, err)
+}
+
+func TestManifestYAMLFromJSONEmpty(t *testing.T) {
+	ret, err, callerr := callNative("manifestYamlFromJson", []interface{}{"{}"})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, "{}\n", ret)
+	assert.Empty(t, err)
+}
+
+func TestManifestYAMLFromJSONKeyValue(t *testing.T) {
+	ret, err, callerr := callNative("manifestYamlFromJson", []interface{}{"{ \"a\": 47}"})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, "a: 47\n", ret)
+	assert.Empty(t, err)
+}
+
+func TestManifestYAMLFromJSONInvalid(t *testing.T) {
+	ret, err, callerr := callNative("manifestYamlFromJson", []interface{}{""})
+
+	assert.Empty(t, callerr)
+	assert.Empty(t, ret)
+	assert.NotEmpty(t, err)
+}
+
+func TestEscapeStringRegex(t *testing.T) {
+	ret, err, callerr := callNative("escapeStringRegex", []interface{}{""})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, "", ret)
+	assert.Empty(t, err)
+}
+
+func TestEscapeStringRegexValue(t *testing.T) {
+	ret, err, callerr := callNative("escapeStringRegex", []interface{}{"([0-9]+).*\\s"})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, "\\(\\[0-9\\]\\+\\)\\.\\*\\\\s", ret)
+	assert.Empty(t, err)
+}
+
+func TestEscapeStringRegexInvalid(t *testing.T) {
+	ret, err, callerr := callNative("escapeStringRegex", []interface{}{"([0-9]+"})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, "\\(\\[0-9\\]\\+", ret)
+	assert.Empty(t, err)
+}
+
+func TestRegexMatch(t *testing.T) {
+	ret, err, callerr := callNative("regexMatch", []interface{}{"", "a"})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, true, ret)
+	assert.Empty(t, err)
+}
+
+func TestRegexMatchNoMatch(t *testing.T) {
+	ret, err, callerr := callNative("regexMatch", []interface{}{"a", "b"})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, false, ret)
+	assert.Empty(t, err)
+}
+
+func TestRegexMatchInvalidRegex(t *testing.T) {
+	ret, err, callerr := callNative("regexMatch", []interface{}{"[0-", "b"})
+
+	assert.Empty(t, callerr)
+	assert.Empty(t, ret)
+	assert.NotEmpty(t, err)
+}
+
+func TestRegexSubstNoChange(t *testing.T) {
+	ret, err, callerr := callNative("regexSubst", []interface{}{"a", "b", "c"})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, "b", ret)
+	assert.Empty(t, err)
+}
+
+func TestRegexSubstValid(t *testing.T) {
+	ret, err, callerr := callNative("regexSubst", []interface{}{"p[^m]*", "pm", "poe"})
+
+	assert.Empty(t, callerr)
+	assert.Equal(t, "poem", ret)
+	assert.Empty(t, err)
+}
+
+func TestRegexSubstInvalid(t *testing.T) {
+	ret, err, callerr := callNative("regexSubst", []interface{}{"p[^m*", "pm", "poe"})
+
+	assert.Empty(t, callerr)
+	assert.Empty(t, ret)
+	assert.NotEmpty(t, err)
+}