mirror of https://github.com/k3s-io/k3s
235 lines
6.3 KiB
Go
235 lines
6.3 KiB
Go
/*
|
|
Copyright 2018 The Kubernetes 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 value
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// FromYAML is a helper function for reading a YAML document; it attempts to
|
|
// preserve order of keys within maps/structs. This is as a convenience to
|
|
// humans keeping YAML documents, not because there is a behavior difference.
|
|
//
|
|
// Known bug: objects with top-level arrays don't parse correctly.
|
|
func FromYAML(input []byte) (Value, error) {
|
|
var decoded interface{}
|
|
|
|
if len(input) == 4 && string(input) == "null" {
|
|
// Special case since the yaml package doesn't accurately
|
|
// preserve this.
|
|
return Value{Null: true}, nil
|
|
}
|
|
|
|
// This attempts to enable order sensitivity; note the yaml package is
|
|
// broken for documents that have root-level arrays, hence the two-step
|
|
// approach. TODO: This is a horrific hack. Is it worth it?
|
|
var ms yaml.MapSlice
|
|
if err := yaml.Unmarshal(input, &ms); err == nil {
|
|
decoded = ms
|
|
} else if err := yaml.Unmarshal(input, &decoded); err != nil {
|
|
return Value{}, err
|
|
}
|
|
|
|
v, err := FromUnstructured(decoded)
|
|
if err != nil {
|
|
return Value{}, fmt.Errorf("failed to interpret (%v):\n%s", err, input)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
// FromJSON is a helper function for reading a JSON document
|
|
func FromJSON(input []byte) (Value, error) {
|
|
var decoded interface{}
|
|
|
|
if err := json.Unmarshal(input, &decoded); err != nil {
|
|
return Value{}, err
|
|
}
|
|
|
|
v, err := FromUnstructured(decoded)
|
|
if err != nil {
|
|
return Value{}, fmt.Errorf("failed to interpret (%v):\n%s", err, input)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
// FromUnstructured will convert a go interface to a Value.
|
|
// It's most commonly expected to be used with map[string]interface{} as the
|
|
// input. `in` must not have any structures with cycles in them.
|
|
// yaml.MapSlice may be used for order-preservation.
|
|
func FromUnstructured(in interface{}) (Value, error) {
|
|
if in == nil {
|
|
return Value{Null: true}, nil
|
|
}
|
|
switch t := in.(type) {
|
|
case map[interface{}]interface{}:
|
|
m := Map{}
|
|
for rawKey, rawVal := range t {
|
|
k, ok := rawKey.(string)
|
|
if !ok {
|
|
return Value{}, fmt.Errorf("key %#v: not a string", k)
|
|
}
|
|
v, err := FromUnstructured(rawVal)
|
|
if err != nil {
|
|
return Value{}, fmt.Errorf("key %v: %v", k, err)
|
|
}
|
|
m.Set(k, v)
|
|
}
|
|
return Value{MapValue: &m}, nil
|
|
case map[string]interface{}:
|
|
m := Map{}
|
|
for k, rawVal := range t {
|
|
v, err := FromUnstructured(rawVal)
|
|
if err != nil {
|
|
return Value{}, fmt.Errorf("key %v: %v", k, err)
|
|
}
|
|
m.Set(k, v)
|
|
}
|
|
return Value{MapValue: &m}, nil
|
|
case yaml.MapSlice:
|
|
m := Map{}
|
|
for _, item := range t {
|
|
k, ok := item.Key.(string)
|
|
if !ok {
|
|
return Value{}, fmt.Errorf("key %#v is not a string", item.Key)
|
|
}
|
|
v, err := FromUnstructured(item.Value)
|
|
if err != nil {
|
|
return Value{}, fmt.Errorf("key %v: %v", k, err)
|
|
}
|
|
m.Set(k, v)
|
|
}
|
|
return Value{MapValue: &m}, nil
|
|
case []interface{}:
|
|
l := List{}
|
|
for i, rawVal := range t {
|
|
v, err := FromUnstructured(rawVal)
|
|
if err != nil {
|
|
return Value{}, fmt.Errorf("index %v: %v", i, err)
|
|
}
|
|
l.Items = append(l.Items, v)
|
|
}
|
|
return Value{ListValue: &l}, nil
|
|
case int:
|
|
n := Int(t)
|
|
return Value{IntValue: &n}, nil
|
|
case int8:
|
|
n := Int(t)
|
|
return Value{IntValue: &n}, nil
|
|
case int16:
|
|
n := Int(t)
|
|
return Value{IntValue: &n}, nil
|
|
case int32:
|
|
n := Int(t)
|
|
return Value{IntValue: &n}, nil
|
|
case int64:
|
|
n := Int(t)
|
|
return Value{IntValue: &n}, nil
|
|
case uint:
|
|
n := Int(t)
|
|
return Value{IntValue: &n}, nil
|
|
case uint8:
|
|
n := Int(t)
|
|
return Value{IntValue: &n}, nil
|
|
case uint16:
|
|
n := Int(t)
|
|
return Value{IntValue: &n}, nil
|
|
case uint32:
|
|
n := Int(t)
|
|
return Value{IntValue: &n}, nil
|
|
case float32:
|
|
f := Float(t)
|
|
return Value{FloatValue: &f}, nil
|
|
case float64:
|
|
f := Float(t)
|
|
return Value{FloatValue: &f}, nil
|
|
case string:
|
|
return StringValue(t), nil
|
|
case bool:
|
|
return BooleanValue(t), nil
|
|
default:
|
|
return Value{}, fmt.Errorf("type unimplemented: %t", in)
|
|
}
|
|
}
|
|
|
|
// ToYAML is a helper function for producing a YAML document; it attempts to
|
|
// preserve order of keys within maps/structs. This is as a convenience to
|
|
// humans keeping YAML documents, not because there is a behavior difference.
|
|
func (v *Value) ToYAML() ([]byte, error) {
|
|
return yaml.Marshal(v.ToUnstructured(true))
|
|
}
|
|
|
|
// ToJSON is a helper function for producing a JSon document.
|
|
func (v *Value) ToJSON() ([]byte, error) {
|
|
return json.Marshal(v.ToUnstructured(false))
|
|
}
|
|
|
|
// ToUnstructured will convert the Value into a go-typed object.
|
|
// If preserveOrder is true, then maps will be converted to the yaml.MapSlice
|
|
// type. Otherwise, map[string]interface{} must be used-- this destroys
|
|
// ordering information and is not recommended if the result of this will be
|
|
// serialized. Other types:
|
|
// * list -> []interface{}
|
|
// * others -> corresponding go type, wrapped in an interface{}
|
|
//
|
|
// Of note, floats and ints will always come out as float64 and int64,
|
|
// respectively.
|
|
func (v *Value) ToUnstructured(preserveOrder bool) interface{} {
|
|
switch {
|
|
case v.FloatValue != nil:
|
|
f := float64(*v.FloatValue)
|
|
return f
|
|
case v.IntValue != nil:
|
|
i := int64(*v.IntValue)
|
|
return i
|
|
case v.StringValue != nil:
|
|
return string(*v.StringValue)
|
|
case v.BooleanValue != nil:
|
|
return bool(*v.BooleanValue)
|
|
case v.ListValue != nil:
|
|
out := []interface{}{}
|
|
for _, item := range v.ListValue.Items {
|
|
out = append(out, item.ToUnstructured(preserveOrder))
|
|
}
|
|
return out
|
|
case v.MapValue != nil:
|
|
m := v.MapValue
|
|
if preserveOrder {
|
|
ms := make(yaml.MapSlice, len(m.Items))
|
|
for i := range m.Items {
|
|
ms[i] = yaml.MapItem{
|
|
Key: m.Items[i].Name,
|
|
Value: m.Items[i].Value.ToUnstructured(preserveOrder),
|
|
}
|
|
}
|
|
return ms
|
|
}
|
|
// This case is unavoidably lossy.
|
|
out := map[string]interface{}{}
|
|
for i := range m.Items {
|
|
out[m.Items[i].Name] = m.Items[i].Value.ToUnstructured(preserveOrder)
|
|
}
|
|
return out
|
|
default:
|
|
fallthrough
|
|
case v.Null == true:
|
|
return nil
|
|
}
|
|
}
|