Skip to content

Commit addad99

Browse files
author
jennybuckley
committed
Use raw bytes in metav1.Fields instead of map
Also define custom proto unmarshaller that understands the old format
1 parent b7649db commit addad99

File tree

9 files changed

+142
-139
lines changed

9 files changed

+142
-139
lines changed

staging/src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/compatibility.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,7 @@ func CompatibilityTestFuzzer(scheme *runtime.Scheme, fuzzFuncs []interface{}) *f
232232
field := metav1.ManagedFieldsEntry{}
233233
c.Fuzz(&field)
234234
if field.Fields != nil {
235-
for k1 := range field.Fields.Map {
236-
for k2 := range field.Fields.Map[k1].Map {
237-
field.Fields.Map[k1].Map[k2] = metav1.Fields{}
238-
}
239-
}
235+
field.Fields.Raw = []byte("{}")
240236
}
241237
*f = []metav1.ManagedFieldsEntry{field}
242238
},

staging/src/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,7 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
272272
},
273273
func(j *metav1.ManagedFieldsEntry, c fuzz.Continue) {
274274
c.FuzzNoCustom(j)
275-
if j.Fields != nil && len(j.Fields.Map) == 0 {
276-
j.Fields = nil
277-
}
275+
j.Fields = nil
278276
},
279277
}
280278
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1
18+
19+
import (
20+
"encoding/json"
21+
)
22+
23+
// Fields is declared in types.go
24+
25+
// ProtoFields is a struct that is equivalent to Fields, but intended for
26+
// protobuf marshalling/unmarshalling. It is generated into a serialization
27+
// that matches Fields. Do not use in Go structs.
28+
type ProtoFields struct {
29+
// Map is the representation used in the alpha version of this API
30+
Map map[string]Fields `json:"-" protobuf:"bytes,1,rep,name=map"`
31+
32+
// Raw is the underlying serialization of this object.
33+
Raw []byte `json:"-" protobuf:"bytes,2,opt,name=raw"`
34+
}
35+
36+
// ProtoFields returns the Fields as a new ProtoFields value.
37+
func (m *Fields) ProtoFields() *ProtoFields {
38+
if m == nil {
39+
return &ProtoFields{}
40+
}
41+
return &ProtoFields{
42+
Raw: m.Raw,
43+
}
44+
}
45+
46+
// Size implements the protobuf marshalling interface.
47+
func (m *Fields) Size() (n int) {
48+
return m.ProtoFields().Size()
49+
}
50+
51+
// Unmarshal implements the protobuf marshalling interface.
52+
func (m *Fields) Unmarshal(data []byte) error {
53+
if len(data) == 0 {
54+
return nil
55+
}
56+
p := ProtoFields{}
57+
if err := p.Unmarshal(data); err != nil {
58+
return err
59+
}
60+
if len(p.Map) == 0 {
61+
return json.Unmarshal(p.Raw, &m)
62+
}
63+
b, err := json.Marshal(&p.Map)
64+
if err != nil {
65+
return err
66+
}
67+
return json.Unmarshal(b, &m)
68+
}
69+
70+
// Marshal implements the protobuf marshaling interface.
71+
func (m *Fields) Marshal() (data []byte, err error) {
72+
return m.ProtoFields().Marshal()
73+
}
74+
75+
// MarshalTo implements the protobuf marshaling interface.
76+
func (m *Fields) MarshalTo(data []byte) (int, error) {
77+
return m.ProtoFields().MarshalTo(data)
78+
}
79+
80+
// MarshalToSizedBuffer implements the protobuf reverse marshaling interface.
81+
func (m *Fields) MarshalToSizedBuffer(data []byte) (int, error) {
82+
return m.ProtoFields().MarshalToSizedBuffer(data)
83+
}
84+
85+
// String implements the protobuf goproto_stringer interface.
86+
func (m *Fields) String() string {
87+
return m.ProtoFields().String()
88+
}

staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/helpers.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ limitations under the License.
1717
package v1
1818

