Files
virtual-kubelet/vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/encode.go

301 lines
8.7 KiB
Go

// Copyright 2016 VMware, Inc. All Rights Reserved.
//
// 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 extraconfig
import (
"encoding/base64"
"errors"
"fmt"
"net"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"time"
)
var (
ErrKeyNotFound = errors.New("key not found")
)
type encoder func(sink DataSink, src reflect.Value, prefix string, depth recursion)
var kindEncoders map[reflect.Kind]encoder
var intfEncoders map[reflect.Type]encoder
func init() {
kindEncoders = map[reflect.Kind]encoder{
reflect.String: encodeString,
reflect.Struct: encodeStruct,
reflect.Slice: encodeSlice,
reflect.Array: encodeSlice,
reflect.Map: encodeMap,
reflect.Ptr: encodePtr,
reflect.Int: encodePrimitive,
reflect.Int8: encodePrimitive,
reflect.Int16: encodePrimitive,
reflect.Int32: encodePrimitive,
reflect.Int64: encodePrimitive,
reflect.Bool: encodePrimitive,
reflect.Float32: encodePrimitive,
reflect.Float64: encodePrimitive,
}
intfEncoders = map[reflect.Type]encoder{
reflect.TypeOf(time.Time{}): encodeTime,
}
}
// decode is the generic switcher that decides which decoder to use for a field
func encode(sink DataSink, src reflect.Value, prefix string, depth recursion) {
// if depth has reached zero, we skip encoding entirely
if depth.depth == 0 || depth.skipEncode {
return
}
depth.depth--
// obtain the handler from the map, checking for the more specific interfaces first
enc, ok := intfEncoders[src.Type()]
if ok {
enc(sink, src, prefix, depth)
return
}
enc, ok = kindEncoders[src.Kind()]
if ok {
enc(sink, src, prefix, depth)
return
}
logger.Debugf("Skipping unsupported field, interface: %T, kind %s", src, src.Kind())
}
// encodeString is the degenerative case where what we get is what we need
func encodeString(sink DataSink, src reflect.Value, prefix string, depth recursion) {
err := sink(prefix, src.String())
if err != nil {
logger.Errorf("Failed to encode string for key %s: %s", prefix, err)
}
}
// encodePrimitive wraps the toString primitive encoding in a manner that can be called via encode
func encodePrimitive(sink DataSink, src reflect.Value, prefix string, depth recursion) {
err := sink(prefix, toString(src))
if err != nil {
logger.Errorf("Failed to encode primitive for key %s: %s", prefix, err)
}
}
func encodePtr(sink DataSink, src reflect.Value, prefix string, depth recursion) {
// if we're not following pointers, return immediately
if !depth.follow {
return
}
logger.Debugf("Encoding object: %#v", src)
if src.IsNil() {
// no need to attempt anything
return
}
encode(sink, src.Elem(), prefix, depth)
}
func encodeStruct(sink DataSink, src reflect.Value, prefix string, depth recursion) {
logger.Debugf("Encoding object: %#v", src)
// iterate through every field in the struct
for i := 0; i < src.NumField(); i++ {
field := src.Field(i)
key, fdepth := calculateKeyFromField(src.Type().Field(i), prefix, depth)
if key == "" {
logger.Debugf("Skipping field %s with empty computed key", src.Type().Field(i).Name)
continue
}
// Dump what we have so far
logger.Debugf("Key: %s, Kind: %s Value: %s", key, field.Kind(), field.String())
encode(sink, field, key, fdepth)
}
}
func isEncodableSliceElemType(t reflect.Type) bool {
switch t {
case reflect.TypeOf((net.IP)(nil)):
return true
}
return false
}
func encodeSlice(sink DataSink, src reflect.Value, prefix string, depth recursion) {
logger.Debugf("Encoding object: %#v", src)
length := src.Len()
if length == 0 {
logger.Debug("Skipping empty slice")
return
}
// determine the key given the array type
kind := src.Type().Elem().Kind()
if kind == reflect.Uint8 {
// special []byte array handling
logger.Debugf("Converting []byte to base64 string")
str := base64.StdEncoding.EncodeToString(src.Bytes())
encode(sink, reflect.ValueOf(str), prefix, depth)
return
} else if kind == reflect.Struct || isEncodableSliceElemType(src.Type().Elem()) {
for i := 0; i < length; i++ {
// convert key to name|index format
key := appendToPrefix(prefix, Separator, fmt.Sprintf("%d", i))
encode(sink, src.Index(i), key, depth)
}
} else {
// else assume it's primitive - we'll panic/recover and continue it not
defer func() {
if err := recover(); err != nil {
logger.Errorf("unable to encode %s (slice) for %s: %s", src.Type(), prefix, err)
}
}()
values := make([]string, length)
for i := 0; i < length; i++ {
values[i] = toString(src.Index(i))
}
// convert key to name|index format
key := appendToPrefix(prefix, "", "~")
err := sink(key, strings.Join(values, Separator))
if err != nil {
logger.Errorf("Failed to encode slice data for key %s: %s", key, err)
}
}
// prefix contains the length of the array
// seems insane calling toString(ValueOf(..)) but it means we're using the same path for everything
err := sink(prefix, toString(reflect.ValueOf(length-1)))
if err != nil {
logger.Errorf("Failed to encode slice length for key %s: %s", prefix, err)
}
}
func encodeMap(sink DataSink, src reflect.Value, prefix string, depth recursion) {
logger.Debugf("Encoding object: %#v", src)
// iterate over keys and recurse
mkeys := src.MapKeys()
length := len(mkeys)
if length == 0 {
logger.Debug("Skipping empty map")
return
}
logger.Debugf("Encoding map entries based off prefix: %s", prefix)
keys := make([]string, length)
for i, v := range mkeys {
keys[i] = toString(v)
key := appendToPrefix(prefix, Separator, keys[i])
encode(sink, src.MapIndex(v), key, depth)
}
// sort the keys before joining - purely to make testing viable
sort.Strings(keys)
err := sink(prefix, strings.Join(keys, Separator))
if err != nil {
logger.Errorf("Failed to encode map keys for key %s: %s", prefix, err)
}
}
func encodeTime(sink DataSink, src reflect.Value, prefix string, depth recursion) {
err := sink(prefix, src.Interface().(time.Time).String())
if err != nil {
logger.Errorf("Failed to encode time for key %s: %s", prefix, err)
}
}
// toString converts a basic type to its string representation
func toString(field reflect.Value) string {
switch field.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(field.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(field.Uint(), 10)
case reflect.Bool:
return strconv.FormatBool(field.Bool())
case reflect.String:
return field.String()
case reflect.Float32, reflect.Float64:
return strconv.FormatFloat(field.Float(), 'E', -1, 64)
default:
panic(field.Type().String() + " is an unhandled type")
}
}
// DataSink provides a function that, given a key/value will persist that
// in some manner suited for later retrieval
type DataSink func(string, string) error
// Encode serializes the given type to the supplied data sink
func Encode(sink DataSink, src interface{}) {
encode(sink, reflect.ValueOf(src), "", Unbounded)
}
// EncodeWithPrefix serializes the given type to the supplied data sink, using
// the supplied prefix - this allows for serialization of subsections of a
// struct
func EncodeWithPrefix(sink DataSink, src interface{}, prefix string) {
encode(sink, reflect.ValueOf(src), prefix, Unbounded)
}
// MapSink takes a map and populates it with key/value pairs from the encode
func MapSink(sink map[string]string) DataSink {
// this is a very basic mechanism of allowing serialized updates to a sink
// a more involved approach is necessary if wanting to do concurrent read/write
mutex := sync.Mutex{}
return func(key, value string) error {
mutex.Lock()
defer mutex.Unlock()
sink[key] = value
return nil
}
}
// ScopeFilterSink will create a DataSink that only stores entries where the key scope
// matches one or more scopes in the filter.
// The filter is a bitwise composion of scope flags
func ScopeFilterSink(filter uint, sink DataSink) DataSink {
return func(key, value string) error {
logger.Debugf("Filtering encode of %s with scopes: %v", key, calculateScopeFromKey(key))
scope := calculateScope(calculateScopeFromKey(key))
if scope&filter != 0 {
sink(key, value)
} else {
logger.Debugf("Skipping encode of %s with scopes that do not match filter: %v", key, calculateScopeFromKey(key))
}
return nil
}
}