Skip to content

Commit a88dfdd

Browse files
Merge pull request #56563 from nikhita/unstructured-error-handling
Automatic merge from submit-queue (batch tested with PRs 56390, 56334, 55572, 55598, 56563). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. apimachinery: improve error handling for unstructured helpers Improve error handling for unstructured helpers to give more information - if the field is missing or a wrong type exists. (taken from kubernetes/kubernetes#55168) **Release note**: ```release-note NONE ``` /assign sttts ash2k Kubernetes-commit: 107375848540e2fadf80b96b408a7f04a412a890
2 parents 23bc0b9 + 44c652b commit a88dfdd

File tree

3 files changed

+113
-96
lines changed

3 files changed

+113
-96
lines changed

pkg/apis/meta/v1/unstructured/helpers.go

Lines changed: 99 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -31,147 +31,163 @@ import (
3131
)
3232

3333
// NestedFieldCopy returns a deep copy of the value of a nested field.
34-
// false is returned if the value is missing.
35-
// nil, true is returned for a nil field.
36-
func NestedFieldCopy(obj map[string]interface{}, fields ...string) (interface{}, bool) {
37-
val, ok := nestedFieldNoCopy(obj, fields...)
38-
if !ok {
39-
return nil, false
40-
}
41-
return runtime.DeepCopyJSONValue(val), true
34+
// Returns false if the value is missing.
35+
// No error is returned for a nil field.
36+
func NestedFieldCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) {
37+
val, found, err := nestedFieldNoCopy(obj, fields...)
38+
if !found || err != nil {
39+
return nil, found, err
40+
}
41+
return runtime.DeepCopyJSONValue(val), true, nil
4242
}
4343

44-
func nestedFieldNoCopy(obj map[string]interface{}, fields ...string) (interface{}, bool) {
44+
func nestedFieldNoCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) {
4545
var val interface{} = obj
4646
for _, field := range fields {
4747
if m, ok := val.(map[string]interface{}); ok {
4848
val, ok = m[field]
4949
if !ok {
50-
return nil, false
50+
return nil, false, nil
5151
}
5252
} else {
53-
// Expected map[string]interface{}, got something else
54-
return nil, false
53+
return nil, false, fmt.Errorf("%v is of the type %T, expected map[string]interface{}", val, val)
5554
}
5655
}
57-
return val, true
56+
return val, true, nil
5857
}
5958

6059
// NestedString returns the string value of a nested field.
61-
// Returns false if value is not found or is not a string.
62-
func NestedString(obj map[string]interface{}, fields ...string) (string, bool) {
63-
val, ok := nestedFieldNoCopy(obj, fields...)
64-
if !ok {
65-
return "", false
60+
// Returns false if value is not found and an error if not a string.
61+
func NestedString(obj map[string]interface{}, fields ...string) (string, bool, error) {
62+
val, found, err := nestedFieldNoCopy(obj, fields...)
63+
if !found || err != nil {
64+
return "", found, err
6665
}
6766
s, ok := val.(string)
68-
return s, ok
67+
if !ok {
68+
return "", false, fmt.Errorf("%v is of the type %T, expected string", val, val)
69+
}
70+
return s, true, nil
6971
}
7072

7173
// NestedBool returns the bool value of a nested field.
72-
// Returns false if value is not found or is not a bool.
73-
func NestedBool(obj map[string]interface{}, fields ...string) (bool, bool) {
74-
val, ok := nestedFieldNoCopy(obj, fields...)
75-
if !ok {
76-
return false, false
74+
// Returns false if value is not found and an error if not a bool.
75+
func NestedBool(obj map[string]interface{}, fields ...string) (bool, bool, error) {
76+
val, found, err := nestedFieldNoCopy(obj, fields...)
77+
if !found || err != nil {
78+
return false, found, err
7779
}
7880
b, ok := val.(bool)
79-
return b, ok
81+
if !ok {
82+
return false, false, fmt.Errorf("%v is of the type %T, expected bool", val, val)
83+
}
84+
return b, true, nil
8085
}
8186

82-
// NestedFloat64 returns the bool value of a nested field.
83-
// Returns false if value is not found or is not a float64.
84-
func NestedFloat64(obj map[string]interface{}, fields ...string) (float64, bool) {
85-
val, ok := nestedFieldNoCopy(obj, fields...)
86-
if !ok {
87-
return 0, false
87+
// NestedFloat64 returns the float64 value of a nested field.
88+
// Returns false if value is not found and an error if not a float64.
89+
func NestedFloat64(obj map[string]interface{}, fields ...string) (float64, bool, error) {
90+
val, found, err := nestedFieldNoCopy(obj, fields...)
91+
if !found || err != nil {
92+
return 0, found, err
8893
}
8994
f, ok := val.(float64)
90-
return f, ok
95+
if !ok {
96+
return 0, false, fmt.Errorf("%v is of the type %T, expected float64", val, val)
97+
}
98+
return f, true, nil
9199
}
92100

93101
// NestedInt64 returns the int64 value of a nested field.
94-
// Returns false if value is not found or is not an int64.
95-
func NestedInt64(obj map[string]interface{}, fields ...string) (int64, bool) {
96-
val, ok := nestedFieldNoCopy(obj, fields...)
97-
if !ok {
98-
return 0, false
102+
// Returns false if value is not found and an error if not an int64.
103+
func NestedInt64(obj map[string]interface{}, fields ...string) (int64, bool, error) {
104+
val, found, err := nestedFieldNoCopy(obj, fields...)
105+
if !found || err != nil {
106+
return 0, found, err
99107
}
100108
i, ok := val.(int64)
101-
return i, ok
109+
if !ok {
110+
return 0, false, fmt.Errorf("%v is of the type %T, expected int64", val, val)
111+
}
112+
return i, true, nil
102113
}
103114

104115
// NestedStringSlice returns a copy of []string value of a nested field.
105-
// Returns false if value is not found, is not a []interface{} or contains non-string items in the slice.
106-
func NestedStringSlice(obj map[string]interface{}, fields ...string) ([]string, bool) {
107-
val, ok := nestedFieldNoCopy(obj, fields...)
116+
// Returns false if value is not found and an error if not a []interface{} or contains non-string items in the slice.
117+
func NestedStringSlice(obj map[string]interface{}, fields ...string) ([]string, bool, error) {
118+
val, found, err := nestedFieldNoCopy(obj, fields...)
119+
if !found || err != nil {
120+
return nil, found, err
121+
}
122+
m, ok := val.([]interface{})
108123
if !ok {
109-
return nil, false
124+
return nil, false, fmt.Errorf("%v is of the type %T, expected []interface{}", val, val)
110125
}
111-
if m, ok := val.([]interface{}); ok {
112-
strSlice := make([]string, 0, len(m))
113-
for _, v := range m {
114-
if str, ok := v.(string); ok {
115-
strSlice = append(strSlice, str)
116-
} else {
117-
return nil, false
118-
}
126+
strSlice := make([]string, 0, len(m))
127+
for _, v := range m {
128+
if str, ok := v.(string); ok {
129+
strSlice = append(strSlice, str)
130+
} else {
131+
return nil, false, fmt.Errorf("contains non-string key in the slice: %v is of the type %T, expected string", v, v)
119132
}
120-
return strSlice, true
121133
}
122-
return nil, false
134+
return strSlice, true, nil
123135
}
124136

125137
// NestedSlice returns a deep copy of []interface{} value of a nested field.
126-
// Returns false if value is not found or is not a []interface{}.
127-
func NestedSlice(obj map[string]interface{}, fields ...string) ([]interface{}, bool) {
128-
val, ok := nestedFieldNoCopy(obj, fields...)
129-
if !ok {
130-
return nil, false
138+
// Returns false if value is not found and an error if not a []interface{}.
139+
func NestedSlice(obj map[string]interface{}, fields ...string) ([]interface{}, bool, error) {
140+
val, found, err := nestedFieldNoCopy(obj, fields...)
141+
if !found || err != nil {
142+
return nil, found, err
131143
}
132-
if _, ok := val.([]interface{}); ok {
133-
return runtime.DeepCopyJSONValue(val).([]interface{}), true
144+
_, ok := val.([]interface{})
145+
if !ok {
146+
return nil, false, fmt.Errorf("%v is of the type %T, expected []interface{}", val, val)
134147
}
135-
return nil, false
148+
return runtime.DeepCopyJSONValue(val).([]interface{}), true, nil
136149
}
137150

138151
// NestedStringMap returns a copy of map[string]string value of a nested field.
139-
// Returns false if value is not found, is not a map[string]interface{} or contains non-string values in the map.
140-
func NestedStringMap(obj map[string]interface{}, fields ...string) (map[string]string, bool) {
141-
m, ok := nestedMapNoCopy(obj, fields...)
142-
if !ok {
143-
return nil, false
152+
// Returns false if value is not found and an error if not a map[string]interface{} or contains non-string values in the map.
153+
func NestedStringMap(obj map[string]interface{}, fields ...string) (map[string]string, bool, error) {
154+
m, found, err := nestedMapNoCopy(obj, fields...)
155+
if !found || err != nil {
156+
return nil, found, err
144157
}
145158
strMap := make(map[string]string, len(m))
146159
for k, v := range m {
147160
if str, ok := v.(string); ok {
148161
strMap[k] = str
149162
} else {
150-
return nil, false
163+
return nil, false, fmt.Errorf("contains non-string key in the map: %v is of the type %T, expected string", v, v)
151164
}
152165
}
153-
return strMap, true
166+
return strMap, true, nil
154167
}
155168

156169
// NestedMap returns a deep copy of map[string]interface{} value of a nested field.
157-
// Returns false if value is not found or is not a map[string]interface{}.
158-
func NestedMap(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool) {
159-
m, ok := nestedMapNoCopy(obj, fields...)
160-
if !ok {
161-
return nil, false
170+
// Returns false if value is not found and an error if not a map[string]interface{}.
171+
func NestedMap(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool, error) {
172+
m, found, err := nestedMapNoCopy(obj, fields...)
173+
if !found || err != nil {
174+
return nil, found, err
162175
}
163-
return runtime.DeepCopyJSON(m), true
176+
return runtime.DeepCopyJSON(m), true, nil
164177
}
165178

166179
// nestedMapNoCopy returns a map[string]interface{} value of a nested field.
167-
// Returns false if value is not found or is not a map[string]interface{}.
168-
func nestedMapNoCopy(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool) {
169-
val, ok := nestedFieldNoCopy(obj, fields...)
170-
if !ok {
171-
return nil, false
180+
// Returns false if value is not found and an error if not a map[string]interface{}.
181+
func nestedMapNoCopy(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool, error) {
182+
val, found, err := nestedFieldNoCopy(obj, fields...)
183+
if !found || err != nil {
184+
return nil, found, err
172185
}
173186
m, ok := val.(map[string]interface{})
174-
return m, ok
187+
if !ok {
188+
return nil, false, fmt.Errorf("%v is of the type %T, expected map[string]interface{}", val, val)
189+
}
190+
return m, true, nil
175191
}
176192

177193
// SetNestedField sets the value of a nested field to a deep copy of the value provided.
@@ -245,8 +261,8 @@ func RemoveNestedField(obj map[string]interface{}, fields ...string) {
245261
}
246262

247263
func getNestedString(obj map[string]interface{}, fields ...string) string {
248-
val, ok := NestedString(obj, fields...)
249-
if !ok {
264+
val, found, err := NestedString(obj, fields...)
265+
if !found || err != nil {
250266
return ""
251267
}
252268
return val
@@ -256,11 +272,11 @@ func extractOwnerReference(v map[string]interface{}) metav1.OwnerReference {
256272
// though this field is a *bool, but when decoded from JSON, it's
257273
// unmarshalled as bool.
258274
var controllerPtr *bool
259-
if controller, ok := NestedBool(v, "controller"); ok {
275+
if controller, found, err := NestedBool(v, "controller"); err == nil && found {
260276
controllerPtr = &controller
261277
}
262278
var blockOwnerDeletionPtr *bool
263-
if blockOwnerDeletion, ok := NestedBool(v, "blockOwnerDeletion"); ok {
279+
if blockOwnerDeletion, found, err := NestedBool(v, "blockOwnerDeletion"); err == nil && found {
264280
blockOwnerDeletionPtr = &blockOwnerDeletion
265281
}
266282
return metav1.OwnerReference{

pkg/apis/meta/v1/unstructured/unstructured.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ func (u *Unstructured) setNestedMap(value map[string]string, fields ...string) {
138138
}
139139

140140
func (u *Unstructured) GetOwnerReferences() []metav1.OwnerReference {
141-
field, ok := nestedFieldNoCopy(u.Object, "metadata", "ownerReferences")
142-
if !ok {
141+
field, found, err := nestedFieldNoCopy(u.Object, "metadata", "ownerReferences")
142+
if !found || err != nil {
143143
return nil
144144
}
145145
original, ok := field.([]interface{})
@@ -228,8 +228,8 @@ func (u *Unstructured) SetResourceVersion(version string) {
228228
}
229229

230230
func (u *Unstructured) GetGeneration() int64 {
231-
val, ok := NestedInt64(u.Object, "metadata", "generation")
232-
if !ok {
231+
val, found, err := NestedInt64(u.Object, "metadata", "generation")
232+
if !found || err != nil {
233233
return 0
234234
}
235235
return val
@@ -289,8 +289,8 @@ func (u *Unstructured) SetDeletionTimestamp(timestamp *metav1.Time) {
289289
}
290290

291291
func (u *Unstructured) GetDeletionGracePeriodSeconds() *int64 {
292-
val, ok := NestedInt64(u.Object, "metadata", "deletionGracePeriodSeconds")
293-
if !ok {
292+
val, found, err := NestedInt64(u.Object, "metadata", "deletionGracePeriodSeconds")
293+
if !found || err != nil {
294294
return nil
295295
}
296296
return &val
@@ -305,7 +305,7 @@ func (u *Unstructured) SetDeletionGracePeriodSeconds(deletionGracePeriodSeconds
305305
}
306306

307307
func (u *Unstructured) GetLabels() map[string]string {
308-
m, _ := NestedStringMap(u.Object, "metadata", "labels")
308+
m, _, _ := NestedStringMap(u.Object, "metadata", "labels")
309309
return m
310310
}
311311

@@ -314,7 +314,7 @@ func (u *Unstructured) SetLabels(labels map[string]string) {
314314
}
315315

316316
func (u *Unstructured) GetAnnotations() map[string]string {
317-
m, _ := NestedStringMap(u.Object, "metadata", "annotations")
317+
m, _, _ := NestedStringMap(u.Object, "metadata", "annotations")
318318
return m
319319
}
320320

@@ -337,8 +337,8 @@ func (u *Unstructured) GroupVersionKind() schema.GroupVersionKind {
337337
}
338338

339339
func (u *Unstructured) GetInitializers() *metav1.Initializers {
340-
m, ok := nestedMapNoCopy(u.Object, "metadata", "initializers")
341-
if !ok {
340+
m, found, err := nestedMapNoCopy(u.Object, "metadata", "initializers")
341+
if !found || err != nil {
342342
return nil
343343
}
344344
out := &metav1.Initializers{}
@@ -362,7 +362,7 @@ func (u *Unstructured) SetInitializers(initializers *metav1.Initializers) {
362362
}
363363

364364
func (u *Unstructured) GetFinalizers() []string {
365-
val, _ := NestedStringSlice(u.Object, "metadata", "finalizers")
365+
val, _, _ := NestedStringSlice(u.Object, "metadata", "finalizers")
366366
return val
367367
}
368368

pkg/apis/meta/v1/unstructured/unstructured_list_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ func TestUnstructuredList(t *testing.T) {
3535
content := list.UnstructuredContent()
3636
items := content["items"].([]interface{})
3737
require.Len(t, items, 1)
38-
val, ok := NestedFieldCopy(items[0].(map[string]interface{}), "metadata", "name")
39-
require.True(t, ok)
38+
val, found, err := NestedFieldCopy(items[0].(map[string]interface{}), "metadata", "name")
39+
require.True(t, found)
40+
require.NoError(t, err)
4041
assert.Equal(t, "test", val)
4142
}
4243

0 commit comments

Comments
 (0)