1919
import (
20+
"bytes"
2021
"encoding/json"
22+
"errors"
2123
"fmt"
2224

2325
"k8s.io/apimachinery/pkg/fields"
@@ -254,13 +256,24 @@ func ResetObjectMetaForStatus(meta, existingMeta Object) {
254256
}
255257

256258
// MarshalJSON implements json.Marshaler
259+
// MarshalJSON may get called on pointers or values, so implement MarshalJSON on value.
260+
// http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go
257261
func (f Fields) MarshalJSON() ([]byte, error) {
258-
return json.Marshal(&f.Map)
262+
if f.Raw == nil {
263+
return []byte("null"), nil
264+
}
265+
return f.Raw, nil
259266
}
260267

261268
// UnmarshalJSON implements json.Unmarshaler
262269
func (f *Fields) UnmarshalJSON(b []byte) error {
263-
return json.Unmarshal(b, &f.Map)
270+
if f == nil {
271+
return errors.New("metav1.Fields: UnmarshalJSON on nil pointer")
272+
}
273+
if !bytes.Equal(b, []byte("null")) {
274+
f.Raw = append(f.Raw[0:0], b...)
275+
}
276+
return nil
264277
}
265278

266279
var _ json.Marshaler = Fields{}

staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,20 +1109,22 @@ const (
11091109
)
11101110

11111111
// Fields stores a set of fields in a data structure like a Trie.
1112-
// To understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff
1112+
//
1113+
// Each key is either a '.' representing the field itself, and will always map to an empty set,
1114+
// or a string representing a sub-field or item. The string will follow one of these four formats:
1115+
// 'f:<name>', where <name> is the name of a field in a struct, or key in a map
1116+
// 'v:<value>', where <value> is the exact json formatted value of a list item
1117+
// 'i:<index>', where <index> is position of a item in a list
1118+
// 'k:<keys>', where <keys> is a map of a list item's key fields to their unique values
1119+
// If a key maps to an empty Fields value, the field that key represents is part of the set.
1120+
//
1121+
// The exact format is defined in sigs.k8s.io/structured-merge-diff
1122+
// +protobuf.options.marshal=false
1123+
// +protobuf.as=ProtoFields
1124+
// +protobuf.options.(gogoproto.goproto_stringer)=false
11131125
type Fields struct {
1114-
// Map stores a set of fields in a data structure like a Trie.
1115-
//
1116-
// Each key is either a '.' representing the field itself, and will always map to an empty set,
1117-
// or a string representing a sub-field or item. The string will follow one of these four formats:
1118-
// 'f:<name>', where <name> is the name of a field in a struct, or key in a map
1119-
// 'v:<value>', where <value> is the exact json formatted value of a list item
1120-
// 'i:<index>', where <index> is position of a item in a list
1121-
// 'k:<keys>', where <keys> is a map of a list item's key fields to their unique values
1122-
// If a key maps to an empty Fields value, the field that key represents is part of the set.
1123-
//
1124-
// The exact format is defined in k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal
1125-
Map map[string]Fields `json:",inline" protobuf:"bytes,1,rep,name=map"`
1126+
// Raw is the underlying serialization of this object.
1127+
Raw []byte `json:"-" protobuf:"-"`
11261128
}
11271129

11281130
// TODO: Table does not generate to protobuf because of the interface{} - fix protobuf

staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go

Lines changed: 15 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,79 +17,31 @@ limitations under the License.
1717
package internal
1818

1919
import (
20+
"bytes"
21+
2022
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2123

2224
"sigs.k8s.io/structured-merge-diff/fieldpath"
2325
)
2426

25-
func newFields() metav1.Fields {
26-
return metav1.Fields{Map: map[string]metav1.Fields{}}
27-
}
28-
29-
func fieldsSet(f metav1.Fields, path fieldpath.Path, set *fieldpath.Set) error {
30-
if len(f.Map) == 0 {
31-
set.Insert(path)
27+
// EmptyFields represents a set with no paths
28+
// It looks like metav1.Fields{Raw: []byte("{}")}
29+
var EmptyFields metav1.Fields = func() metav1.Fields {
30+
f, err := SetToFields(*fieldpath.NewSet())
31+
if err != nil {
32+
panic("should never happen")
3233
}
33-
for k := range f.Map {
34-
if k == "." {
35-
set.Insert(path)
36-
continue
37-
}
38-
pe, err := NewPathElement(k)
39-
if err != nil {
40-
return err
41-
}
42-
path = append(path, pe)
43-
err = fieldsSet(f.Map[k], path, set)
44-
if err != nil {
45-
return err
46-
}
47-
path = path[:len(path)-1]
48-
}
49-
return nil
50-
}
34+
return f
35+
}()
5136

5237
// FieldsToSet creates a set paths from an input trie of fields
53-
func FieldsToSet(f metav1.Fields) (fieldpath.Set, error) {
54-
set := fieldpath.Set{}
55-
return set, fieldsSet(f, fieldpath.Path{}, &set)
56-
}
57-
58-
func removeUselessDots(f metav1.Fields) metav1.Fields {
59-
if _, ok := f.Map["."]; ok && len(f.Map) == 1 {
60-
delete(f.Map, ".")
61-
return f
62-
}
63-
for k, tf := range f.Map {
64-
f.Map[k] = removeUselessDots(tf)
65-
}
66-
return f
38+
func FieldsToSet(f metav1.Fields) (s fieldpath.Set, err error) {
39+
err = s.FromJSON(bytes.NewReader(f.Raw))
40+
return s, err
6741
}
6842

6943
// SetToFields creates a trie of fields from an input set of paths
70-
func SetToFields(s fieldpath.Set) (metav1.Fields, error) {
71-
var err error
72-
f := newFields()
73-
s.Iterate(func(path fieldpath.Path) {
74-
if err != nil {
75-
return
76-
}
77-
tf := f
78-
for _, pe := range path {
79-
var str string
80-
str, err = PathElementString(pe)
81-
if err != nil {
82-
break
83-
}
84-
if _, ok := tf.Map[str]; ok {
85-
tf = tf.Map[str]
86-
} else {
87-
tf.Map[str] = newFields()
88-
tf = tf.Map[str]
89-
}
90-
}
91-
tf.Map["."] = newFields()
92-
})
93-
f = removeUselessDots(f)
44+
func SetToFields(s fieldpath.Set) (f metav1.Fields, err error) {
45+
f.Raw, err = s.ToJSON()
9446
return f, err
9547
}

staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields_test.go

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,9 @@ import (
3131
func TestFieldsRoundTrip(t *testing.T) {
3232
tests := []metav1.Fields{
3333
{
34-
Map: map[string]metav1.Fields{
35-
"f:metadata": {
36-
Map: map[string]metav1.Fields{
37-
".": newFields(),
38-
"f:name": newFields(),
39-
},
40-
},
41-
},
34+
Raw: []byte(`{"f:metadata":{"f:name":{},".":{}}}`),
4235
},
36+
EmptyFields,
4337
}
4438

4539
for _, test := range tests {
@@ -65,16 +59,9 @@ func TestFieldsToSetError(t *testing.T) {
6559
}{
6660
{
6761
fields: metav1.Fields{
68-
Map: map[string]metav1.Fields{
69-
"k:{invalid json}": {
70-
Map: map[string]metav1.Fields{
71-
".": newFields(),
72-
"f:name": newFields(),
73-
},
74-
},
75-
},
62+
Raw: []byte(`{"k:{invalid json}":{"f:name":{},".":{}}}`),
7663
},
77-
errString: "invalid character",
64+
errString: "ReadObjectCB",
7865
},
7966
}
8067

@@ -97,7 +84,7 @@ func TestSetToFieldsError(t *testing.T) {
9784
}{
9885
{
9986
set: *fieldpath.NewSet(invalidPath),
100-
errString: "Invalid type of path element",
87+
errString: "invalid PathElement",
10188
},
10289
}
10390

staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func BuildManagerIdentifier(encodedManager *metav1.ManagedFieldsEntry) (manager
113113
}
114114

115115
func decodeVersionedSet(encodedVersionedSet *metav1.ManagedFieldsEntry) (versionedSet fieldpath.VersionedSet, err error) {
116-
fields := metav1.Fields{}
116+
fields := EmptyFields
117117
if encodedVersionedSet.Fields != nil {
118118
fields = *encodedVersionedSet.Fields
119119
}

test/integration/apiserver/apply/apply_test.go

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -789,40 +789,7 @@ func TestApplyConvertsManagedFieldsVersion(t *testing.T) {
789789
APIVersion: "apps/v1",
790790
Time: actual.Time,
791791
Fields: &metav1.Fields{
792-
Map: map[string]metav1.Fields{
793-
"f:metadata": {
794-
Map: map[string]metav1.Fields{
795-
"f:labels": {
796-
Map: map[string]metav1.Fields{
797-
"f:sidecar_version": {Map: map[string]metav1.Fields{}},
798-
},
799-
},
800-
},
801-
},
802-
"f:spec": {
803-
Map: map[string]metav1.Fields{
804-
"f:template": {
805-
Map: map[string]metav1.Fields{
806-
"f:spec": {
807-
Map: map[string]metav1.Fields{
808-
"f:containers": {
809-
Map: map[string]metav1.Fields{
810-
"k:{\"name\":\"sidecar\"}": {
811-
Map: map[string]metav1.Fields{
812-
".": {Map: map[string]metav1.Fields{}},
813-
"f:image": {Map: map[string]metav1.Fields{}},
814-
"f:name": {Map: map[string]metav1.Fields{}},
815-
},
816-
},
817-
},
818-
},
819-
},
820-
},
821-
},
822-
},
823-
},
824-
},
825-
},
792+
Raw: []byte(`{"f:metadata":{"f:labels":{"f:sidecar_version":{}}},"f:spec":{"f:template":{"f:spec":{"f:containers":{"k:{\"name\":\"sidecar\"}":{".":{},"f:image":{},"f:name":{}}}}}}}`),
826793
},
827794
}
828795

0 commit comments

Comments
 (0)