forked from hswaw/hscloud
227 lines
5.3 KiB
Go
227 lines
5.3 KiB
Go
// 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.
|
|
|
|
package utils
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
jsonnet "github.com/google/go-jsonnet"
|
|
log "github.com/sirupsen/logrus"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/util/yaml"
|
|
)
|
|
|
|
// 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.Object, 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.Object, error) {
|
|
data, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obj, _, err := unstructured.UnstructuredJSONScheme.Decode(data, nil, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []runtime.Object{obj}, nil
|
|
}
|
|
|
|
func yamlReader(r io.ReadCloser) ([]runtime.Object, error) {
|
|
decoder := yaml.NewYAMLReader(bufio.NewReader(r))
|
|
ret := []runtime.Object{}
|
|
for {
|
|
bytes, err := decoder.Read()
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(bytes) == 0 {
|
|
continue
|
|
}
|
|
jsondata, err := yaml.ToJSON(bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obj, _, err := unstructured.UnstructuredJSONScheme.Decode(jsondata, nil, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret = append(ret, obj)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
type walkContext struct {
|
|
parent *walkContext
|
|
label string
|
|
}
|
|
|
|
func (c *walkContext) String() string {
|
|
parent := ""
|
|
if c.parent != nil {
|
|
parent = c.parent.String()
|
|
}
|
|
return parent + c.label
|
|
}
|
|
|
|
func jsonWalk(parentCtx *walkContext, obj interface{}) ([]interface{}, error) {
|
|
switch o := obj.(type) {
|
|
case nil:
|
|
return []interface{}{}, nil
|
|
case map[string]interface{}:
|
|
if o["kind"] != nil && o["apiVersion"] != nil {
|
|
return []interface{}{o}, nil
|
|
}
|
|
ret := []interface{}{}
|
|
for k, v := range o {
|
|
ctx := walkContext{
|
|
parent: parentCtx,
|
|
label: "." + k,
|
|
}
|
|
children, err := jsonWalk(&ctx, v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret = append(ret, children...)
|
|
}
|
|
return ret, nil
|
|
case []interface{}:
|
|
ret := make([]interface{}, 0, len(o))
|
|
for i, v := range o {
|
|
ctx := walkContext{
|
|
parent: parentCtx,
|
|
label: fmt.Sprintf("[%d]", i),
|
|
}
|
|
children, err := jsonWalk(&ctx, v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret = append(ret, children...)
|
|
}
|
|
return ret, nil
|
|
default:
|
|
return nil, fmt.Errorf("Looking for kubernetes object at %s, but instead found %T", parentCtx, o)
|
|
}
|
|
}
|
|
|
|
func jsonnetReader(vm *jsonnet.VM, path string) ([]runtime.Object, error) {
|
|
// TODO: Read via Importer, so we support HTTP, etc for first
|
|
// file too.
|
|
abs, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pathUrl := &url.URL{Scheme: "file", Path: filepath.ToSlash(abs)}
|
|
|
|
bytes, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
jsonstr, err := vm.EvaluateSnippet(pathUrl.String(), string(bytes))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Debugf("jsonnet result is: %s", jsonstr)
|
|
|
|
var top interface{}
|
|
if err = json.Unmarshal([]byte(jsonstr), &top); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
objs, err := jsonWalk(&walkContext{label: "<top>"}, top)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ret := make([]runtime.Object, 0, len(objs))
|
|
for _, v := range objs {
|
|
obj := &unstructured.Unstructured{Object: v.(map[string]interface{})}
|
|
if obj.IsList() {
|
|
// TODO: Use obj.ToList with newer apimachinery
|
|
list := &unstructured.UnstructuredList{
|
|
Object: obj.Object,
|
|
}
|
|
err := obj.EachListItem(func(item runtime.Object) error {
|
|
castItem := item.(*unstructured.Unstructured)
|
|
list.Items = append(list.Items, *castItem)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret = append(ret, list)
|
|
} else {
|
|
ret = append(ret, obj)
|
|
}
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// FlattenToV1 expands any List-type objects into their members, and
|
|
// cooerces everything to v1.Unstructured. Panics if coercion
|
|
// encounters an unexpected object type.
|
|
func FlattenToV1(objs []runtime.Object) []*unstructured.Unstructured {
|
|
ret := make([]*unstructured.Unstructured, 0, len(objs))
|
|
for _, obj := range objs {
|
|
switch o := obj.(type) {
|
|
case *unstructured.UnstructuredList:
|
|
for i := range o.Items {
|
|
ret = append(ret, &o.Items[i])
|
|
}
|
|
case *unstructured.Unstructured:
|
|
ret = append(ret, o)
|
|
default:
|
|
panic("Unexpected unstructured object type")
|
|
}
|
|
}
|
|
return ret
|
|
}
|