From 8f76ab4f5335c23f57be87811a9e4c72f5dd92ad Mon Sep 17 00:00:00 2001 From: Angus Lees <gus@inodes.org> Date: Thu, 22 Jun 2017 09:27:50 +1000 Subject: [PATCH] Add native convenience functions to parse JSON and YAML Fixes #12 --- Makefile | 7 ++++- README.md | 1 + cmd/root.go | 2 ++ lib/kubecfg.libsonnet | 11 ++++++++ lib/kubecfg_test.jsonnet | 20 +++++++++++++ utils/nativefuncs.go | 33 ++++++++++++++++++++++ utils/nativefuncs_test.go | 59 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 lib/kubecfg.libsonnet create mode 100644 lib/kubecfg_test.jsonnet create mode 100644 utils/nativefuncs.go create mode 100644 utils/nativefuncs_test.go diff --git a/Makefile b/Makefile index 4f36f1fd..48969bc1 100644 --- a/Makefile +++ b/Makefile @@ -12,9 +12,14 @@ all: kubecfg kubecfg: $(GO) build $(GO_FLAGS) . -test: +test: gotest jsonnettest + +gotest: $(GO) test $(GO_FLAGS) $(GO_PACKAGES) +jsonnettest: kubecfg lib/kubecfg_test.jsonnet + ./kubecfg -J lib show lib/kubecfg_test.jsonnet + vet: $(GO) vet $(GO_FLAGS) $(GO_PACKAGES) diff --git a/README.md b/README.md index 7adc1f01..969bebf8 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ avoid an immediate `Killed: 9`. - Supports JSON, YAML or jsonnet files (by file suffix). - Best-effort sorts objects before updating, so that dependencies are pushed to the server before objects that refer to them. +- Additional jsonnet builtin functions. See `lib/kubecfg.libsonnet`. ## Infrastructure-as-code Philosophy diff --git a/cmd/root.go b/cmd/root.go index 1bf86f61..9be10471 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -91,6 +91,8 @@ func JsonnetVM(cmd *cobra.Command) (*jsonnet.VM, error) { vm.ExtVar(kv[0], kv[1]) } + utils.RegisterNativeFuncs(vm) + return vm, nil } diff --git a/lib/kubecfg.libsonnet b/lib/kubecfg.libsonnet new file mode 100644 index 00000000..a6e271b4 --- /dev/null +++ b/lib/kubecfg.libsonnet @@ -0,0 +1,11 @@ +{ + // parseJson(data): parses the `data` string as a json document, and + // returns the resulting jsonnet object. + parseJson:: std.native("parseJson"), + + // parseYaml(data): parse the `data` string as a YAML stream, and + // returns an *array* of the resulting jsonnet objects. A single + // YAML document will still be returned as an array with one + // element. + parseYaml:: std.native("parseYaml"), +} diff --git a/lib/kubecfg_test.jsonnet b/lib/kubecfg_test.jsonnet new file mode 100644 index 00000000..8184ee4b --- /dev/null +++ b/lib/kubecfg_test.jsonnet @@ -0,0 +1,20 @@ +// Run me with `../kubecfg show kubecfg_test.jsonnet` +local kubecfg = import "kubecfg.libsonnet"; + +assert kubecfg.parseJson("[3, 4]") == [3, 4]; + +local x = kubecfg.parseYaml("--- +- 3 +- 4 +--- +foo: bar +baz: xyzzy +"); +assert x == [[3, 4], {foo: "bar", baz: "xyzzy"}] : "got " + x; + +// Kubecfg wants to see something that looks like a k8s object +{ + apiVersion: "test", + kind: "Result", + result: "SUCCESS" +} diff --git a/utils/nativefuncs.go b/utils/nativefuncs.go new file mode 100644 index 00000000..a6b4731e --- /dev/null +++ b/utils/nativefuncs.go @@ -0,0 +1,33 @@ +package utils + +import ( + "bytes" + "encoding/json" + "io" + + jsonnet "github.com/strickyak/jsonnet_cgo" + "k8s.io/client-go/pkg/util/yaml" +) + +func RegisterNativeFuncs(vm *jsonnet.VM) { + 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 + } + return nil, err + } + ret = append(ret, doc) + } + return ret, nil + }) +} diff --git a/utils/nativefuncs_test.go b/utils/nativefuncs_test.go new file mode 100644 index 00000000..6bcbc771 --- /dev/null +++ b/utils/nativefuncs_test.go @@ -0,0 +1,59 @@ +package utils + +import ( + "testing" + + jsonnet "github.com/strickyak/jsonnet_cgo" +) + +// check there is no err, and a == b. +func check(t *testing.T, err error, actual, expected string) { + if err != nil { + t.Errorf("Expected %q, got error: %q", expected, err.Error()) + } else if actual != expected { + t.Errorf("Expected %q, got %q", expected, actual) + } +} + +func TestParseJson(t *testing.T) { + vm := jsonnet.Make() + defer vm.Destroy() + RegisterNativeFuncs(vm) + + _, err := vm.EvaluateSnippet("failtest", `std.native("parseJson")("barf{")`) + if err == nil { + t.Errorf("parseJson succeeded on invalid json") + } + + x, err := vm.EvaluateSnippet("test", `std.native("parseJson")("null")`) + check(t, err, x, "null\n") + + x, err = vm.EvaluateSnippet("test", ` + local a = std.native("parseJson")('{"foo": 3, "bar": 4}'); + a.foo + a.bar`) + check(t, err, x, "7\n") +} + +func TestParseYaml(t *testing.T) { + vm := jsonnet.Make() + defer vm.Destroy() + RegisterNativeFuncs(vm) + + _, err := vm.EvaluateSnippet("failtest", `std.native("parseYaml")("[barf")`) + if err == nil { + t.Errorf("parseYaml succeeded on invalid yaml") + } + + x, err := vm.EvaluateSnippet("test", `std.native("parseYaml")("")`) + check(t, err, x, "[ ]\n") + + 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") + + x, err = vm.EvaluateSnippet("test", ` + local a = std.native("parseYaml")("---\nhello\n---\nworld"); + a[0] + a[1]`) + check(t, err, x, "\"helloworld\"\n") +} -- GitLab