blob: c97931695955dfab4a5a912f621d9fb0d0decef8 [file] [log] [blame]
Serge Bazanskibe538db2020-11-12 00:22:42 +01001// Copyright 2017 The kubecfg authors
2//
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16package utils
17
18import (
19 "bufio"
20 "encoding/json"
21 "fmt"
22 "io"
23 "io/ioutil"
24 "net/url"
25 "os"
26 "path/filepath"
27
28 jsonnet "github.com/google/go-jsonnet"
29 log "github.com/sirupsen/logrus"
30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
31 "k8s.io/apimachinery/pkg/runtime"
32 "k8s.io/apimachinery/pkg/util/yaml"
33)
34
35// Read fetches and decodes K8s objects by path.
36// TODO: Replace this with something supporting more sophisticated
37// content negotiation.
38func Read(vm *jsonnet.VM, path string) ([]runtime.Object, error) {
39 ext := filepath.Ext(path)
40 if ext == ".json" {
41 f, err := os.Open(path)
42 if err != nil {
43 return nil, err
44 }
45 defer f.Close()
46 return jsonReader(f)
47 } else if ext == ".yaml" {
48 f, err := os.Open(path)
49 if err != nil {
50 return nil, err
51 }
52 defer f.Close()
53 return yamlReader(f)
54 } else if ext == ".jsonnet" {
55 return jsonnetReader(vm, path)
56 }
57
58 return nil, fmt.Errorf("Unknown file extension: %s", path)
59}
60
61func jsonReader(r io.Reader) ([]runtime.Object, error) {
62 data, err := ioutil.ReadAll(r)
63 if err != nil {
64 return nil, err
65 }
66 obj, _, err := unstructured.UnstructuredJSONScheme.Decode(data, nil, nil)
67 if err != nil {
68 return nil, err
69 }
70 return []runtime.Object{obj}, nil
71}
72
73func yamlReader(r io.ReadCloser) ([]runtime.Object, error) {
74 decoder := yaml.NewYAMLReader(bufio.NewReader(r))
75 ret := []runtime.Object{}
76 for {
77 bytes, err := decoder.Read()
78 if err == io.EOF {
79 break
80 } else if err != nil {
81 return nil, err
82 }
83 if len(bytes) == 0 {
84 continue
85 }
86 jsondata, err := yaml.ToJSON(bytes)
87 if err != nil {
88 return nil, err
89 }
90 obj, _, err := unstructured.UnstructuredJSONScheme.Decode(jsondata, nil, nil)
91 if err != nil {
92 return nil, err
93 }
94 ret = append(ret, obj)
95 }
96 return ret, nil
97}
98
99type walkContext struct {
100 parent *walkContext
101 label string
102}
103
104func (c *walkContext) String() string {
105 parent := ""
106 if c.parent != nil {
107 parent = c.parent.String()
108 }
109 return parent + c.label
110}
111
112func jsonWalk(parentCtx *walkContext, obj interface{}) ([]interface{}, error) {
113 switch o := obj.(type) {
114 case nil:
115 return []interface{}{}, nil
116 case map[string]interface{}:
117 if o["kind"] != nil && o["apiVersion"] != nil {
118 return []interface{}{o}, nil
119 }
120 ret := []interface{}{}
121 for k, v := range o {
122 ctx := walkContext{
123 parent: parentCtx,
124 label: "." + k,
125 }
126 children, err := jsonWalk(&ctx, v)
127 if err != nil {
128 return nil, err
129 }
130 ret = append(ret, children...)
131 }
132 return ret, nil
133 case []interface{}:
134 ret := make([]interface{}, 0, len(o))
135 for i, v := range o {
136 ctx := walkContext{
137 parent: parentCtx,
138 label: fmt.Sprintf("[%d]", i),
139 }
140 children, err := jsonWalk(&ctx, v)
141 if err != nil {
142 return nil, err
143 }
144 ret = append(ret, children...)
145 }
146 return ret, nil
147 default:
148 return nil, fmt.Errorf("Looking for kubernetes object at %s, but instead found %T", parentCtx, o)
149 }
150}
151
152func jsonnetReader(vm *jsonnet.VM, path string) ([]runtime.Object, error) {
153 // TODO: Read via Importer, so we support HTTP, etc for first
154 // file too.
155 abs, err := filepath.Abs(path)
156 if err != nil {
157 return nil, err
158 }
159 pathUrl := &url.URL{Scheme: "file", Path: filepath.ToSlash(abs)}
160
161 bytes, err := ioutil.ReadFile(path)
162 if err != nil {
163 return nil, err
164 }
165
166 jsonstr, err := vm.EvaluateSnippet(pathUrl.String(), string(bytes))
167 if err != nil {
168 return nil, err
169 }
170
171 log.Debugf("jsonnet result is: %s", jsonstr)
172
173 var top interface{}
174 if err = json.Unmarshal([]byte(jsonstr), &top); err != nil {
175 return nil, err
176 }
177
178 objs, err := jsonWalk(&walkContext{label: "<top>"}, top)
179 if err != nil {
180 return nil, err
181 }
182
183 ret := make([]runtime.Object, 0, len(objs))
184 for _, v := range objs {
185 obj := &unstructured.Unstructured{Object: v.(map[string]interface{})}
186 if obj.IsList() {
187 // TODO: Use obj.ToList with newer apimachinery
188 list := &unstructured.UnstructuredList{
189 Object: obj.Object,
190 }
191 err := obj.EachListItem(func(item runtime.Object) error {
192 castItem := item.(*unstructured.Unstructured)
193 list.Items = append(list.Items, *castItem)
194 return nil
195 })
196 if err != nil {
197 return nil, err
198 }
199 ret = append(ret, list)
200 } else {
201 ret = append(ret, obj)
202 }
203 }
204
205 return ret, nil
206}
207
208// FlattenToV1 expands any List-type objects into their members, and
209// cooerces everything to v1.Unstructured. Panics if coercion
210// encounters an unexpected object type.
211func FlattenToV1(objs []runtime.Object) []*unstructured.Unstructured {
212 ret := make([]*unstructured.Unstructured, 0, len(objs))
213 for _, obj := range objs {
214 switch o := obj.(type) {
215 case *unstructured.UnstructuredList:
216 for i := range o.Items {
217 ret = append(ret, &o.Items[i])
218 }
219 case *unstructured.Unstructured:
220 ret = append(ret, o)
221 default:
222 panic("Unexpected unstructured object type")
223 }
224 }
225 return ret
226}