Skip to content
Snippets Groups Projects
Commit 9ddea438 authored by Angus Lees's avatar Angus Lees
Browse files

Add ability to read json/yaml/jsonnet input files

File format is recognised via simple file extension check.

Any "List" objects found will be expanded into their constituent
items, so single-element formats like json and jsonnet can use a
v1.List to capture multiple resource objects.
parent cda51abd
No related branches found
No related tags found
No related merge requests found
package cmd
import (
"encoding/json"
goflag "flag"
"fmt"
"path/filepath"
"github.com/golang/glog"
"github.com/spf13/cobra"
jsonnet "github.com/strickyak/jsonnet_cgo"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/bitnami/kubecfg/utils"
)
func init() {
......@@ -40,30 +43,27 @@ func JsonnetVM(cmd *cobra.Command) (*jsonnet.VM, error) {
return nil, err
}
for _, p := range filepath.SplitList(jpath) {
glog.V(2).Infoln("Adding jsonnet path", p)
glog.V(2).Infoln("Adding jsonnet search path", p)
vm.JpathAdd(p)
}
return vm, nil
}
func evalFile(vm *jsonnet.VM, file string) (interface{}, error) {
var err error
jsonstr := ""
if file != "" {
jsonstr, err = vm.EvaluateFile(file)
if err != nil {
return nil, err
}
}
glog.V(4).Infof("jsonnet result is: %s\n", jsonstr)
var jsobj interface{}
err = json.Unmarshal([]byte(jsonstr), &jsobj)
func readObjs(cmd *cobra.Command, paths []string) ([]metav1.Object, error) {
vm, err := JsonnetVM(cmd)
if err != nil {
return nil, err
}
defer vm.Destroy()
return jsobj, nil
res := []metav1.Object{}
for _, path := range paths {
objs, err := utils.Read(vm, path)
if err != nil {
return nil, fmt.Errorf("Error reading %s: %v", path, err)
}
res = append(res, utils.FlattenToV1(objs)...)
}
return res, nil
}
......@@ -10,8 +10,6 @@ import (
func init() {
RootCmd.AddCommand(showCmd)
showCmd.PersistentFlags().StringP("file", "f", "", "Input jsonnet file")
showCmd.MarkFlagFilename("file", "jsonnet", "libsonnet")
showCmd.PersistentFlags().StringP("format", "o", "yaml", "Output format. Supported values are: json, yaml")
}
......@@ -28,11 +26,7 @@ var showCmd = &cobra.Command{
}
defer vm.Destroy()
file, err := flags.GetString("file")
if err != nil {
return err
}
jsobj, err := evalFile(vm, file)
objs, err := readObjs(cmd, args)
if err != nil {
return err
}
......@@ -43,16 +37,36 @@ var showCmd = &cobra.Command{
}
switch format {
case "yaml":
buf, err := yaml.Marshal(jsobj)
if err != nil {
return err
for _, obj := range objs {
fmt.Fprintln(out, "---")
// Urgh. Go via json because we need
// to trigger the custom scheme
// encoding.
buf, err := json.Marshal(obj)
if err != nil {
return err
}
o := map[string]interface{}{}
if err := json.Unmarshal(buf, &o); err != nil {
return err
}
buf, err = yaml.Marshal(o)
if err != nil {
return err
}
out.Write(buf)
}
out.Write(buf)
case "json":
enc := json.NewEncoder(out)
enc.SetIndent("", " ")
if err := enc.Encode(&jsobj); err != nil {
return err
for _, obj := range objs {
// TODO: this is not valid framing for JSON
if len(objs) > 1 {
fmt.Fprintln(out, "---")
}
if err := enc.Encode(obj); err != nil {
return err
}
}
default:
return fmt.Errorf("Unknown --format: %s", format)
......
......@@ -39,6 +39,8 @@ func TestShow(t *testing.T) {
// Use the fact that JSON is also valid YAML ..
expected := `
{
"apiVersion": "v0alpha1",
"kind": "TestObject",
"nil": null,
"bool": true,
"number": 42,
......@@ -56,8 +58,8 @@ func TestShow(t *testing.T) {
output := cmdOutput(t, []string{"show",
"-J", filepath.FromSlash("../testdata/lib"),
"-f", filepath.FromSlash("../testdata/test.jsonnet"),
"-o", format,
filepath.FromSlash("../testdata/test.jsonnet"),
})
t.Log("output is", output)
......
......@@ -14,7 +14,6 @@ func main() {
cmd.Version = version
if err := cmd.RootCmd.Execute(); err != nil {
fmt.Println("got error")
fmt.Println("Error:", err)
os.Exit(1)
}
......
{
apiVersion: "v0alpha1",
kind: "TestObject",
nil: null,
bool: true,
number: 42,
......
{
apiVersion: "v0alpha1",
kind: "JsonObject"
foo: "bar",
}
local test = import "test.libsonnet";
test {
string: "bar",
{
apiVersion: "v1",
kind: "List",
items: [
test {
string: "bar",
}
],
}
apiVersion: v0alpha0
kind: YamlObject
foo: bar
package utils
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/golang/glog"
jsonnet "github.com/strickyak/jsonnet_cgo"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
unstructuredv1 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apimachinery/pkg/runtime"
)
// Read fetches and decodes K8s objects by path.
// TODO: Replace this with something supporting more sophisticated
// content negotiation.
func Read(vm *jsonnet.VM, path string) ([]runtime.Unstructured, error) {
ext := filepath.Ext(path)
if ext == ".json" {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return jsonReader(f)
} else if ext == ".yaml" {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return yamlReader(f)
} else if ext == ".jsonnet" {
return jsonnetReader(vm, path)
}
return nil, fmt.Errorf("Unknown file extension: %s", path)
}
func jsonReader(r io.Reader) ([]runtime.Unstructured, error) {
data, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
obj, _, err := unstructuredv1.UnstructuredJSONScheme.Decode(data, nil, nil)
if err != nil {
return nil, err
}
return []runtime.Unstructured{obj.(runtime.Unstructured)}, nil
}
func yamlReader(r io.ReadCloser) ([]runtime.Unstructured, error) {
decoder := yaml.NewDocumentDecoder(r)
ret := []runtime.Unstructured{}
buf := []byte{}
for {
_, err := decoder.Read(buf)
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
jsondata, err := yaml.ToJSON(buf)
if err != nil {
return nil, err
}
obj, _, err := unstructuredv1.UnstructuredJSONScheme.Decode(jsondata, nil, nil)
if err != nil {
return nil, err
}
ret = append(ret, obj.(runtime.Unstructured))
}
return ret, nil
}
func jsonnetReader(vm *jsonnet.VM, path string) ([]runtime.Unstructured, error) {
jsonstr, err := vm.EvaluateFile(path)
if err != nil {
return nil, err
}
glog.V(4).Infof("jsonnet result is: %s\n", jsonstr)
return jsonReader(strings.NewReader(jsonstr))
}
// FlattenToV1 expands any List-type objects into their members, and
// cooerces everything to metav1.Objects. Panics if coercion
// encounters an unexpected object type.
func FlattenToV1(objs []runtime.Unstructured) []metav1.Object {
ret := make([]metav1.Object, 0, len(objs))
for _, obj := range objs {
switch o := obj.(type) {
case *unstructuredv1.UnstructuredList:
for _, item := range o.Items {
ret = append(ret, &item)
}
case *unstructuredv1.Unstructured:
ret = append(ret, o)
default:
panic("Unexpected unstructured object type")
}
}
return ret
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment