acquire.go 4.31 KB
Newer Older
Angus Lees's avatar
Angus Lees committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Copyright 2017 The kubecfg authors
//
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.

16 17 18
package utils

import (
Angus Lees's avatar
Angus Lees committed
19
	"bufio"
20
	"encoding/json"
21 22 23 24 25 26 27 28
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/golang/glog"
	jsonnet "github.com/strickyak/jsonnet_cgo"
Angus Lees's avatar
Angus Lees committed
29
	"k8s.io/client-go/pkg/runtime"
Angus Lees's avatar
Angus Lees committed
30
	"k8s.io/client-go/pkg/util/yaml"
31 32 33 34 35
)

// Read fetches and decodes K8s objects by path.
// TODO: Replace this with something supporting more sophisticated
// content negotiation.
Angus Lees's avatar
Angus Lees committed
36
func Read(vm *jsonnet.VM, path string) ([]runtime.Object, error) {
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
	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)
}

Angus Lees's avatar
Angus Lees committed
59
func jsonReader(r io.Reader) ([]runtime.Object, error) {
60 61 62 63
	data, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}
Angus Lees's avatar
Angus Lees committed
64
	obj, _, err := runtime.UnstructuredJSONScheme.Decode(data, nil, nil)
65 66 67
	if err != nil {
		return nil, err
	}
Angus Lees's avatar
Angus Lees committed
68
	return []runtime.Object{obj}, nil
69 70
}

Angus Lees's avatar
Angus Lees committed
71
func yamlReader(r io.ReadCloser) ([]runtime.Object, error) {
Angus Lees's avatar
Angus Lees committed
72
	decoder := yaml.NewYAMLReader(bufio.NewReader(r))
Angus Lees's avatar
Angus Lees committed
73
	ret := []runtime.Object{}
74
	for {
Angus Lees's avatar
Angus Lees committed
75
		bytes, err := decoder.Read()
76 77 78 79 80
		if err == io.EOF {
			break
		} else if err != nil {
			return nil, err
		}
Angus Lees's avatar
Angus Lees committed
81 82 83 84 85
		glog.V(4).Infof("Read %d bytes of YAML: %s", len(bytes), bytes)
		if len(bytes) == 0 {
			continue
		}
		jsondata, err := yaml.ToJSON(bytes)
86 87 88
		if err != nil {
			return nil, err
		}
Angus Lees's avatar
Angus Lees committed
89
		glog.V(4).Infof("Converted to JSON: %s", jsondata)
Angus Lees's avatar
Angus Lees committed
90
		obj, _, err := runtime.UnstructuredJSONScheme.Decode(jsondata, nil, nil)
91 92 93
		if err != nil {
			return nil, err
		}
Angus Lees's avatar
Angus Lees committed
94
		ret = append(ret, obj)
95 96 97 98
	}
	return ret, nil
}

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
func jsonWalk(obj interface{}) ([]interface{}, error) {
	switch o := obj.(type) {
	case map[string]interface{}:
		if o["kind"] != nil && o["apiVersion"] != nil {
			return []interface{}{o}, nil
		}
		ret := []interface{}{}
		for _, v := range o {
			children, err := jsonWalk(v)
			if err != nil {
				return nil, err
			}
			ret = append(ret, children...)
		}
		return ret, nil
	case []interface{}:
		ret := make([]interface{}, 0, len(o))
		for _, v := range o {
			children, err := jsonWalk(v)
			if err != nil {
				return nil, err
			}
			ret = append(ret, children...)
		}
		return ret, nil
	default:
		return nil, fmt.Errorf("Unexpected object structure: %T", o)
	}
}

Angus Lees's avatar
Angus Lees committed
129
func jsonnetReader(vm *jsonnet.VM, path string) ([]runtime.Object, error) {
130 131 132 133 134 135 136
	jsonstr, err := vm.EvaluateFile(path)
	if err != nil {
		return nil, err
	}

	glog.V(4).Infof("jsonnet result is: %s\n", jsonstr)

137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
	var top interface{}
	if err = json.Unmarshal([]byte(jsonstr), &top); err != nil {
		return nil, err
	}

	objs, err := jsonWalk(top)
	if err != nil {
		return nil, err
	}

	ret := make([]runtime.Object, 0, len(objs))
	for _, v := range objs {
		// TODO: Going to json and back is a bit horrible
		data, err := json.Marshal(v)
		if err != nil {
			return nil, err
		}
		obj, _, err := runtime.UnstructuredJSONScheme.Decode(data, nil, nil)
		if err != nil {
			return nil, err
		}
		ret = append(ret, obj)
	}

	return ret, nil
162 163 164
}

// FlattenToV1 expands any List-type objects into their members, and
Angus Lees's avatar
Angus Lees committed
165
// cooerces everything to v1.Unstructured.  Panics if coercion
166
// encounters an unexpected object type.
Angus Lees's avatar
Angus Lees committed
167 168
func FlattenToV1(objs []runtime.Object) []*runtime.Unstructured {
	ret := make([]*runtime.Unstructured, 0, len(objs))
169 170
	for _, obj := range objs {
		switch o := obj.(type) {
Angus Lees's avatar
Angus Lees committed
171
		case *runtime.UnstructuredList:
172
			for _, item := range o.Items {
Angus Lees's avatar
Angus Lees committed
173
				ret = append(ret, item)
174
			}
Angus Lees's avatar
Angus Lees committed
175
		case *runtime.Unstructured:
176 177 178 179 180 181 182
			ret = append(ret, o)
		default:
			panic("Unexpected unstructured object type")
		}
	}
	return ret
}