VMware vSphere Integrated Containers provider (#206)
* Add Virtual Kubelet provider for VIC Initial virtual kubelet provider for VMware VIC. This provider currently handles creating and starting of a pod VM via the VIC portlayer and persona server. Image store handling via the VIC persona server. This provider currently requires the feature/wolfpack branch of VIC. * Added pod stop and delete. Also added node capacity. Added the ability to stop and delete pod VMs via VIC. Also retrieve node capacity information from the VCH. * Cleanup and readme file Some file clean up and added a Readme.md markdown file for the VIC provider. * Cleaned up errors, added function comments, moved operation code 1. Cleaned up error handling. Set standard for creating errors. 2. Added method prototype comments for all interface functions. 3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder. * Add mocking code and unit tests for podcache, podcreator, and podstarter Used the unit test framework used in VIC to handle assertions in the provider's unit test. Mocking code generated using OSS project mockery, which is compatible with the testify assertion framework. * Vendored packages for the VIC provider Requires feature/wolfpack branch of VIC and a few specific commit sha of projects used within VIC. * Implementation of POD Stopper and Deleter unit tests (#4) * Updated files for initial PR
This commit is contained in:
454
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/basic_test.go
generated
vendored
Normal file
454
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/basic_test.go
generated
vendored
Normal file
@@ -0,0 +1,454 @@
|
||||
// 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"
|
||||
"net"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// [BEGIN] SLIMMED DOWNED and MODIFIED VERSION of github.com/vmware/vic/lib/metadata
|
||||
type Common struct {
|
||||
ExecutionEnvironment string `vic:"0.1" recurse:"depth=0"`
|
||||
|
||||
ID string `vic:"0.1" scope:"read-only" key:"id"`
|
||||
|
||||
Name string `vic:"0.1" scope:"read-only" key:"name"`
|
||||
|
||||
Notes string `vic:"0.1" scope:"read-only" key:"notes"`
|
||||
}
|
||||
|
||||
type ContainerVM struct {
|
||||
Common `vic:"0.1" scope:"read-only" key:"common"`
|
||||
|
||||
Version string `vic:"0.1" scope:"hidden" key:"version"`
|
||||
|
||||
Aliases map[string]string `vic:"0.1" recurse:"depth=0"`
|
||||
|
||||
Interaction url.URL `vic:"0.1" recurse:"depth=0"`
|
||||
|
||||
AgentKey []byte `vic:"0.1" recurse:"depth=0"`
|
||||
}
|
||||
|
||||
type ExecutorConfig struct {
|
||||
Common `vic:"0.1" scope:"read-only" key:"common"`
|
||||
|
||||
Sessions map[string]SessionConfig `vic:"0.1" scope:"hidden" key:"sessions"`
|
||||
|
||||
Key string `json:"string"`
|
||||
}
|
||||
|
||||
type ExecutorConfigPointers struct {
|
||||
Common `vic:"0.1" scope:"read-only" key:"common"`
|
||||
|
||||
Sessions map[string]*SessionConfig `vic:"0.1" scope:"hidden" key:"sessions"`
|
||||
|
||||
Key string `json:"string"` // will inherit parent vic attributes
|
||||
}
|
||||
|
||||
type Cmd struct {
|
||||
Path string `vic:"0.1" scope:"hidden" key:"path"`
|
||||
|
||||
Args []string `vic:"0.1" scope:"hidden" key:"args"`
|
||||
|
||||
Env []string `vic:"0.1" scope:"hidden" key:"env"`
|
||||
|
||||
Dir string `vic:"0.1" scope:"hidden" key:"dir"`
|
||||
|
||||
Cmd *exec.Cmd `vic:"0.1" scope:"hidden" key:"cmd" recurse:"depth=0"`
|
||||
}
|
||||
|
||||
type SessionConfig struct {
|
||||
Common `vic:"0.1" scope:"hidden" key:"common" json:"page"`
|
||||
|
||||
Cmd Cmd `vic:"0.1" scope:"hidden" key:"cmd"`
|
||||
|
||||
Tty bool `vic:"0.1" scope:"hidden" key:"tty"`
|
||||
}
|
||||
|
||||
type ExecutorConfigPointersVisible struct {
|
||||
Sessions map[string]*VisibleSessionConfig `vic:"0.1" scope:"read-only" key:"sessions"`
|
||||
}
|
||||
|
||||
type VisibleSessionConfig struct {
|
||||
Cmd Cmd `vic:"0.1" scope:"read-only" key:"cmd"`
|
||||
|
||||
Tty bool `vic:"0.1" scope:"read-only" key:"tty"`
|
||||
}
|
||||
|
||||
// [END] SLIMMED VERSION of github.com/vmware/vic/lib/metadata
|
||||
|
||||
// make it verbose during testing
|
||||
func init() {
|
||||
logger.Level = logrus.DebugLevel
|
||||
}
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
type Type struct {
|
||||
Int int `vic:"0.1" scope:"read-write" key:"int"`
|
||||
Bool bool `vic:"0.1" scope:"read-write" key:"bool"`
|
||||
Float float64 `vic:"0.1" scope:"read-write" key:"float"`
|
||||
String string `vic:"0.1" scope:"read-write" key:"string"`
|
||||
}
|
||||
|
||||
Struct := Type{
|
||||
42,
|
||||
true,
|
||||
3.14,
|
||||
"Grrr",
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Struct)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRW("int"): "42",
|
||||
visibleRW("bool"): "true",
|
||||
visibleRW("float"): "3.14E+00",
|
||||
visibleRW("string"): "Grrr",
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Struct, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestBasicMap(t *testing.T) {
|
||||
type Type struct {
|
||||
IntMap map[string]int `vic:"0.1" scope:"read-only" key:"intmap"`
|
||||
}
|
||||
|
||||
// key is not present
|
||||
var decoded Type
|
||||
Decode(MapSource(nil), &decoded)
|
||||
assert.NotNil(t, decoded.IntMap)
|
||||
assert.Empty(t, decoded.IntMap)
|
||||
|
||||
IntMap := Type{
|
||||
map[string]int{
|
||||
"1st": 12345,
|
||||
"2nd": 67890,
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), IntMap)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("intmap" + Separator + "1st"): "12345",
|
||||
visibleRO("intmap" + Separator + "2nd"): "67890",
|
||||
visibleRO("intmap"): "1st" + Separator + "2nd",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
// Decode to new variable
|
||||
decoded = Type{}
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, IntMap, decoded, "Encoded and decoded does not match")
|
||||
|
||||
// Decode to already existing variable
|
||||
IntMapOptimusPrime := Type{
|
||||
map[string]int{
|
||||
"first": 1,
|
||||
"second": 2,
|
||||
"1st": 0,
|
||||
},
|
||||
}
|
||||
Decode(MapSource(encoded), &IntMapOptimusPrime)
|
||||
|
||||
// We expect a merge and over-write
|
||||
expectedOptimusPrime := Type{
|
||||
map[string]int{
|
||||
"1st": 12345,
|
||||
"2nd": 67890,
|
||||
"first": 1,
|
||||
"second": 2,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, IntMapOptimusPrime, expectedOptimusPrime, "Decoded and expected does not match")
|
||||
|
||||
}
|
||||
|
||||
func TestBasicSlice(t *testing.T) {
|
||||
type Type struct {
|
||||
IntSlice []int `vic:"0.1" scope:"read-only" key:"intslice"`
|
||||
}
|
||||
|
||||
IntSlice := Type{
|
||||
[]int{1, 2, 3, 4, 5},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), IntSlice)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("intslice~"): "1" + Separator + "2" + Separator + "3" + Separator + "4" + Separator + "5",
|
||||
visibleRO("intslice"): "4",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
decoded.IntSlice = make([]int, 1)
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, IntSlice, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestStruct(t *testing.T) {
|
||||
type Type struct {
|
||||
Common Common `vic:"0.1" scope:"read-only" key:"common"`
|
||||
}
|
||||
|
||||
Struct := Type{
|
||||
Common: Common{
|
||||
ID: "0xDEADBEEF",
|
||||
Name: "Struct",
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Struct)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("common/id"): "0xDEADBEEF",
|
||||
visibleRO("common/name"): "Struct",
|
||||
visibleRO("common/notes"): "",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Struct, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestTime(t *testing.T) {
|
||||
type Type struct {
|
||||
Time time.Time `vic:"0.1" scope:"read-only" key:"time"`
|
||||
}
|
||||
|
||||
Time := Type{
|
||||
Time: time.Date(2009, 11, 10, 23, 00, 00, 0, time.UTC),
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Time)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("time"): "2009-11-10 23:00:00 +0000 UTC",
|
||||
}
|
||||
assert.Equal(t, encoded, expected, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Time, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestNet(t *testing.T) {
|
||||
type Type struct {
|
||||
Net net.IPNet `vic:"0.1" scope:"read-only" key:"net"`
|
||||
}
|
||||
|
||||
// 127.0.0.1/8
|
||||
n := net.IPNet{IP: net.IP{0x7f, 0x0, 0x0, 0x1}, Mask: net.IPMask{0xff, 0x0, 0x0, 0x0}}
|
||||
Net := Type{
|
||||
Net: n,
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Net)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("net/IP"): base64.StdEncoding.EncodeToString(n.IP),
|
||||
visibleRO("net/Mask"): base64.StdEncoding.EncodeToString(n.Mask),
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Net, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestNilNetPointer(t *testing.T) {
|
||||
type Type struct {
|
||||
Net *net.IPNet `vic:"0.1" scope:"read-only" key:"net"`
|
||||
}
|
||||
|
||||
Net := Type{
|
||||
Net: nil,
|
||||
}
|
||||
|
||||
// Net should be nil - pointers are supposed to be nil if the referenced tree is zero valued
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Net)
|
||||
|
||||
expected := map[string]string{}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Net, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestPointer(t *testing.T) {
|
||||
type Type struct {
|
||||
Pointer *ContainerVM `vic:"0.1" scope:"hidden" key:"pointer"`
|
||||
PointerOmitnested *ContainerVM `vic:"0.1" scope:"non-persistent" key:"pointeromitnested" recurse:"depth=0"`
|
||||
}
|
||||
|
||||
Pointer := Type{
|
||||
Pointer: &ContainerVM{Version: "0.1"},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Pointer)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("pointer/common/id"): "",
|
||||
visibleRO("pointer/common/name"): "",
|
||||
visibleRO("pointer/common/notes"): "",
|
||||
"pointer/version": "0.1",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Pointer, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestInheritenceOfNonPersistence(t *testing.T) {
|
||||
type CommonPersistence struct {
|
||||
ExecutionEnvironment string `vic:"0.1" recurse:"depth=0"`
|
||||
|
||||
ID string `vic:"0.1" scope:"read-only" key:"id"`
|
||||
|
||||
Name string `vic:"0.1" scope:"read-only" key:"name"`
|
||||
|
||||
Notes string `vic:"0.1" scope:"hidden" key:"notes"`
|
||||
}
|
||||
|
||||
type Type struct {
|
||||
Common CommonPersistence `vic:"0.1" scope:"read-write,non-persistent" key:"common"`
|
||||
}
|
||||
|
||||
Struct := Type{
|
||||
Common: CommonPersistence{
|
||||
ID: "0xDEADBEEF",
|
||||
Name: "Struct",
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
filterSink := ScopeFilterSink(NonPersistent|Hidden, MapSink(encoded))
|
||||
Encode(filterSink, Struct)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRONonpersistent("common/id"): "0xDEADBEEF",
|
||||
visibleRONonpersistent("common/name"): "Struct",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Struct, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestInheritenceOfNonPersistenceWithPointer(t *testing.T) {
|
||||
|
||||
type Persistence struct {
|
||||
ExecutorConfigPointersVisible `vic:"0.1" scope:"read-only,non-persistent" key:"pointers"`
|
||||
}
|
||||
|
||||
Struct := Persistence{
|
||||
ExecutorConfigPointersVisible: ExecutorConfigPointersVisible{
|
||||
Sessions: map[string]*VisibleSessionConfig{
|
||||
"primary": {
|
||||
Tty: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
filterSink := ScopeFilterSink(NonPersistent|Hidden, MapSink(encoded))
|
||||
Encode(filterSink, Struct)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRONonpersistent("pointers/sessions"): "primary",
|
||||
visibleRONonpersistent("pointers/sessions" + Separator + "primary/tty"): "true",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Persistence
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Struct, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestFilterSink(t *testing.T) {
|
||||
type CommonPersistence struct {
|
||||
ExecutionEnvironment string `vic:"0.1" recurse:"depth=0"`
|
||||
|
||||
ID string `vic:"0.1" scope:"read-only" key:"id"`
|
||||
|
||||
Name string `vic:"0.1" scope:"read-only,non-persistent" key:"name"`
|
||||
|
||||
Notes string `vic:"0.1" scope:"read-only" key:"notes"`
|
||||
}
|
||||
|
||||
type Type struct {
|
||||
Common CommonPersistence `vic:"0.1" scope:"read-write" key:"common"`
|
||||
}
|
||||
|
||||
Struct := Type{
|
||||
Common: CommonPersistence{
|
||||
ID: "0xDEADBEEF",
|
||||
Name: "Struct",
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
filterSink := ScopeFilterSink(NonPersistent|Hidden, MapSink(encoded))
|
||||
Encode(filterSink, Struct)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRONonpersistent("common/name"): "Struct",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
// strip ID as that would be filtered out
|
||||
Struct.Common.ID = ""
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Struct, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
537
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/clean_test.go
generated
vendored
Normal file
537
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/clean_test.go
generated
vendored
Normal file
@@ -0,0 +1,537 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// make it verbose during testing
|
||||
func init() {
|
||||
logger.Level = logrus.DebugLevel
|
||||
}
|
||||
|
||||
func TestEmbedded(t *testing.T) {
|
||||
|
||||
type Type struct {
|
||||
Common `vic:"0.1" scope:"read-only" key:"common"`
|
||||
}
|
||||
|
||||
Embedded := Type{
|
||||
Common: Common{
|
||||
ID: "0xDEADBEEF",
|
||||
Name: "Embedded",
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Embedded)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("common/id"): "0xDEADBEEF",
|
||||
visibleRO("common/name"): "Embedded",
|
||||
visibleRO("common/notes"): "",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Embedded, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestNetPointer(t *testing.T) {
|
||||
type Type struct {
|
||||
Net *net.IPNet `vic:"0.1" scope:"read-only" key:"net"`
|
||||
}
|
||||
|
||||
// 127.0.0.1/8
|
||||
n := net.IPNet{IP: net.IP{0x7f, 0x0, 0x0, 0x1}, Mask: net.IPMask{0xff, 0x0, 0x0, 0x0}}
|
||||
Net := Type{
|
||||
Net: &n,
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Net)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("net/IP"): base64.StdEncoding.EncodeToString(n.IP),
|
||||
visibleRO("net/Mask"): base64.StdEncoding.EncodeToString(n.Mask),
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Net, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestTimePointer(t *testing.T) {
|
||||
d := time.Date(2009, 11, 10, 23, 00, 00, 0, time.UTC)
|
||||
|
||||
type Type struct {
|
||||
Time *time.Time `vic:"0.1" scope:"read-only" key:"time"`
|
||||
}
|
||||
|
||||
Time := Type{
|
||||
Time: &d,
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Time)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("time"): "2009-11-10 23:00:00 +0000 UTC",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Time, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestStructMap(t *testing.T) {
|
||||
type Type struct {
|
||||
StructMap map[string]Common `vic:"0.1" scope:"read-only" key:"map"`
|
||||
}
|
||||
|
||||
StructMap := Type{
|
||||
map[string]Common{
|
||||
"Key1": {
|
||||
ID: "0xDEADBEEF",
|
||||
Name: "beef",
|
||||
},
|
||||
"Key2": {
|
||||
ID: "0x8BADF00D",
|
||||
Name: "food",
|
||||
},
|
||||
"Key3": {
|
||||
ID: "0xDEADF00D",
|
||||
Name: "dead",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), StructMap)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("map" + Separator + "Key1/id"): "0xDEADBEEF",
|
||||
visibleRO("map" + Separator + "Key1/name"): "beef",
|
||||
visibleRO("map" + Separator + "Key1/notes"): "",
|
||||
visibleRO("map" + Separator + "Key2/id"): "0x8BADF00D",
|
||||
visibleRO("map" + Separator + "Key2/name"): "food",
|
||||
visibleRO("map" + Separator + "Key2/notes"): "",
|
||||
visibleRO("map" + Separator + "Key3/id"): "0xDEADF00D",
|
||||
visibleRO("map" + Separator + "Key3/name"): "dead",
|
||||
visibleRO("map" + Separator + "Key3/notes"): "",
|
||||
visibleRO("map"): "Key1" + Separator + "Key2" + Separator + "Key3",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, StructMap, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestIntStructMap(t *testing.T) {
|
||||
type Type struct {
|
||||
StructMap map[int]Common `vic:"0.1" scope:"read-only" key:"map"`
|
||||
}
|
||||
|
||||
StructMap := Type{
|
||||
map[int]Common{
|
||||
1: {
|
||||
ID: "0xDEADBEEF",
|
||||
Name: "beef",
|
||||
},
|
||||
2: {
|
||||
ID: "0x8BADF00D",
|
||||
Name: "food",
|
||||
},
|
||||
3: {
|
||||
ID: "0xDEADF00D",
|
||||
Name: "dead",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), StructMap)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("map" + Separator + "1/id"): "0xDEADBEEF",
|
||||
visibleRO("map" + Separator + "1/name"): "beef",
|
||||
visibleRO("map" + Separator + "1/notes"): "",
|
||||
visibleRO("map" + Separator + "2/id"): "0x8BADF00D",
|
||||
visibleRO("map" + Separator + "2/name"): "food",
|
||||
visibleRO("map" + Separator + "2/notes"): "",
|
||||
visibleRO("map" + Separator + "3/id"): "0xDEADF00D",
|
||||
visibleRO("map" + Separator + "3/name"): "dead",
|
||||
visibleRO("map" + Separator + "3/notes"): "",
|
||||
visibleRO("map"): "1" + Separator + "2" + Separator + "3",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, StructMap, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestStructSlice(t *testing.T) {
|
||||
type Type struct {
|
||||
StructSlice []Common `vic:"0.1" scope:"read-only" key:"slice"`
|
||||
}
|
||||
|
||||
StructSlice := Type{
|
||||
[]Common{
|
||||
{
|
||||
ID: "0xDEADFEED",
|
||||
Name: "feed",
|
||||
},
|
||||
{
|
||||
ID: "0xFACEFEED",
|
||||
Name: "face",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), StructSlice)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("slice"): "1",
|
||||
visibleRO("slice" + Separator + "0/id"): "0xDEADFEED",
|
||||
visibleRO("slice" + Separator + "0/name"): "feed",
|
||||
visibleRO("slice" + Separator + "0/notes"): "",
|
||||
visibleRO("slice" + Separator + "1/id"): "0xFACEFEED",
|
||||
visibleRO("slice" + Separator + "1/name"): "face",
|
||||
visibleRO("slice" + Separator + "1/notes"): "",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, StructSlice, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestMultipleScope(t *testing.T) {
|
||||
MultipleScope := struct {
|
||||
MultipleScope string `vic:"0.1" scope:"read-only,hidden,non-persistent" key:"multiscope"`
|
||||
}{
|
||||
"MultipleScope",
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), MultipleScope)
|
||||
|
||||
expected := map[string]string{}
|
||||
assert.Equal(t, expected, encoded, "Not equal")
|
||||
}
|
||||
|
||||
func TestUnknownScope(t *testing.T) {
|
||||
UnknownScope := struct {
|
||||
UnknownScope int `vic:"0.1" scope:"unknownscope" key:"unknownscope"`
|
||||
}{
|
||||
42,
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), UnknownScope)
|
||||
|
||||
expected := map[string]string{}
|
||||
assert.Equal(t, encoded, expected, "Not equal")
|
||||
}
|
||||
|
||||
func TestUnknownProperty(t *testing.T) {
|
||||
UnknownProperty := struct {
|
||||
UnknownProperty int `vic:"0.1" scope:"hidden" key:"unknownproperty" recurse:"unknownproperty"`
|
||||
}{
|
||||
42,
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), UnknownProperty)
|
||||
|
||||
expected := map[string]string{
|
||||
hidden("unknownproperty"): "42",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Not equal")
|
||||
}
|
||||
|
||||
func TestOmitNested(t *testing.T) {
|
||||
OmitNested := struct {
|
||||
Time time.Time `vic:"0.1" scope:"volatile" key:"time" recurse:"depth=0"`
|
||||
CurrentTime time.Time `vic:"0.1" scope:"volatile" key:"time"`
|
||||
}{
|
||||
Time: time.Date(2009, 11, 10, 23, 00, 00, 0, time.UTC),
|
||||
CurrentTime: time.Date(2009, 11, 10, 23, 00, 00, 0, time.UTC),
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), OmitNested)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("time"): "2009-11-10 23:00:00 +0000 UTC",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and decoded does not match")
|
||||
|
||||
}
|
||||
|
||||
func TestComplex(t *testing.T) {
|
||||
type Type struct {
|
||||
ExecutorConfig ExecutorConfig `vic:"0.1" scope:"hidden" key:"executorconfig"`
|
||||
}
|
||||
|
||||
ExecutorConfig := Type{
|
||||
ExecutorConfig{
|
||||
Sessions: map[string]SessionConfig{
|
||||
"Session1": {
|
||||
Common: Common{
|
||||
ID: "SessionID",
|
||||
Name: "SessionName",
|
||||
},
|
||||
Tty: true,
|
||||
Cmd: Cmd{
|
||||
Path: "/vmware",
|
||||
Args: []string{"/bin/imagec", "-standalone"},
|
||||
Env: []string{"PATH=/bin", "USER=imagec"},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), ExecutorConfig)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("executorconfig/common/id"): "",
|
||||
visibleRO("executorconfig/common/name"): "",
|
||||
visibleRO("executorconfig/common/notes"): "",
|
||||
visibleRO("executorconfig/sessions" + Separator + "Session1/common/id"): "SessionID",
|
||||
visibleRO("executorconfig/sessions" + Separator + "Session1/common/name"): "SessionName",
|
||||
visibleRO("executorconfig/sessions" + Separator + "Session1/common/notes"): "",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/path"): "/vmware",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/args~"): "/bin/imagec" + Separator + "-standalone",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/args"): "1",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/env~"): "PATH=/bin" + Separator + "USER=imagec",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/env"): "1",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/dir"): "/",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/tty"): "true",
|
||||
hidden("executorconfig/sessions"): "Session1",
|
||||
hidden("executorconfig/Key"): "",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, ExecutorConfig, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestComplexPointer(t *testing.T) {
|
||||
type Type struct {
|
||||
ExecutorConfig *ExecutorConfig `vic:"0.1" scope:"hidden" key:"executorconfig"`
|
||||
}
|
||||
|
||||
ExecutorConfig := Type{
|
||||
&ExecutorConfig{
|
||||
Sessions: map[string]SessionConfig{
|
||||
"Session1": {
|
||||
Common: Common{
|
||||
ID: "SessionID",
|
||||
Name: "SessionName",
|
||||
},
|
||||
Tty: true,
|
||||
Cmd: Cmd{
|
||||
Path: "/vmware",
|
||||
Args: []string{"/bin/imagec", "-standalone"},
|
||||
Env: []string{"PATH=/bin", "USER=imagec"},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), ExecutorConfig)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("executorconfig/common/id"): "",
|
||||
visibleRO("executorconfig/common/name"): "",
|
||||
visibleRO("executorconfig/common/notes"): "",
|
||||
visibleRO("executorconfig/sessions" + Separator + "Session1/common/id"): "SessionID",
|
||||
visibleRO("executorconfig/sessions" + Separator + "Session1/common/name"): "SessionName",
|
||||
visibleRO("executorconfig/sessions" + Separator + "Session1/common/notes"): "",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/path"): "/vmware",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/args~"): "/bin/imagec" + Separator + "-standalone",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/args"): "1",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/env~"): "PATH=/bin" + Separator + "USER=imagec",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/env"): "1",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/dir"): "/",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/tty"): "true",
|
||||
hidden("executorconfig/sessions"): "Session1",
|
||||
hidden("executorconfig/Key"): "",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, ExecutorConfig, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
// TestPointerDecode tests the translation from a type where the sessions are direct values to
|
||||
// one where they are pointers
|
||||
func TestPointerDecode(t *testing.T) {
|
||||
reference := ExecutorConfig{
|
||||
Sessions: map[string]SessionConfig{
|
||||
"Session1": {
|
||||
Common: Common{
|
||||
ID: "SessionID",
|
||||
Name: "SessionName",
|
||||
},
|
||||
Tty: true,
|
||||
Cmd: Cmd{
|
||||
Path: "/vmware",
|
||||
Args: []string{"/bin/imagec", "-standalone"},
|
||||
Env: []string{"PATH=/bin", "USER=imagec"},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), reference)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("common/id"): "",
|
||||
visibleRO("common/name"): "",
|
||||
visibleRO("common/notes"): "",
|
||||
visibleRO("sessions" + Separator + "Session1/common/id"): "SessionID",
|
||||
visibleRO("sessions" + Separator + "Session1/common/name"): "SessionName",
|
||||
visibleRO("sessions" + Separator + "Session1/common/notes"): "",
|
||||
hidden("sessions" + Separator + "Session1/cmd/path"): "/vmware",
|
||||
hidden("sessions" + Separator + "Session1/cmd/args~"): "/bin/imagec" + Separator + "-standalone",
|
||||
hidden("sessions" + Separator + "Session1/cmd/args"): "1",
|
||||
hidden("sessions" + Separator + "Session1/cmd/env~"): "PATH=/bin" + Separator + "USER=imagec",
|
||||
hidden("sessions" + Separator + "Session1/cmd/env"): "1",
|
||||
hidden("sessions" + Separator + "Session1/cmd/dir"): "/",
|
||||
hidden("sessions" + Separator + "Session1/tty"): "true",
|
||||
hidden("sessions"): "Session1",
|
||||
hidden("Key"): "",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded ExecutorConfigPointers
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
// cannot assert equality at a high level because of the different structure types, but we can test the
|
||||
// common structure fragments
|
||||
assert.Equal(t, reference.Sessions["Session1"], *decoded.Sessions["Session1"], "Encoded and decoded sessions do not match")
|
||||
|
||||
}
|
||||
|
||||
func TestInsideOutside(t *testing.T) {
|
||||
type Inside struct {
|
||||
ID string `vic:"0.1" scope:"read-write" key:"id"`
|
||||
Name string `vic:"0.1" scope:"read-write" key:"name"`
|
||||
}
|
||||
|
||||
type Outside struct {
|
||||
Inside Inside `vic:"0.1" scope:"read-only" key:"inside"`
|
||||
ID string `vic:"0.1" scope:"read-write" key:"id"`
|
||||
Name string `vic:"0.1" scope:"read-write" key:"name"`
|
||||
}
|
||||
outside := Outside{
|
||||
Inside: Inside{
|
||||
ID: "inside",
|
||||
Name: "Inside",
|
||||
},
|
||||
ID: "outside",
|
||||
Name: "Outside",
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), outside)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRW("inside.id"): "inside",
|
||||
visibleRW("inside.name"): "Inside",
|
||||
visibleRW("id"): "outside",
|
||||
visibleRW("name"): "Outside",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Outside
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, outside, decoded, "Encoded and decoded does not match")
|
||||
|
||||
}
|
||||
|
||||
func TestIPSlice(t *testing.T) {
|
||||
type Slice struct {
|
||||
Slice []net.IP `vic:"0.1" scope:"read-only" key:"slice"`
|
||||
}
|
||||
|
||||
ips := []net.IP{
|
||||
net.ParseIP("10.10.10.10"),
|
||||
net.ParseIP("10.10.10.1"),
|
||||
}
|
||||
|
||||
encodedIPs := make([]string, len(ips))
|
||||
for i := range ips {
|
||||
Encode(func(key, value string) error {
|
||||
encodedIPs[i] = value
|
||||
return nil
|
||||
}, ips[i])
|
||||
}
|
||||
|
||||
s := Slice{
|
||||
Slice: ips,
|
||||
}
|
||||
|
||||
encoded := make(map[string]string)
|
||||
Encode(MapSink(encoded), s)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("slice"): fmt.Sprintf("%d", len(ips)-1),
|
||||
}
|
||||
for i := range encodedIPs {
|
||||
expected[visibleRO(fmt.Sprintf("slice"+Separator+"%d", i))] = encodedIPs[i]
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected do not match")
|
||||
|
||||
var decoded Slice
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
assert.Equal(t, s, decoded, "Encoded and decoded do not match")
|
||||
}
|
||||
477
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/decode.go
generated
vendored
Normal file
477
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/decode.go
generated
vendored
Normal file
@@ -0,0 +1,477 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
nilValue = reflect.ValueOf(nil)
|
||||
)
|
||||
|
||||
type decoder func(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error)
|
||||
|
||||
var (
|
||||
kindDecoders map[reflect.Kind]decoder
|
||||
intfDecoders map[reflect.Type]decoder
|
||||
)
|
||||
|
||||
func init() {
|
||||
kindDecoders = map[reflect.Kind]decoder{
|
||||
reflect.String: decodeString,
|
||||
reflect.Struct: decodeStruct,
|
||||
reflect.Slice: decodeSlice,
|
||||
reflect.Array: decodeSlice,
|
||||
reflect.Map: decodeMap,
|
||||
reflect.Ptr: decodePtr,
|
||||
reflect.Int: decodePrimitive,
|
||||
reflect.Int8: decodePrimitive,
|
||||
reflect.Int16: decodePrimitive,
|
||||
reflect.Int32: decodePrimitive,
|
||||
reflect.Int64: decodePrimitive,
|
||||
reflect.Bool: decodePrimitive,
|
||||
reflect.Float32: decodePrimitive,
|
||||
reflect.Float64: decodePrimitive,
|
||||
}
|
||||
|
||||
intfDecoders = map[reflect.Type]decoder{
|
||||
reflect.TypeOf(time.Time{}): decodeTime,
|
||||
}
|
||||
}
|
||||
|
||||
// decode is the generic switcher that decides which decoder to use for a field
|
||||
func decode(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
||||
// if depth has reached zero, we skip decoding entirely
|
||||
if depth.depth == 0 {
|
||||
return dest, nil
|
||||
}
|
||||
depth.depth--
|
||||
|
||||
// obtain the handler from the map, checking for the more specific interfaces first
|
||||
dec, ok := intfDecoders[dest.Type()]
|
||||
if ok {
|
||||
return dec(src, dest, prefix, depth)
|
||||
}
|
||||
|
||||
dec, ok = kindDecoders[dest.Kind()]
|
||||
if ok {
|
||||
return dec(src, dest, prefix, depth)
|
||||
}
|
||||
|
||||
logger.Debugf("Skipping unsupported field, interface: %T, kind %s", dest, dest.Kind())
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
// decodeString is the degenerative case where what we get is what we need
|
||||
func decodeString(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
||||
v, err := src(prefix)
|
||||
if err != nil {
|
||||
logger.Debugf("No value found in data source for string at key %q", prefix)
|
||||
return nilValue, err
|
||||
}
|
||||
|
||||
return reflect.ValueOf(v), nil
|
||||
}
|
||||
|
||||
// decodePrimitive wraps the fromString primitive decoding in a manner that can be called via decode
|
||||
func decodePrimitive(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
||||
var this reflect.Value
|
||||
if !dest.CanAddr() {
|
||||
logger.Debugf("Making new primitive for %s", prefix)
|
||||
ptr := reflect.New(dest.Type())
|
||||
this = ptr.Elem()
|
||||
} else {
|
||||
logger.Debugf("Reusing existing struct for %s", prefix)
|
||||
this = dest
|
||||
}
|
||||
|
||||
// see if there's a value to decode
|
||||
v, err := src(prefix)
|
||||
if err != nil {
|
||||
logger.Debugf("No value available for key to primitive %s", prefix)
|
||||
return nilValue, err
|
||||
}
|
||||
|
||||
t := this.Type()
|
||||
this.Set(fromString(reflect.Zero(t), v))
|
||||
|
||||
return this, nil
|
||||
}
|
||||
|
||||
func decodePtr(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
||||
// if we're not following pointers, then return immediately
|
||||
if !depth.follow {
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
// value representing the run-time data
|
||||
logger.Debugf("Decoding pointer into object: %#v", dest)
|
||||
|
||||
// if the pointer is nil we need to create the destination type
|
||||
target := dest
|
||||
if dest.IsNil() {
|
||||
target = reflect.New(dest.Type().Elem())
|
||||
}
|
||||
|
||||
// check to see if the resulting object is not nil
|
||||
// If it is nil, then there was nothing to decode and the pointer remains nil
|
||||
result, err := decode(src, target.Elem(), prefix, depth)
|
||||
logger.Debugf("target is now %#v, %+q ", target, target.Type())
|
||||
if !result.IsValid() || err == ErrKeyNotFound {
|
||||
// leave the pointer as nil if the result is zero type or invalid
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
// neither pointer, nor zero
|
||||
// NOTE: if the returned result is not addressable this can panic - that generally
|
||||
// indicates an incorrect implementation of a decodeX method... those should always
|
||||
// return addressable Values. See decodeByteSlice as an example - this uses make([]byte)
|
||||
// rather than built in string(bytes) conversion specifically to get an addressable return
|
||||
if dest.IsNil() {
|
||||
dest = target
|
||||
}
|
||||
|
||||
dest.Elem().Set(result)
|
||||
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
var typeType = reflect.TypeOf((*reflect.Type)(nil)).Elem()
|
||||
|
||||
func decodeStruct(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
||||
// value representing the run-time data
|
||||
logger.Debugf("Decoding struct into object: %#v, type: %s", dest, dest.Type().Name())
|
||||
|
||||
var this reflect.Value
|
||||
if !dest.CanAddr() {
|
||||
logger.Debugf("Making new struct for %s", prefix)
|
||||
ptr := reflect.New(dest.Type())
|
||||
this = ptr.Elem()
|
||||
} else {
|
||||
logger.Debugf("Reusing existing struct for %s", prefix)
|
||||
this = dest
|
||||
}
|
||||
|
||||
// do we have any data for this struct at all
|
||||
var valid bool
|
||||
var err error
|
||||
noKeysFound := true
|
||||
|
||||
// iterate through every field in the struct
|
||||
for i := 0; i < this.NumField(); i++ {
|
||||
field := this.Field(i)
|
||||
key, fdepth := calculateKeyFromField(this.Type().Field(i), prefix, depth)
|
||||
if key == "" {
|
||||
// this is either a malformed key or explicitly skipped
|
||||
continue
|
||||
}
|
||||
|
||||
// Dump what we have so far
|
||||
logger.Debugf("Key: %s, Kind: %s Value: %s", key, field.Kind(), field.String())
|
||||
|
||||
// check to see if the resulting object is not nil
|
||||
// If it is nil, then there was nothing to decode
|
||||
var result reflect.Value
|
||||
result, err = decode(src, field, key, fdepth)
|
||||
if result.IsValid() {
|
||||
logger.Debugf("Setting field %s to %#v", this.Type().Field(i).Name, result)
|
||||
field.Set(result)
|
||||
valid = true
|
||||
if err != ErrKeyNotFound {
|
||||
noKeysFound = false
|
||||
}
|
||||
} else {
|
||||
logger.Debugf("Invalid result for field %s", this.Type().Field(i).Name)
|
||||
}
|
||||
}
|
||||
|
||||
if !valid || noKeysFound {
|
||||
logger.Debugf("No valid result, returning nil value")
|
||||
return nilValue, err
|
||||
}
|
||||
|
||||
logger.Debugf("Return decoded structure for %s: %#v", prefix, this)
|
||||
return this, nil
|
||||
}
|
||||
|
||||
func decodeByteSlice(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
||||
logger.Debugf("Converting string to []byte")
|
||||
base, err := src(prefix)
|
||||
if err != nil {
|
||||
logger.Debugf("No value found in data source for []byte %q", prefix)
|
||||
return nilValue, err
|
||||
}
|
||||
|
||||
bytes, err := base64.StdEncoding.DecodeString(base)
|
||||
if err != nil {
|
||||
logger.Debugf("Expected base64 encoded string for []byte %q: %s", prefix, err)
|
||||
return nilValue, err
|
||||
}
|
||||
|
||||
length := len(bytes)
|
||||
|
||||
// we don't even try to merge byte arrays - no idea how to get append behaviour
|
||||
// correct with reflection
|
||||
// use make([]byte) rather than built in string(bytes) conversion to get an addressable return value
|
||||
logger.Debugf("Making new slice for %s", prefix)
|
||||
this := make([]byte, length, length)
|
||||
|
||||
copy(this, bytes)
|
||||
|
||||
return reflect.ValueOf(this), nil
|
||||
}
|
||||
|
||||
func decodeSlice(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
||||
// value representing the run-time data
|
||||
logger.Debugf("Decoding struct into object: %#v", dest)
|
||||
kind := dest.Type().Elem().Kind()
|
||||
|
||||
if kind == reflect.Uint8 {
|
||||
return decodeByteSlice(src, dest, prefix, depth)
|
||||
}
|
||||
|
||||
// do we have any data for this struct at all
|
||||
length := 0
|
||||
curLen := 0
|
||||
|
||||
// get the length of the array
|
||||
len, err := src(prefix)
|
||||
if err != nil || len == "" {
|
||||
logger.Debugf("No value available for key %s - will create empty array if needed", prefix)
|
||||
} else {
|
||||
// if there's any data at all then we can assume we need to be extant
|
||||
lengthValue := fromString(reflect.ValueOf(0), len)
|
||||
length = int(lengthValue.Int()) + 1
|
||||
}
|
||||
|
||||
var this reflect.Value
|
||||
if !dest.IsValid() || dest.IsNil() || length > dest.Cap() {
|
||||
logger.Debugf("Making new slice for %s", prefix)
|
||||
this = reflect.MakeSlice(dest.Type(), length, length)
|
||||
} else {
|
||||
this = dest
|
||||
this.SetLen(length)
|
||||
curLen = this.Len()
|
||||
}
|
||||
|
||||
// determine the key given the array type
|
||||
if kind == reflect.Struct || isEncodableSliceElemType(dest.Type().Elem()) {
|
||||
for i := 0; i < length; i++ {
|
||||
// convert key to name|index format
|
||||
key := appendToPrefix(prefix, Separator, fmt.Sprintf("%d", i))
|
||||
|
||||
// if there's already a struct in the array at this index then we pass that as the current
|
||||
// value
|
||||
var cur reflect.Value
|
||||
if i < curLen {
|
||||
cur = this.Index(i)
|
||||
} else {
|
||||
cur = reflect.Zero(dest.Type().Elem())
|
||||
}
|
||||
|
||||
var result reflect.Value
|
||||
// #nosec: Errors unhandled.
|
||||
result, _ = decode(src, cur, key, depth)
|
||||
if result.IsValid() {
|
||||
this.Index(i).Set(result)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
return this, nil
|
||||
}
|
||||
|
||||
// convert key to name|index format
|
||||
key := appendToPrefix(prefix, "", "~")
|
||||
kval, err := src(key)
|
||||
if err != nil {
|
||||
logger.Debugf("No value found in data source for key %q", key)
|
||||
return this, err
|
||||
}
|
||||
|
||||
// lookup the key and split it
|
||||
values := strings.Split(kval, Separator)
|
||||
for i := 0; i < length; i++ {
|
||||
v := values[i]
|
||||
t := this.Type().Elem()
|
||||
k := fromString(reflect.Zero(t), v)
|
||||
// set the i'th slice item
|
||||
this.Index(i).Set(k)
|
||||
}
|
||||
|
||||
return this, nil
|
||||
}
|
||||
|
||||
func decodeMap(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
||||
// value representing the run-time data
|
||||
logger.Debugf("Decoding struct into object: %#v", dest)
|
||||
|
||||
// if the value is the zero type, we have to create ourselves
|
||||
var this reflect.Value
|
||||
if !dest.IsValid() || dest.IsNil() {
|
||||
logger.Debugf("Making new maps for %s", prefix)
|
||||
this = reflect.MakeMap(dest.Type())
|
||||
} else {
|
||||
this = dest
|
||||
}
|
||||
|
||||
mapkeys, err := src(prefix)
|
||||
if mapkeys == "" || err != nil {
|
||||
logger.Debugf("No value found in data source for maps keys %q", prefix)
|
||||
return this, err
|
||||
}
|
||||
|
||||
keytype := this.Type().Key()
|
||||
valtype := this.Type().Elem()
|
||||
|
||||
// split the list of map keys and iterate
|
||||
for _, value := range strings.Split(mapkeys, Separator) {
|
||||
k := fromString(reflect.Zero(keytype), value)
|
||||
target := this.MapIndex(k)
|
||||
if !target.IsValid() {
|
||||
target = reflect.Zero(valtype)
|
||||
}
|
||||
|
||||
key := appendToPrefix(prefix, Separator, value)
|
||||
|
||||
// check to see if the resulting object is not nil
|
||||
// If it is nil, then there was nothing to decode and the pointer remains nil
|
||||
// #nosec: Errors unhandled.
|
||||
result, _ := decode(src, target, key, depth)
|
||||
if result.IsValid() {
|
||||
this.SetMapIndex(k, result)
|
||||
}
|
||||
}
|
||||
|
||||
return this, nil
|
||||
}
|
||||
|
||||
func decodeTime(src DataSource, dest reflect.Value, prefix string, depth recursion) (reflect.Value, error) {
|
||||
v, err := src(prefix)
|
||||
if err != nil {
|
||||
logger.Debugf("No value found in data source for time %q", prefix)
|
||||
return nilValue, err
|
||||
}
|
||||
|
||||
t, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", v)
|
||||
if err != nil {
|
||||
logger.Debugf("Failed to convert value %q to time", v)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(t), nil
|
||||
}
|
||||
|
||||
// fromString converts string representation of a basic type to basic type
|
||||
func fromString(field reflect.Value, value string) reflect.Value {
|
||||
// handle the zero value
|
||||
// TODO: can probably handle this more efficiently with a nil pointer return
|
||||
// as whatever we're populating with primitives will already have their zero
|
||||
// value.
|
||||
if value == "" {
|
||||
return reflect.Zero(field.Type())
|
||||
}
|
||||
|
||||
switch field.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
s, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to convert value %#v (%s) to int: %s", value, field.Kind(), err.Error())
|
||||
return field
|
||||
}
|
||||
return reflect.ValueOf(s).Convert(field.Type())
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
s, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to convert value %#v (%s) to uint: %s", value, field.Kind(), err.Error())
|
||||
return field
|
||||
}
|
||||
return reflect.ValueOf(s).Convert(field.Type())
|
||||
|
||||
case reflect.Bool:
|
||||
s, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to convert value %#v (%s) to bool: %s", value, field.Kind(), err.Error())
|
||||
return field
|
||||
}
|
||||
return reflect.ValueOf(s)
|
||||
|
||||
case reflect.String:
|
||||
return reflect.ValueOf(value)
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
s, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to convert value %#v (%s) to float: %s", value, field.Kind(), err.Error())
|
||||
return field
|
||||
}
|
||||
return reflect.ValueOf(s)
|
||||
|
||||
}
|
||||
logger.Debugf("Invalid Kind: %s (%#v)", field.Kind(), value)
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
// DataSource provides a function that, give a key will return a value
|
||||
// this is to be used during extraConfig decode to obtain values. Should
|
||||
// return ErrKeyNotFound if the key does not exist in the data source.
|
||||
type DataSource func(string) (string, error)
|
||||
|
||||
// Decode populates a destination with data from the supplied data source
|
||||
func Decode(src DataSource, dest interface{}) interface{} {
|
||||
if src == nil {
|
||||
logger.Warnf("Decode source is nil - unable to continue")
|
||||
return dest
|
||||
}
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
value, _ := decode(src, reflect.ValueOf(dest), DefaultPrefix, Unbounded)
|
||||
|
||||
return value.Interface()
|
||||
}
|
||||
|
||||
// DecodeWithPrefix populates a destination with data from the supplied data source, using
|
||||
// the specified prefix - this allows for decode into substructres.
|
||||
func DecodeWithPrefix(src DataSource, dest interface{}, prefix string) interface{} {
|
||||
if src == nil {
|
||||
logger.Warnf("Decode source is nil - unable to continue")
|
||||
return dest
|
||||
}
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
value, _ := decode(src, reflect.ValueOf(dest), prefix, Unbounded)
|
||||
|
||||
return value.Interface()
|
||||
}
|
||||
|
||||
// MapSource takes a key/value map and uses that as the datasource for decoding into
|
||||
// target structures
|
||||
func MapSource(src map[string]string) DataSource {
|
||||
return func(key string) (string, error) {
|
||||
val, ok := src[key]
|
||||
if !ok {
|
||||
return "", ErrKeyNotFound
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
}
|
||||
30
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/decode_darwin.go
generated
vendored
Normal file
30
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/decode_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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 "errors"
|
||||
|
||||
// GuestInfoSource uses the rpcvmx mechanism to access the guestinfo key/value map as
|
||||
// the datasource for decoding into target structures
|
||||
func GuestInfoSource() (DataSource, error) {
|
||||
return GuestInfoSourceWithPrefix("")
|
||||
}
|
||||
|
||||
// GuestInfoSourceWithPrefix adds a prefix to all keys accessed. The key must not have leading
|
||||
// or trailing separator characters, but may have separators in other positions. The separator
|
||||
// (either . or /) will be replaced with the appropriate value for the key in question.
|
||||
func GuestInfoSourceWithPrefix(prefix string) (DataSource, error) {
|
||||
return nil, errors.New("Not implemented on OSX")
|
||||
}
|
||||
63
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/decode_linux.go
generated
vendored
Normal file
63
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/decode_linux.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vmw-guestinfo/rpcvmx"
|
||||
"github.com/vmware/vmw-guestinfo/vmcheck"
|
||||
)
|
||||
|
||||
// GuestInfoSource uses the rpcvmx mechanism to access the guestinfo key/value map as
|
||||
// the datasource for decoding into target structures
|
||||
func GuestInfoSource() (DataSource, error) {
|
||||
return GuestInfoSourceWithPrefix("")
|
||||
}
|
||||
|
||||
// GuestInfoSourceWithPrefix adds a prefix to all keys accessed. The key must not have leading
|
||||
// or trailing separator characters, but may have separators in other positions. The separator
|
||||
// (either . or /) will be replaced with the appropriate value for the key in question.
|
||||
func GuestInfoSourceWithPrefix(prefix string) (DataSource, error) {
|
||||
// Check we're using a vcpu (which doesn't assume this is UID 0).
|
||||
if !vmcheck.IsVirtualCPU() {
|
||||
return nil, fmt.Errorf("not in a virtual world")
|
||||
}
|
||||
|
||||
guestinfo := rpcvmx.NewConfig()
|
||||
|
||||
source := func(key string) (string, error) {
|
||||
if key != GuestInfoSecretKey {
|
||||
key = addPrefixToKey(DefaultGuestInfoPrefix, prefix, key)
|
||||
}
|
||||
|
||||
value, err := guestinfo.String(key, "")
|
||||
if value == "" {
|
||||
err = ErrKeyNotFound
|
||||
} else if value == "<nil>" {
|
||||
value = ""
|
||||
}
|
||||
|
||||
if key != GuestInfoSecretKey { // don't log the secret key
|
||||
log.Debugf("GuestInfoSource: key: %s, value: %#v, error: %s", key, value, err)
|
||||
}
|
||||
|
||||
return value, err
|
||||
}
|
||||
|
||||
return new(SecretKey).Source(source), nil
|
||||
}
|
||||
32
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/decode_windows.go
generated
vendored
Normal file
32
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/decode_windows.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// GuestInfoSource uses the rpcvmx mechanism to access the guestinfo key/value map as
|
||||
// the datasource for decoding into target structures
|
||||
func GuestInfoSource() (DataSource, error) {
|
||||
return GuestInfoSourceWithPrefix("")
|
||||
}
|
||||
|
||||
// GuestInfoSourceWithPrefix adds a prefix to all keys accessed. The key must not have leading
|
||||
// or trailing separator characters, but may have separators in other positions. The separator
|
||||
// (either . or /) will be replaced with the appropriate value for the key in question.
|
||||
func GuestInfoSourceWithPrefix(prefix string) (DataSource, error) {
|
||||
return nil, errors.New("Not implemented on Windows")
|
||||
}
|
||||
47
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/doc.go
generated
vendored
Normal file
47
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/doc.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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
|
||||
|
||||
/*
|
||||
Package extraconfig provides Encode/Decode methods to convert data between Go structs and VMware Extraconfig values.
|
||||
|
||||
The implementation understands the following set of annotations and map the fields to appropriate extraConfig keys - in the case where the key describes a boolean state, omitting the annotation implies the opposite:
|
||||
|
||||
hidden - hidden from GuestOS
|
||||
read-only - value can only be modified via vSphere APIs
|
||||
read-write - value can be modified
|
||||
non-persistent - value will be lost on VM reboot
|
||||
volatile - field is not exported directly, but via a function that freshens the value each time)
|
||||
|
||||
The struct fields are required to be annotated with the "vic" tag, otherwise extraconfig package simply skips them. Scope and key tags are also required.
|
||||
|
||||
Scope tag can contain multiple values (comma separated)
|
||||
Key tag can contain extra properties (comma separated) but the first element has to the name of the key.
|
||||
|
||||
type Example struct {
|
||||
// skipped - does not contain any tag
|
||||
Note string
|
||||
|
||||
// skipped - does not contain scope and key
|
||||
ID string `vic:"0.1"`
|
||||
|
||||
// valid - extraconfig will encode this using a read-only key (as instructed by scope)
|
||||
Name string `vic:"0.1" scope:"read-only" key:"name"`
|
||||
|
||||
// valid - but extraconfig won't nest into the struct (so it's value will be type's zero value)
|
||||
Time time.Time `vic:"0.1" scope:"volatile" key:"time,omitnested"`
|
||||
}
|
||||
|
||||
*/
|
||||
300
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/encode.go
generated
vendored
Normal file
300
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/encode.go
generated
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
// 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 {
|
||||
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), DefaultPrefix, 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
|
||||
}
|
||||
}
|
||||
30
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/encode_darwin.go
generated
vendored
Normal file
30
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/encode_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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 "errors"
|
||||
|
||||
// GuestInfoSink uses the rpcvmx mechanism to update the guestinfo key/value map as
|
||||
// the datasink for encoding target structures
|
||||
func GuestInfoSink() (DataSink, error) {
|
||||
return GuestInfoSinkWithPrefix("")
|
||||
}
|
||||
|
||||
// GuestInfoSinkWithPrefix adds a prefix to all keys accessed. The key must not have leading
|
||||
// or trailing separator characters, but may have separators in other positions. The separator
|
||||
// (either . or /) will be replaced with the appropriate value for the key in question.
|
||||
func GuestInfoSinkWithPrefix(prefix string) (DataSink, error) {
|
||||
return nil, errors.New("Not implemented on OSX")
|
||||
}
|
||||
63
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/encode_linux.go
generated
vendored
Normal file
63
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/encode_linux.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vmw-guestinfo/rpcvmx"
|
||||
"github.com/vmware/vmw-guestinfo/vmcheck"
|
||||
)
|
||||
|
||||
// GuestInfoSink uses the rpcvmx mechanism to update the guestinfo key/value map as
|
||||
// the datasink for encoding target structures
|
||||
func GuestInfoSink() (DataSink, error) {
|
||||
return GuestInfoSinkWithPrefix("")
|
||||
}
|
||||
|
||||
// GuestInfoSinkWithPrefix adds a prefix to all keys accessed. The key must not have leading
|
||||
// or trailing separator characters, but may have separators in other positions. The separator
|
||||
// (either . or /) will be replaced with the appropriate value for the key in question.
|
||||
func GuestInfoSinkWithPrefix(prefix string) (DataSink, error) {
|
||||
// Check we're using a vcpu (which doesn't assume this is UID 0).
|
||||
if !vmcheck.IsVirtualCPU() {
|
||||
return nil, fmt.Errorf("not in a virtual world")
|
||||
}
|
||||
|
||||
guestinfo := rpcvmx.NewConfig()
|
||||
|
||||
return func(key, value string) error {
|
||||
if strings.Contains(key, "/") {
|
||||
// quietly skip if it's a read-only key
|
||||
return nil
|
||||
}
|
||||
|
||||
key = addPrefixToKey(DefaultGuestInfoPrefix, prefix, key)
|
||||
|
||||
if value == "" {
|
||||
value = "<nil>"
|
||||
}
|
||||
|
||||
log.Debugf("GuestInfoSink: setting key: %s, value: %#v", key, value)
|
||||
err := guestinfo.SetString(key, value)
|
||||
if err != nil {
|
||||
log.Errorf("GuestInfoSink: error: %#v", err)
|
||||
}
|
||||
return err
|
||||
}, nil
|
||||
}
|
||||
30
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/encode_windows.go
generated
vendored
Normal file
30
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/encode_windows.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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 "errors"
|
||||
|
||||
// GuestInfoSink uses the rpcvmx mechanism to update the guestinfo key/value map as
|
||||
// the datasink for encoding target structures
|
||||
func GuestInfoSink() (DataSink, error) {
|
||||
return GuestInfoSinkWithPrefix("")
|
||||
}
|
||||
|
||||
// GuestInfoSinkWithPrefix adds a prefix to all keys accessed. The key must not have leading
|
||||
// or trailing separator characters, but may have separators in other positions. The separator
|
||||
// (either . or /) will be replaced with the appropriate value for the key in question.
|
||||
func GuestInfoSinkWithPrefix(prefix string) (DataSink, error) {
|
||||
return nil, errors.New("Not implemented on Windows")
|
||||
}
|
||||
21
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/existing_test.go
generated
vendored
Normal file
21
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/existing_test.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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 "testing"
|
||||
|
||||
func TestExistingBasic(t *testing.T) {
|
||||
t.Skip("decode into existing target tests not yet implemented")
|
||||
}
|
||||
510
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/keys.go
generated
vendored
Normal file
510
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/keys.go
generated
vendored
Normal file
@@ -0,0 +1,510 @@
|
||||
// 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 (
|
||||
// DefaultTagName value
|
||||
DefaultTagName = "vic"
|
||||
// DefaultPrefix value
|
||||
DefaultPrefix = ""
|
||||
// DefaultGuestInfoPrefix value
|
||||
DefaultGuestInfoPrefix = "guestinfo.vice."
|
||||
//Separator for slice values and map keys
|
||||
Separator = "|"
|
||||
|
||||
// suffix separator character
|
||||
suffixSeparator = "@"
|
||||
// secret suffix
|
||||
secretSuffix = "secret"
|
||||
// non-persistent suffix
|
||||
nonpersistentSuffix = "non-persistent"
|
||||
)
|
||||
|
||||
const (
|
||||
// Invalid value
|
||||
Invalid = 1 << iota
|
||||
// Hidden value
|
||||
Hidden
|
||||
// ReadOnly value
|
||||
ReadOnly
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 "hidden":
|
||||
scope |= Hidden
|
||||
case "read-only":
|
||||
scope |= ReadOnly
|
||||
case "read-write":
|
||||
scope |= ReadWrite
|
||||
case nonpersistentSuffix:
|
||||
scope |= NonPersistent
|
||||
case "volatile":
|
||||
scope |= Volatile
|
||||
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, DefaultGuestInfoPrefix) {
|
||||
scopes = append(scopes, "hidden")
|
||||
}
|
||||
|
||||
if strings.Contains(key, "/") {
|
||||
scopes = append(scopes, "read-only")
|
||||
} else {
|
||||
scopes = append(scopes, "read-write")
|
||||
}
|
||||
|
||||
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("scope"), ",")
|
||||
logger.Debugf("Scopes: %#v", scopes)
|
||||
|
||||
// get the keys and split properties from it
|
||||
key = tags.Get("key")
|
||||
logger.Debugf("Key specified: %s", key)
|
||||
|
||||
// get the keys and split properties from it
|
||||
recurse := tags.Get("recurse")
|
||||
if recurse != "" {
|
||||
props := strings.Split(recurse, ",")
|
||||
// process properties
|
||||
for _, prop := range props {
|
||||
// determine recursion depth
|
||||
if strings.HasPrefix(prop, "depth") {
|
||||
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 == "nofollow" {
|
||||
fdepth.follow = false
|
||||
} else if prop == "follow" {
|
||||
fdepth.follow = 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, DefaultGuestInfoPrefix)
|
||||
|
||||
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)
|
||||
}
|
||||
269
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/keys_test.go
generated
vendored
Normal file
269
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/keys_test.go
generated
vendored
Normal file
@@ -0,0 +1,269 @@
|
||||
// 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 (
|
||||
"testing"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func visibleRO(key string) string {
|
||||
return calculateKey(calculateScope([]string{"read-only"}), "", key)
|
||||
}
|
||||
|
||||
func visibleRONonpersistent(key string) string {
|
||||
return calculateKey(calculateScope([]string{"read-only", "non-persistent"}), "", key)
|
||||
}
|
||||
|
||||
func visibleRW(key string) string {
|
||||
return calculateKey(calculateScope([]string{"read-write"}), "", key)
|
||||
}
|
||||
|
||||
func hidden(key string) string {
|
||||
return calculateKey(calculateScope([]string{"hidden"}), "", key)
|
||||
}
|
||||
|
||||
func TestHidden(t *testing.T) {
|
||||
scopes := []string{"hidden"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), "a/b", "c")
|
||||
|
||||
assert.Equal(t, "a/b/c", key, "Key should remain hidden")
|
||||
}
|
||||
|
||||
func TestHide(t *testing.T) {
|
||||
scopes := []string{"hidden"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+"/a/b", "c")
|
||||
|
||||
assert.Equal(t, "a/b/c", key, "Key should be hidden")
|
||||
}
|
||||
|
||||
func TestReveal(t *testing.T) {
|
||||
scopes := []string{"read-only"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), "a/b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+"/a/b/c", key, "Key should be exposed")
|
||||
}
|
||||
|
||||
func TestVisibleReadOnly(t *testing.T) {
|
||||
scopes := []string{"read-only"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+"/a/b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+"/a/b/c", key, "Key should be remain visible and read-only")
|
||||
}
|
||||
|
||||
func TestVisibleReadWrite(t *testing.T) {
|
||||
scopes := []string{"read-write"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a.b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+".a.b.c", key, "Key should be remain visible and read-write")
|
||||
}
|
||||
|
||||
func TestTopLevelReadOnly(t *testing.T) {
|
||||
scopes := []string{"read-only"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), "", "a")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+"/a", key, "Key should be visible and read-only")
|
||||
}
|
||||
|
||||
func TestReadOnlyToReadWrite(t *testing.T) {
|
||||
scopes := []string{"read-write"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+"/a/b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+".a.b.c", key, "Key should be visible and change to read-write")
|
||||
}
|
||||
|
||||
func TestReadWriteToReadOnly(t *testing.T) {
|
||||
scopes := []string{"read-only"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a.b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+"/a/b/c", key, "Key should be visible and change to read-only")
|
||||
}
|
||||
|
||||
func TestCompoundKey(t *testing.T) {
|
||||
scopes := []string{"read-write"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a", "b/c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+".a.b.c", key, "Key should be visible and read-write")
|
||||
}
|
||||
|
||||
func TestNoScopes(t *testing.T) {
|
||||
scopes := []string{}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a/b", "c")
|
||||
assert.Equal(t, "a/b/c", key, "Key should be completely proscriptive")
|
||||
|
||||
key = calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a.b", "c")
|
||||
assert.Equal(t, "a.b/c", key, "Key should be hidden")
|
||||
|
||||
key = calculateKey(calculateScope(scopes), "a.b", "c")
|
||||
assert.Equal(t, "a.b/c", key, "Key should remain hidden")
|
||||
}
|
||||
|
||||
func TestSecret(t *testing.T) {
|
||||
scopes := []string{"secret", "read-write"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a.b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+".a.b.c"+suffixSeparator+secretSuffix, key, "Key should have secret suffix")
|
||||
}
|
||||
|
||||
func TestNonpersistent(t *testing.T) {
|
||||
scopes := []string{"non-persistent", "read-write"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a.b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+".a.b.c"+suffixSeparator+nonpersistentSuffix, key, "Key should have non-persistent suffix")
|
||||
}
|
||||
|
||||
func TestMultipleSuffixes(t *testing.T) {
|
||||
scopes := []string{"non-persistent", "secret", "read-write"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a.b", "c")
|
||||
|
||||
assert.True(t, strings.Contains(key, suffixSeparator+secretSuffix) && strings.Contains(key, suffixSeparator+nonpersistentSuffix), "Key should contain both secret and non-persistent suffix")
|
||||
}
|
||||
|
||||
func TestCalculateKeys(t *testing.T) {
|
||||
type AStruct struct {
|
||||
I int
|
||||
}
|
||||
type Type struct {
|
||||
ExecutorConfig ExecutorConfig `vic:"0.1" scope:"hidden" key:"executorconfig"`
|
||||
Array []AStruct `vic:"0.1" scope:"read-write" key:"array"`
|
||||
Ptr *AStruct `vic:"0.1" scope:"read-only" key:"ptr"`
|
||||
Str string `vic:"0.1" scope:"read-only" key:"str"`
|
||||
Bytes []uint8 `vic:"0.1" scope:"read-write" key:"bytes"`
|
||||
}
|
||||
|
||||
ec := Type{
|
||||
ExecutorConfig: ExecutorConfig{
|
||||
Sessions: map[string]SessionConfig{
|
||||
"Session1": {
|
||||
Common: Common{
|
||||
ID: "SessionID",
|
||||
Name: "SessionName",
|
||||
},
|
||||
Tty: true,
|
||||
Cmd: Cmd{
|
||||
Path: "/vmware",
|
||||
Args: []string{"/bin/imagec", "-standalone"},
|
||||
Env: []string{"PATH=/bin", "USER=imagec"},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Array: []AStruct{
|
||||
{I: 0},
|
||||
},
|
||||
Ptr: &AStruct{
|
||||
I: 1,
|
||||
},
|
||||
Str: "foo",
|
||||
Bytes: []byte{0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf},
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{
|
||||
"ExecutorConfig.*",
|
||||
[]string{
|
||||
visibleRO("executorconfig/common"),
|
||||
hidden("executorconfig/sessions"),
|
||||
"executorconfig/Key",
|
||||
},
|
||||
},
|
||||
{
|
||||
"ExecutorConfig.Sessions.*",
|
||||
[]string{"executorconfig/sessions" + Separator + "Session1"},
|
||||
},
|
||||
{
|
||||
"ExecutorConfig.Sessions.Session1.Cmd.Args",
|
||||
[]string{"executorconfig/sessions" + Separator + "Session1/cmd/args"},
|
||||
},
|
||||
{
|
||||
"ExecutorConfig.Sessions.*.Cmd.Args.*",
|
||||
[]string{"executorconfig/sessions" + Separator + "Session1/cmd/args~"},
|
||||
},
|
||||
{
|
||||
"ExecutorConfig.Sessions.*.Cmd.Args.0",
|
||||
[]string{"executorconfig/sessions" + Separator + "Session1/cmd/args~"},
|
||||
},
|
||||
{
|
||||
"Array.0.I",
|
||||
[]string{visibleRW("array" + Separator + "0/I")},
|
||||
},
|
||||
{
|
||||
"Array.*",
|
||||
[]string{visibleRW("array" + Separator + "0")},
|
||||
},
|
||||
{
|
||||
"Ptr.I",
|
||||
[]string{visibleRO("ptr/I")},
|
||||
},
|
||||
{
|
||||
"Str",
|
||||
[]string{visibleRO("str")},
|
||||
},
|
||||
{
|
||||
"Bytes",
|
||||
[]string{visibleRW("bytes")},
|
||||
},
|
||||
{
|
||||
"Bytes.0",
|
||||
[]string{visibleRW("bytes")},
|
||||
},
|
||||
{
|
||||
"Bytes.*",
|
||||
[]string{visibleRW("bytes")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
keys := CalculateKeys(ec, te.in, "")
|
||||
assert.Equal(t, te.out, keys)
|
||||
}
|
||||
|
||||
panicTests := []string{
|
||||
"Array.1.I",
|
||||
"Array.0.i",
|
||||
"Array.f.i",
|
||||
"ExecutorConfig.foo",
|
||||
"foo",
|
||||
"ExecutorConfig.Sessions.foo",
|
||||
"Str.*",
|
||||
"Str.foo",
|
||||
}
|
||||
|
||||
for _, te := range panicTests {
|
||||
assert.Panics(t, func() {
|
||||
CalculateKeys(ec, te, "")
|
||||
})
|
||||
}
|
||||
}
|
||||
127
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/secret.go
generated
vendored
Normal file
127
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/secret.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
// 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 (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
)
|
||||
|
||||
// The value of this key is hidden from API requests, but visible within the guest
|
||||
// #nosec: Potential hardcoded credentials
|
||||
const GuestInfoSecretKey = "guestinfo.ovfEnv"
|
||||
|
||||
// SecretKey provides helpers to encrypt/decrypt extraconfig values
|
||||
type SecretKey struct {
|
||||
key [32]byte
|
||||
}
|
||||
|
||||
// NewSecretKey generates a new secret key
|
||||
func NewSecretKey() (*SecretKey, error) {
|
||||
s := new(SecretKey)
|
||||
|
||||
if _, err := rand.Read(s.key[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// FromString base64 decodes an existing SecretKey
|
||||
func (s *SecretKey) FromString(key string) error {
|
||||
b, err := base64.StdEncoding.DecodeString(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(b) != 32 {
|
||||
return errors.New("invalid secret key")
|
||||
}
|
||||
|
||||
copy(s.key[:], b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String base64 encodes a SecretKey
|
||||
func (s *SecretKey) String() string {
|
||||
return base64.StdEncoding.EncodeToString(s.key[:])
|
||||
}
|
||||
|
||||
// Source wraps the given DataSource, decrypting any secret values
|
||||
func (s *SecretKey) Source(ds DataSource) DataSource {
|
||||
// If GuestInfoSecretKey has a value, it should be our secret key.
|
||||
// #nosec: Errors unhandled.
|
||||
if val, _ := ds(GuestInfoSecretKey); val != "" {
|
||||
if err := s.FromString(val); err != nil {
|
||||
log.Errorf("failed to decode %s: %s", GuestInfoSecretKey, err)
|
||||
} else {
|
||||
log.Debugf("secret key decoded from %s", GuestInfoSecretKey)
|
||||
}
|
||||
}
|
||||
|
||||
return func(key string) (string, error) {
|
||||
val, err := ds(key)
|
||||
|
||||
if err == nil && isSecret(key) {
|
||||
b, err := base64.StdEncoding.DecodeString(val)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var nonce [24]byte
|
||||
copy(nonce[:], b[:24])
|
||||
|
||||
plaintext, ok := secretbox.Open([]byte{}, b[24:], &nonce, &s.key)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("failed to decrypt value for %s", key)
|
||||
}
|
||||
|
||||
val = string(plaintext)
|
||||
}
|
||||
|
||||
return val, err
|
||||
}
|
||||
}
|
||||
|
||||
// Sink wraps the given DataSink, encrypting any secret values
|
||||
func (s *SecretKey) Sink(ds DataSink) DataSink {
|
||||
// Store our secret key.
|
||||
if err := ds(GuestInfoSecretKey, s.String()); err != nil {
|
||||
log.Errorf("failed to store %s: %s", GuestInfoSecretKey, err)
|
||||
}
|
||||
|
||||
return func(key, value string) error {
|
||||
if isSecret(key) {
|
||||
var nonce [24]byte
|
||||
|
||||
if _, err := rand.Read(nonce[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ciphertext := secretbox.Seal(nonce[:], []byte(value), &nonce, &s.key)
|
||||
|
||||
value = base64.StdEncoding.EncodeToString(ciphertext)
|
||||
}
|
||||
|
||||
return ds(key, value)
|
||||
}
|
||||
}
|
||||
67
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/secret_test.go
generated
vendored
Normal file
67
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/secret_test.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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 (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSecretFields(t *testing.T) {
|
||||
type tell struct {
|
||||
Who string `vic:"0.1" scope:"secret" key:"who"`
|
||||
}
|
||||
|
||||
type stuff struct {
|
||||
Username string `vic:"0.1" scope:"read-only" key:"username"`
|
||||
Password string `vic:"0.1" scope:"secret" key:"password"`
|
||||
Tell tell
|
||||
}
|
||||
|
||||
config := stuff{
|
||||
Username: "root",
|
||||
Password: "super-s@fe-passw0rd",
|
||||
Tell: tell{"noone"},
|
||||
}
|
||||
|
||||
out, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(out.Sink(MapSink(encoded)), config)
|
||||
|
||||
password := encoded["guestinfo.vice./password"+suffixSeparator+secretSuffix]
|
||||
assert.NotEmpty(t, password, "encrypted password")
|
||||
assert.NotEqual(t, password, config.Password, "encrypted password")
|
||||
|
||||
for _, expectEq := range []bool{true, false} {
|
||||
var in SecretKey
|
||||
|
||||
var decoded stuff
|
||||
Decode(in.Source(MapSource(encoded)), &decoded)
|
||||
|
||||
if expectEq {
|
||||
assert.Equal(t, config, decoded, "Encoded and decoded does not match")
|
||||
} else {
|
||||
assert.NotEqual(t, config, decoded, "Encoded and decoded should not not match")
|
||||
}
|
||||
|
||||
// second time should fail to decrypt w/o GuestInfoSecretKey
|
||||
delete(encoded, GuestInfoSecretKey)
|
||||
}
|
||||
}
|
||||
56
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/store.go
generated
vendored
Normal file
56
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/store.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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 (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Store provides combined DataSource and DataSink.
|
||||
type Store interface {
|
||||
Get(string) (string, error)
|
||||
Put(string, string) error
|
||||
}
|
||||
|
||||
type MapStore struct {
|
||||
mutex sync.Mutex
|
||||
|
||||
store map[string]string
|
||||
}
|
||||
|
||||
func New() *MapStore {
|
||||
return &MapStore{
|
||||
store: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *MapStore) Get(key string) (string, error) {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
|
||||
val, ok := t.store[key]
|
||||
if !ok {
|
||||
return "", ErrKeyNotFound
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (t *MapStore) Put(key, value string) error {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
|
||||
t.store[key] = value
|
||||
return nil
|
||||
}
|
||||
55
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi/delta_test.go
generated
vendored
Normal file
55
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi/delta_test.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
// 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 vmomi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"reflect"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
func TestDelta(t *testing.T) {
|
||||
new := map[string]string{
|
||||
"hello": "goodbye",
|
||||
"cruel": "world",
|
||||
"is": "not",
|
||||
"enough": "already",
|
||||
}
|
||||
|
||||
existing := []types.BaseOptionValue{
|
||||
&types.OptionValue{Key: "hello", Value: "goodbye"},
|
||||
&types.OptionValue{Key: "is", Value: "always"},
|
||||
&types.OptionValue{Key: "present", Value: "regardless"},
|
||||
}
|
||||
|
||||
updatesSlice := OptionValueUpdatesFromMap(existing, new)
|
||||
|
||||
expected := map[string]string{
|
||||
"enough": "already", // added
|
||||
"cruel": "world", // added
|
||||
"is": "not", // changed
|
||||
}
|
||||
|
||||
// turn them back into maps for equality check
|
||||
updates := OptionValueMap(updatesSlice)
|
||||
|
||||
if !assert.True(t, reflect.DeepEqual(expected, updates), "DeepEqual says they do not match") {
|
||||
t.Fatalf("Expected: %+q \nActual: %+q\n", expected, updates)
|
||||
}
|
||||
}
|
||||
140
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi/optionvalue.go
generated
vendored
Normal file
140
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi/optionvalue.go
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
// 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 vmomi is in a separate package to avoid the transitive inclusion of govmomi
|
||||
// as a fundamental dependency of the main extraconfig
|
||||
package vmomi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
// OptionValueMap returns a map from array of OptionValues
|
||||
func OptionValueMap(src []types.BaseOptionValue) map[string]string {
|
||||
// create the key/value store from the extraconfig slice for lookups
|
||||
kv := make(map[string]string)
|
||||
for i := range src {
|
||||
k := src[i].GetOptionValue().Key
|
||||
v := src[i].GetOptionValue().Value.(string)
|
||||
kv[k] = unescapeNil(v)
|
||||
}
|
||||
return kv
|
||||
}
|
||||
|
||||
// OptionValueSource is a convenience method to generate a MapSource source from
|
||||
// and array of OptionValue's
|
||||
func OptionValueSource(src []types.BaseOptionValue) extraconfig.DataSource {
|
||||
kv := OptionValueMap(src)
|
||||
return extraconfig.MapSource(kv)
|
||||
}
|
||||
|
||||
// OptionValueFromMap is a convenience method to convert a map into a BaseOptionValue array
|
||||
// escapeNil - if true a nil string is replaced with "<nil>". Allows us to distinguish between
|
||||
// deletion and nil as a value
|
||||
func OptionValueFromMap(data map[string]string, escape bool) []types.BaseOptionValue {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
array := make([]types.BaseOptionValue, len(data))
|
||||
|
||||
i := 0
|
||||
for k, v := range data {
|
||||
if escape {
|
||||
v = escapeNil(v)
|
||||
}
|
||||
array[i] = &types.OptionValue{Key: k, Value: v}
|
||||
i++
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
// OptionValueArrayToString translates the options array in to a Go formatted structure dump
|
||||
func OptionValueArrayToString(options []types.BaseOptionValue) string {
|
||||
// create the key/value store from the extraconfig slice for lookups
|
||||
kv := make(map[string]string)
|
||||
for i := range options {
|
||||
k := options[i].GetOptionValue().Key
|
||||
v := options[i].GetOptionValue().Value.(string)
|
||||
kv[k] = v
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%#v", kv)
|
||||
}
|
||||
|
||||
// OptionValueUpdatesFromMap generates an optionValue array for those entries in the map that do not
|
||||
// already exist, are changed from the reference array, or a removed
|
||||
// A removed entry will have a nil string for the value
|
||||
// NOTE: DOES NOT CURRENTLY SUPPORT DELETION OF KEYS - KEYS MISSING FROM NEW MAP ARE IGNORED
|
||||
func OptionValueUpdatesFromMap(existing []types.BaseOptionValue, new map[string]string) []types.BaseOptionValue {
|
||||
e := len(existing)
|
||||
if e == 0 {
|
||||
return OptionValueFromMap(new, true)
|
||||
}
|
||||
|
||||
n := len(new)
|
||||
updates := make(map[string]string, n+e)
|
||||
unchanged := make(map[string]struct{}, n+e)
|
||||
|
||||
// first the existing keys
|
||||
for i := range existing {
|
||||
v := existing[i].GetOptionValue()
|
||||
if nV, ok := new[v.Key]; ok && nV == v.Value.(string) {
|
||||
unchanged[v.Key] = struct{}{}
|
||||
// no change
|
||||
continue
|
||||
} else if ok {
|
||||
// changed
|
||||
updates[v.Key] = escapeNil(nV)
|
||||
} else {
|
||||
// deletion
|
||||
// NOTE: ignored as this also deletes non VIC entries currently
|
||||
// there's no prefix for the non-guestinfo keys so cannot easily filter
|
||||
// updates[v.Key] = ""
|
||||
}
|
||||
}
|
||||
|
||||
// now the new keys
|
||||
for k, v := range new {
|
||||
if _, ok := unchanged[k]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := updates[k]; !ok {
|
||||
updates[k] = escapeNil(v)
|
||||
}
|
||||
}
|
||||
|
||||
return OptionValueFromMap(updates, false)
|
||||
}
|
||||
|
||||
func escapeNil(input string) string {
|
||||
if input == "" {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
func unescapeNil(input string) string {
|
||||
if input == "<nil>" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
Reference in New Issue
Block a user