blob: c97931695955dfab4a5a912f621d9fb0d0decef8 [file] [log] [blame]
// 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
}