convert ManagedFields to internal type

This commit is contained in:
jennybuckley
2018-11-13 12:42:36 -08:00
parent 38ab32898e
commit 0253ed4a43
5 changed files with 698 additions and 0 deletions

8
Godeps/Godeps.json generated
View File

@@ -4008,6 +4008,14 @@
"ImportPath": "k8s.io/utils/pointer",
"Rev": "66066c83e385e385ccc3c964b44fd7dcd413d0ed"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
"Rev": "261cd9791e59cf384903eff0e7281c6522f5cec7"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/value",
"Rev": "261cd9791e59cf384903eff0e7281c6522f5cec7"
},
{
"ImportPath": "vbom.ml/util/sortorder",
"Rev": "db5cfe13f5cc80a4990d98e2e1b0707a4d1a5394"

View File

@@ -0,0 +1,173 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
import (
"fmt"
"reflect"
"strconv"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/value"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// DecodeManagedFields converts ManagedFields from the wire format (api format)
// to the format used by sigs.k8s.io/structured-merge-diff
func DecodeManagedFields(encodedManagedFields map[string]metav1.VersionedFieldSet) (managedFields fieldpath.ManagedFields, err error) {
managedFields = make(map[string]*fieldpath.VersionedSet, len(encodedManagedFields))
for manager, encodedVersionedSet := range encodedManagedFields {
managedFields[manager], err = decodeVersionedSet(&encodedVersionedSet)
if err != nil {
return nil, fmt.Errorf("error decoding versioned set for %v: %v", manager, err)
}
}
return managedFields, nil
}
func decodeVersionedSet(encodedVersionedSet *metav1.VersionedFieldSet) (versionedSet *fieldpath.VersionedSet, err error) {
versionedSet = &fieldpath.VersionedSet{}
versionedSet.APIVersion = fieldpath.APIVersion(encodedVersionedSet.APIVersion)
versionedSet.Set = &fieldpath.Set{}
err = decodeSetInto(&encodedVersionedSet.Fields, versionedSet.Set)
if err != nil {
return nil, fmt.Errorf("error decoding set: %v", err)
}
return versionedSet, nil
}
func decodeSetInto(encodedSet *metav1.FieldSet, set *fieldpath.Set) (err error) {
m, err := decodePathElementSet(encodedSet.Members)
if err != nil {
return fmt.Errorf("error decoding path element set: %v", err)
}
set.Members = *m
c, err := decodeSetNodeMap(encodedSet.Children)
if err != nil {
return fmt.Errorf("error decoding children: %v", err)
}
set.Children = *c
return nil
}
func decodePathElementSet(encodedPathElementSet []metav1.FieldPathElement) (pathElementSet *fieldpath.PathElementSet, err error) {
pathElementSet = &fieldpath.PathElementSet{}
for _, encodedPathElement := range encodedPathElementSet {
pathElement, err := decodePathElement(&encodedPathElement)
if err != nil {
return nil, fmt.Errorf("error decoding path element: %v", err)
}
pathElementSet.Insert(*pathElement)
}
return pathElementSet, nil
}
func decodeSetNodeMap(encodedSetNodeMap []metav1.FieldSetNode) (setNodeMap *fieldpath.SetNodeMap, err error) {
setNodeMap = &fieldpath.SetNodeMap{}
for _, encodedSetNode := range encodedSetNodeMap {
pathElement, err := decodePathElement(&encodedSetNode.PathElement)
if err != nil {
return nil, fmt.Errorf("error decoding path element: %v", err)
}
set := setNodeMap.Descend(*pathElement)
decodeSetInto(encodedSetNode.Set, set)
}
return setNodeMap, nil
}
func decodePathElement(encodedPathElement *metav1.FieldPathElement) (pathElement *fieldpath.PathElement, err error) {
if encodedPathElement == nil {
return nil, nil
}
if !validateOneOf(encodedPathElement.FieldName, encodedPathElement.Key, encodedPathElement.Value, encodedPathElement.Index) {
return nil, fmt.Errorf("too many fields set: %v", encodedPathElement)
}
pathElement = &fieldpath.PathElement{}
pathElement.FieldName = encodedPathElement.FieldName
if encodedPathElement.Key != nil {
pathElement.Key = make([]value.Field, len(encodedPathElement.Key))
for i, encodedField := range encodedPathElement.Key {
pathElement.Key[i], err = decodeField(&encodedField)
if err != nil {
return nil, fmt.Errorf("error decoding field: %v", err)
}
}
}
pathElement.Value, err = decodeValue(encodedPathElement.Value)
if err != nil {
return nil, fmt.Errorf("error decoding value: %v", err)
}
if encodedPathElement.Index != nil {
v := int(*encodedPathElement.Index)
pathElement.Index = &v
}
return pathElement, nil
}
func decodeField(encodedField *metav1.FieldNameValuePair) (field value.Field, err error) {
field = value.Field{}
if encodedField == nil {
return field, fmt.Errorf("unexpected nil pointer")
}
field.Name = encodedField.Name
v, err := decodeValue(&encodedField.Value)
if err != nil {
return field, fmt.Errorf("error decoding value: %v", err)
}
if v != nil {
field.Value = *v
}
return field, nil
}
func decodeValue(encodedValue *metav1.FieldValue) (v *value.Value, err error) {
if encodedValue == nil {
return nil, nil
}
if !validateOneOf(encodedValue.FloatValue, encodedValue.IntValue, encodedValue.StringValue, encodedValue.BooleanValue) {
return nil, fmt.Errorf("too many fields set: %v", encodedValue)
}
v = &value.Value{}
if encodedValue.FloatValue != nil {
f, err := strconv.ParseFloat(*encodedValue.FloatValue, 64)
if err != nil {
return nil, fmt.Errorf("error parsing float: %v", err)
}
v.FloatValue = (*value.Float)(&f)
}
if encodedValue.IntValue != nil {
i := int64(*encodedValue.IntValue)
v.IntValue = (*value.Int)(&i)
}
v.StringValue = (*value.String)(encodedValue.StringValue)
v.BooleanValue = (*value.Boolean)(encodedValue.BooleanValue)
v.Null = encodedValue.Null
return v, nil
}
func validateOneOf(fields ...interface{}) bool {
nonNilCount := 0
for _, field := range fields {
if !reflect.ValueOf(field).IsNil() {
nonNilCount++
}
}
return nonNilCount <= 1
}

View File

@@ -0,0 +1,64 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
import (
"testing"
)
// TestValidateOneOf tests that validateOneOf returns true if <= 1
// non-nil fields are passed to it are and false otherwise
func TestValidateOneOf(t *testing.T) {
var nilValue *string
nonNilValue := strPtr("test")
tests := []struct {
fields []interface{}
expected bool
}{
{
fields: []interface{}{},
expected: true,
}, {
fields: []interface{}{nilValue},
expected: true,
}, {
fields: []interface{}{nonNilValue},
expected: true,
}, {
fields: []interface{}{nilValue, nonNilValue, nilValue},
expected: true,
}, {
fields: []interface{}{nonNilValue, nonNilValue},
expected: false,
}, {
fields: []interface{}{nilValue, nonNilValue, nonNilValue, nilValue},
expected: false,
},
}
for i, tc := range tests {
actual := validateOneOf(tc.fields...)
if actual != tc.expected {
t.Errorf("[%v]expected validateOneOf(%v) to return:\n\t%+v\nbut got:\n\t%+v", i, tc.fields, tc.expected, actual)
}
}
}
func strPtr(s string) *string {
return &s
}

View File

@@ -0,0 +1,188 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
import (
"fmt"
"sort"
"strconv"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/value"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EncodeManagedFields converts ManagedFields from the the format used by
// sigs.k8s.io/structured-merge-diff to the the wire format (api format)
func EncodeManagedFields(managedFields fieldpath.ManagedFields) (encodedManagedFields map[string]metav1.VersionedFieldSet, err error) {
encodedManagedFields = make(map[string]metav1.VersionedFieldSet, len(managedFields))
for manager, versionedSet := range managedFields {
v, err := encodeVersionedSet(versionedSet)
if err != nil {
return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err)
}
encodedManagedFields[manager] = *v
}
return encodedManagedFields, nil
}
func encodeVersionedSet(versionedSet *fieldpath.VersionedSet) (encodedVersionedSet *metav1.VersionedFieldSet, err error) {
encodedVersionedSet = &metav1.VersionedFieldSet{}
encodedVersionedSet.APIVersion = string(versionedSet.APIVersion)
f, err := encodeSet(versionedSet.Set)
if err != nil {
return nil, fmt.Errorf("error encoding set: %v", err)
}
encodedVersionedSet.Fields = *f
return encodedVersionedSet, nil
}
func encodeSet(set *fieldpath.Set) (encodedSet *metav1.FieldSet, err error) {
encodedSet = &metav1.FieldSet{}
encodedSet.Members, err = encodePathElementSet(&set.Members)
if err != nil {
return nil, fmt.Errorf("error encoding path element set: %v", err)
}
encodedSet.Children, err = encodeSetNodeMap(&set.Children)
if err != nil {
return nil, fmt.Errorf("error encoding children: %v", err)
}
return encodedSet, nil
}
func encodePathElementSet(pathElementSet *fieldpath.PathElementSet) (encodedPathElementSet []metav1.FieldPathElement, err error) {
// Get a list of the elements in the pathElementSet and sort it
pathElementList := make(sortablePathElementList, 0)
pathElementSet.Iterate(func(pathElement fieldpath.PathElement) {
pathElementList = append(pathElementList, &pathElement)
})
sort.Sort(pathElementList)
if len(pathElementList) == 0 {
return nil, nil
}
encodedPathElementSet = make([]metav1.FieldPathElement, len(pathElementList))
for i, pathElement := range pathElementList {
encodedPathElement, err := encodePathElement(pathElement)
if err != nil {
return nil, fmt.Errorf("error encoding path element: %v", err)
}
encodedPathElementSet[i] = encodedPathElement
}
return encodedPathElementSet, nil
}
func encodeSetNodeMap(setNodeMap *fieldpath.SetNodeMap) (encodedSetNodeMap []metav1.FieldSetNode, err error) {
// Get a list of the keys in the setNodeMap and sort it
pathElementList := make(sortablePathElementList, 0)
setNodeMap.Iterate(func(pathElement fieldpath.PathElement) {
pathElementList = append(pathElementList, &pathElement)
})
sort.Sort(pathElementList)
if len(pathElementList) == 0 {
return nil, nil
}
encodedSetNodeMap = make([]metav1.FieldSetNode, len(pathElementList))
for i, pathElement := range pathElementList {
encodedPathElement, err := encodePathElement(pathElement)
if err != nil {
return nil, fmt.Errorf("error encoding path element %v: %v", pathElement.String(), err)
}
set, ok := setNodeMap.Get(*pathElement)
if !ok {
return nil, fmt.Errorf("error looking up path element %v", pathElement.String())
}
encodedSet, err := encodeSet(set)
if err != nil {
return nil, fmt.Errorf("error encoding set for %v: %v", pathElement.String(), err)
}
encodedSetNodeMap[i] = metav1.FieldSetNode{
PathElement: encodedPathElement,
Set: encodedSet,
}
}
return encodedSetNodeMap, nil
}
func encodePathElement(pathElement *fieldpath.PathElement) (encodedPathElement metav1.FieldPathElement, err error) {
encodedPathElement = metav1.FieldPathElement{}
if pathElement == nil {
return encodedPathElement, nil
}
encodedPathElement.FieldName = pathElement.FieldName
if pathElement.Key != nil {
encodedPathElement.Key = make([]metav1.FieldNameValuePair, len(pathElement.Key))
for i, field := range pathElement.Key {
encodedPathElement.Key[i], err = encodeField(&field)
if err != nil {
return encodedPathElement, fmt.Errorf("error encoding field: %v", err)
}
}
}
encodedPathElement.Value, err = encodeValue(pathElement.Value)
if err != nil {
return encodedPathElement, fmt.Errorf("error encoding value: %v", err)
}
if pathElement.Index != nil {
v := int32(*pathElement.Index)
encodedPathElement.Index = &v
}
return encodedPathElement, nil
}
func encodeField(field *value.Field) (encodedField metav1.FieldNameValuePair, err error) {
encodedField = metav1.FieldNameValuePair{}
if field == nil {
return encodedField, fmt.Errorf("unexpected nil pointer")
}
encodedField.Name = field.Name
v, err := encodeValue(&field.Value)
if err != nil {
return encodedField, fmt.Errorf("error encoding value: %v", err)
}
if v != nil {
encodedField.Value = *v
}
return encodedField, nil
}
func encodeValue(v *value.Value) (encodedValue *metav1.FieldValue, err error) {
if v == nil {
return nil, nil
}
encodedValue = &metav1.FieldValue{}
if v.FloatValue != nil {
f := strconv.FormatFloat(float64(*v.FloatValue), 'f', -1, 64)
encodedValue.FloatValue = &f
}
if v.IntValue != nil {
i := int32(int64(*v.IntValue))
encodedValue.IntValue = &i
}
encodedValue.StringValue = (*string)(v.StringValue)
encodedValue.BooleanValue = (*bool)(v.BooleanValue)
encodedValue.Null = v.Null
return encodedValue, nil
}
type sortablePathElementList []*fieldpath.PathElement
func (p sortablePathElementList) Len() int { return len(p) }
func (p sortablePathElementList) Less(i, j int) bool { return p[i].String() < p[j].String() }
func (p sortablePathElementList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

View File

@@ -0,0 +1,265 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
import (
"reflect"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/ghodss/yaml"
)
// TestRoundTripManagedFields will roundtrip ManagedFields from the format used by
// sigs.k8s.io/structured-merge-diff to the wire format (api format) and back
func TestRoundTripManagedFields(t *testing.T) {
tests := []struct {
yaml []byte
errString string
}{
{
yaml: []byte(`foo:
apiVersion: v1
fields:
children:
- pathElement:
index: 5
set:
members:
- fieldName: i
- pathElement:
value:
floatValue: 3.1415
set:
members:
- fieldName: pi
- pathElement:
value:
intValue: 3
set:
members:
- fieldName: alsoPi
- pathElement:
value:
booleanValue: false
set:
members:
- fieldName: notTrue`),
}, {
yaml: []byte(`foo:
apiVersion: v1
fields:
children:
- pathElement:
fieldName: spec
set:
children:
- pathElement:
fieldName: containers
set:
children:
- pathElement:
key:
- name: name
value:
stringValue: c
'null': false
set:
members:
- fieldName: image
- fieldName: name`),
}, {
yaml: []byte(`foo:
apiVersion: v1
fields:
members:
- fieldName: apiVersion
- fieldName: kind
children:
- pathElement:
fieldName: metadata
set:
members:
- fieldName: name
children:
- pathElement:
fieldName: labels
set:
members:
- fieldName: app
- pathElement:
fieldName: spec
set:
members:
- fieldName: replicas
children:
- pathElement:
fieldName: selector
set:
children:
- pathElement:
fieldName: matchLabels
set:
members:
- fieldName: app
- pathElement:
fieldName: template
set:
children:
- pathElement:
fieldName: metadata
set:
children:
- pathElement:
fieldName: labels
set:
members:
- fieldName: app
- pathElement:
fieldName: spec
set:
children:
- pathElement:
fieldName: containers
set:
children:
- pathElement:
key:
- name: name
value:
stringValue: nginx
'null': false
set:
members:
- fieldName: image
- fieldName: name
children:
- pathElement:
fieldName: ports
set:
children:
- pathElement:
index: 0
set:
members:
- fieldName: containerPort`),
}, {
yaml: []byte(`foo:
apiVersion: v1
fields:
members:
- fieldName: allowVolumeExpansion
- fieldName: apiVersion
- fieldName: kind
- fieldName: provisioner
children:
- pathElement:
fieldName: metadata
set:
members:
- fieldName: name
- pathElement:
fieldName: parameters
set:
members:
- fieldName: resturl
- fieldName: restuser
- fieldName: secretName
- fieldName: secretNamespace`),
}, {
yaml: []byte(`foo:
apiVersion: v1
fields:
members:
- fieldName: apiVersion
- fieldName: kind
children:
- pathElement:
fieldName: metadata
set:
members:
- fieldName: name
- pathElement:
fieldName: spec
set:
members:
- fieldName: group
- fieldName: scope
children:
- pathElement:
fieldName: names
set:
members:
- fieldName: kind
- fieldName: plural
- fieldName: singular
children:
- pathElement:
fieldName: shortNames
set:
members:
- index: 0
- pathElement:
fieldName: versions
set:
children:
- pathElement:
key:
- name: name
value:
stringValue: v1
'null': false
set:
members:
- fieldName: name
- fieldName: served
- fieldName: storage`),
},
}
for i, tc := range tests {
var original map[string]metav1.VersionedFieldSet
if err := yaml.Unmarshal(tc.yaml, &original); err != nil {
t.Errorf("[%v]did not expect yaml unmarshalling error but got: %v", i, err)
continue
}
decoded, err := DecodeManagedFields(original)
if err == nil && len(tc.errString) > 0 {
t.Errorf("[%v]expected error but got none.", i)
continue
}
if err != nil && len(tc.errString) == 0 {
t.Errorf("[%v]did not expect error but got: %v", i, err)
continue
}
if err != nil && len(tc.errString) > 0 && !strings.Contains(err.Error(), tc.errString) {
t.Errorf("[%v]expected error with %q but got: %v", i, tc.errString, err)
continue
}
encoded, err := EncodeManagedFields(decoded)
if err != nil {
t.Errorf("[%v]did not expect round trip error but got: %v", i, err)
continue
}
if !reflect.DeepEqual(encoded, original) {
t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, original, encoded)
}
}
}