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

573 lines
15 KiB
Go

// Copyright 2016-2017 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 (
"fmt"
"os"
"reflect"
"strconv"
"strings"
"github.com/Sirupsen/logrus"
"github.com/vmware/vic/pkg/log"
)
const (
// GuestInfoPrefix is dictated by vSphere
GuestInfoPrefix = "guestinfo."
// ScopeTag is the tag name used for declaring scopes for a field
ScopeTag = "scope"
// KeyTag is the tag name by which to override default key naming based on field name
KeyTag = "key"
// RecurseTag is the tag name with which different recursion properties are declared
RecurseTag = "recurse"
// HiddenScope means the key is hidden from the guest and will not have a GuestInfoPrefix
HiddenScope = "hidden"
// ReadOnlyScope means the key is read-only from the guest
ReadOnlyScope = "read-only"
// ReadWriteScope means the key may be read and modified by the guest
ReadWriteScope = "read-write"
// VolatileScope means that the value is expected to change and should be refreshed on use
VolatileScope = "volatile"
// SecretSuffix means the value should be encrypted in the vmx.
SecretSuffix = "secret"
// NonPersistentSuffix means the key should only be written if the key will be deleted on guest power off.
NonPersistentSuffix = "non-persistent"
// RecurseDepthProperty controls how deep to recuse into a structure field from this level. A value of zero
// prevents both encode and decode of that field. This is provided to control recursion into unannotated structures.
// This is unbounded if not specified.
RecurseDepthProperty = "depth"
// RecurseFollowProperty instructs encode and decode to follow pointers.
RecurseFollowProperty = "follow"
// RecurseNoFollowProperty instructs encode and decode not to follow pointers.
RecurseNoFollowProperty = "nofollow"
// RecurseSkipEncodeProperty causes the marked field and subfields to be skipped when encoding.
RecurseSkipEncodeProperty = "skip-encode"
// RecurseSkipDecodeProperty causes the marked field and subfields to be skipped when decoding.
RecurseSkipDecodeProperty = "skip-decode"
)
// TODO: this entire section of variables should be turned into a config struct
// that can be passed to Encode and Decode, or that Encode and Decode are method on
var (
// DefaultTagName is the annotation tag name we use for basic semantic version. Not currently used.
DefaultTagName = "vic"
// DefaultPrefix is prepended to generated key paths for basic namespacing
DefaultPrefix = "vice."
//Separator for slice values and map keys
Separator = "|"
// suffix separator character
suffixSeparator = "@"
)
func defaultGuestInfoPrefix() string {
return GuestInfoPrefix + DefaultPrefix
}
const (
// Invalid value
Invalid = 1 << iota
// Hidden value
Hidden
// ReadOnly value
ReadOnly
// WriteOnly value
WriteOnly
// ReadWrite value
ReadWrite
// NonPersistent value
NonPersistent
// Volatile value
Volatile
// Secret value
Secret
)
type recursion struct {
// depth is a recursion depth, 0 equating to skip field
depth int
// follow controls whether we follow pointers
follow bool
// set to skip decode of a field but still allow encode
skipDecode bool
// set to skip encode of a field but still allow decode
skipEncode bool
}
// Unbounded is the value used for unbounded recursion
var Unbounded = recursion{depth: -1, follow: true}
var logger = &logrus.Logger{
Out: os.Stderr,
// We're using our own text formatter to skip the \n and \t escaping logrus
// was doing on non TTY Out (we redirect to a file) descriptors.
Formatter: log.NewTextFormatter(),
Hooks: make(logrus.LevelHooks),
Level: logrus.InfoLevel,
}
// SetLogLevel for the extraconfig package
func SetLogLevel(level logrus.Level) {
logger.Level = level
}
// calculateScope returns the uint representation of scope tag
func calculateScope(scopes []string) uint {
var scope uint
empty := true
for i := range scopes {
if scopes[i] != "" {
empty = false
break
}
}
if empty {
return Hidden | ReadOnly
}
for _, v := range scopes {
switch v {
case HiddenScope:
scope |= Hidden
case ReadOnlyScope:
scope |= ReadOnly
case ReadWriteScope:
scope |= ReadWrite
case VolatileScope:
scope |= Volatile
case NonPersistentSuffix:
scope |= NonPersistent
case SecretSuffix:
scope |= Secret | ReadOnly
default:
return Invalid
}
}
return scope
}
func isSecret(key string) bool {
suffix := strings.Split(key, suffixSeparator)
if len(suffix) < 2 {
// no @ separator
return false
}
for i := range suffix[1:] {
if suffix[i+1] == SecretSuffix {
return true
}
}
return false
}
func isNonPersistent(key string) bool {
suffix := strings.Split(key, suffixSeparator)
if len(suffix) < 2 {
// no @ separator
return false
}
for i := range suffix[1:] {
if suffix[i+1] == NonPersistentSuffix {
return true
}
}
return false
}
func calculateScopeFromKey(key string) []string {
scopes := []string{}
if !strings.HasPrefix(key, GuestInfoPrefix) {
scopes = append(scopes, HiddenScope)
}
if strings.Contains(key, "/") {
scopes = append(scopes, ReadOnlyScope)
} else {
scopes = append(scopes, ReadWriteScope)
}
if isSecret(key) {
scopes = append(scopes, SecretSuffix)
}
if isNonPersistent(key) {
scopes = append(scopes, NonPersistentSuffix)
}
return scopes
}
func calculateKeyFromField(field reflect.StructField, prefix string, depth recursion) (string, recursion) {
skip := recursion{}
//skip unexported fields
if field.PkgPath != "" {
logger.Debugf("Skipping %s (not exported)", field.Name)
return "", skip
}
// get the annotations
tags := field.Tag
logger.Debugf("Tags: %#v", tags)
var key string
var scopes []string
var scope uint
fdepth := depth
prefixScopes := calculateScopeFromKey(prefix)
prefixScope := calculateScope(prefixScopes)
// do we have DefaultTagName?
if tags.Get(DefaultTagName) != "" {
// get the scopes
scopes = strings.Split(tags.Get(ScopeTag), ",")
logger.Debugf("Scopes: %#v", scopes)
// get the keys and split properties from it
key = tags.Get(KeyTag)
logger.Debugf("Key specified: %s", key)
// get the keys and split properties from it
recurse := tags.Get(RecurseTag)
if recurse != "" {
props := strings.Split(recurse, ",")
// process properties
for _, prop := range props {
// determine recursion depth
if strings.HasPrefix(prop, RecurseDepthProperty) {
parts := strings.Split(prop, "=")
if len(parts) != 2 {
logger.Warnf("Skipping field with incorrect recurse property: %s", prop)
return "", skip
}
val, err := strconv.ParseInt(parts[1], 10, 64)
if err != nil {
logger.Warnf("Skipping field with incorrect recurse value: %s", parts[1])
return "", skip
}
fdepth.depth = int(val)
} else if prop == RecurseNoFollowProperty {
fdepth.follow = false
} else if prop == RecurseFollowProperty {
fdepth.follow = true
} else if prop == RecurseSkipDecodeProperty {
fdepth.skipDecode = true
} else if prop == RecurseSkipEncodeProperty {
fdepth.skipEncode = true
} else {
logger.Warnf("Ignoring unknown recurse property %s (%s)", key, prop)
continue
}
}
}
} else {
logger.Debugf("%s not tagged - inheriting parent scope", field.Name)
scopes = prefixScopes
}
if key == "" {
logger.Debugf("%s does not specify key - defaulting to fieldname", field.Name)
key = field.Name
}
scope = calculateScope(scopes)
// non-persistent is inherited, even if other scopes are specified
if prefixScope&NonPersistent != 0 {
scope |= NonPersistent
}
// re-calculate the key based on the scope and prefix
if key = calculateKey(scope, prefix, key); key == "" {
logger.Debugf("Skipping %s (unknown scope %s)", field.Name, scopes)
return "", skip
}
return key, fdepth
}
// calculateKey calculates the key based on the scope and current prefix
func calculateKey(scope uint, prefix string, key string) string {
if scope&Invalid != 0 {
logger.Debugf("invalid scope")
return ""
}
newSep := "/"
oldSep := "."
key = strings.TrimSpace(key)
hide := scope&Hidden != 0
write := scope&ReadWrite != 0
visible := strings.HasPrefix(prefix, GuestInfoPrefix)
if !hide && write {
oldSep = "/"
newSep = "."
}
// strip any existing suffix from the prefix - it'll be re-added if still applicable
suffix := strings.Index(prefix, suffixSeparator)
if suffix != -1 {
prefix = prefix[:suffix]
}
// assemble the actual keypath with appropriate separators
out := key
if prefix != "" {
out = strings.Join([]string{prefix, key}, newSep)
}
if scope&Secret != 0 {
out += suffixSeparator + SecretSuffix
}
if scope&NonPersistent != 0 {
if hide {
logger.Debugf("Unable to combine non-persistent and hidden scopes")
return ""
}
out += suffixSeparator + NonPersistentSuffix
}
// we don't care about existing separators when hiden
if hide {
if !visible {
return out
}
// strip the prefix and the leading r/w signifier
return out[len(defaultGuestInfoPrefix())+1:]
}
// ensure that separators are correct
out = strings.Replace(out, oldSep, newSep, -1)
// Assemble the base that controls key publishing in guest
if !visible {
return defaultGuestInfoPrefix() + newSep + out
}
// prefix will have been mangled by strings.Replace
return defaultGuestInfoPrefix() + out[len(defaultGuestInfoPrefix()):]
}
// utility function to allow adding of arbitrary prefix into key
// header is a leading segment that is preserved, prefix is injected after that
func addPrefixToKey(header, prefix, key string) string {
if prefix == "" {
return key
}
base := strings.TrimPrefix(key, header)
separator := base[0]
var modifiedPrefix string
if separator == '.' {
modifiedPrefix = strings.Replace(prefix, "/", ".", -1)
} else {
modifiedPrefix = strings.Replace(prefix, ".", "/", -1)
}
// we assume (given usage comment for WithPrefix) that there's no leading or trailing separator
// on the prefix. base has a leading separator
// guestinfoPrefix is const so adding it to the format string directly
return fmt.Sprintf(header+"%c%s%s", separator, modifiedPrefix, base)
}
// appendToPrefix will join the value to the prefix with the separator (if any) while ensuring that
// any suffixes are moved to the end of the key
func appendToPrefix(prefix, separator, value string) string {
// strip any existing suffix from the prefix - it'll be re-added if still applicable
index := strings.Index(prefix, suffixSeparator)
suffix := ""
if index != -1 {
suffix = prefix[index:]
prefix = prefix[:index]
}
// suffix wil still include the suffix separator if present
key := fmt.Sprintf("%s%s%s%s", prefix, separator, value, suffix)
return key
}
func calculateKeys(v reflect.Value, field string, prefix string) []string {
logger.Debugf("v=%#v, field=%#v, prefix=%#v", v, field, prefix)
if v.Kind() == reflect.Ptr {
return calculateKeys(v.Elem(), field, prefix)
}
if field == "" {
return []string{prefix}
}
s := strings.SplitN(field, ".", 2)
field = ""
iterate := false
if s[0] == "*" {
iterate = true
}
if len(s) > 1 {
field = s[1]
}
if !iterate {
switch v.Kind() {
case reflect.Map:
found := false
for _, k := range v.MapKeys() {
sk := k.Convert(reflect.TypeOf(""))
if sk.String() == s[0] {
v = v.MapIndex(k)
found = true
break
}
}
if !found {
panic(fmt.Sprintf("could not find map key %s", s[0]))
}
prefix = appendToPrefix(prefix, Separator, s[0])
case reflect.Array, reflect.Slice:
i, err := strconv.Atoi(s[0])
if err != nil {
panic(fmt.Sprintf("bad array index %s: %s", s[0], err))
}
switch v.Type().Elem().Kind() {
case reflect.Struct:
prefix = appendToPrefix(prefix, Separator, fmt.Sprintf("%d", i))
case reflect.Uint8:
return []string{prefix}
default:
prefix = appendToPrefix(prefix, "", "~")
}
v = v.Index(i)
case reflect.Struct:
f, found := v.Type().FieldByName(s[0])
if !found {
panic(fmt.Sprintf("could not find field %s", s[0]))
}
prefix, _ = calculateKeyFromField(f, prefix, recursion{})
v = v.FieldByIndex(f.Index)
default:
panic(fmt.Sprintf("cannot get field from type %s", v.Type()))
}
return calculateKeys(v, field, prefix)
}
var out []string
switch v.Kind() {
case reflect.Map:
for _, k := range v.MapKeys() {
sk := k.Convert(reflect.TypeOf(""))
prefix := appendToPrefix(prefix, Separator, sk.String())
out = append(out, calculateKeys(v.MapIndex(k), field, prefix)...)
}
case reflect.Array, reflect.Slice:
switch v.Type().Elem().Kind() {
case reflect.Struct:
for i := 0; i < v.Len(); i++ {
prefix := appendToPrefix(prefix, Separator, fmt.Sprintf("%d", i))
out = append(out, calculateKeys(v.Index(i), field, prefix)...)
}
case reflect.Uint8:
return []string{prefix}
default:
return []string{appendToPrefix(prefix, "", "~")}
}
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
prefix, _ := calculateKeyFromField(v.Type().Field(i), prefix, recursion{})
out = append(out, calculateKeys(v.Field(i), field, prefix)...)
}
default:
panic(fmt.Sprintf("can't iterate type %s", v.Type().String()))
}
return out
}
// CalculateKeys gets the keys in extraconfig corresponding to the field
// specification passed in for obj. Examples:
//
// type struct A {
// I int `vic:"0.1" scope:"read-only" key:"i"`
// Str string `vic:"0.1" scope:"read-only" key:"str"`
// }
//
// type struct B {
// A A `vic:"0.1" scope:"read-only" key:"a"`
// Array []A `vic:"0.1" scope:"read-only" key:"array"`
// Map map[string]string `vic:"0.1" scope:"read-only" key:"map"`
// }
//
// b := B{}
// b.Array = []A{A{}}
// b.Map = map[string]string{"foo": "", "bar": ""}
// // returns []string{"a/str"}
// CalculateKeys(b, "A.Str", "")
//
// // returns []string{"array|0"}
// CalculateKeys(b, "Array.0", "")
//
// // returns []string{"array|0"}
// CalculateKeys(b, "Array.*", "")
//
// // returns []string{"map|foo", "map|bar"}
// CalculateKeys(b, "Map.*", "")
//
// // returns []string{"map|foo"}
// CalculateKeys(b, "Map.foo", "")
//
// // returns []string{"map|foo/str"}
// CalculateKeys(b, "Map.foo.str", "")
//
func CalculateKeys(obj interface{}, field string, prefix string) []string {
return calculateKeys(reflect.ValueOf(obj), field, prefix)
}
// CalculateKey is a specific case of CalculateKeys that will panic if more than one key
// matches the field pattern passed in.
func CalculateKey(obj interface{}, field string, prefix string) string {
keys := calculateKeys(reflect.ValueOf(obj), field, prefix)
if len(keys) != 1 {
panic("CalculateKey should only ever return one key")
}
return keys[0]
}