K8s/Unstructured: Avoid panic in DeepCopy (#99840)

This commit is contained in:
Ryan McKinley
2025-01-31 01:19:09 +03:00
committed by GitHub
parent 8ce8c1635f
commit e3d9b6cadf
2 changed files with 97 additions and 1 deletions

View File

@@ -2,6 +2,7 @@ package v0alpha1
import (
"encoding/json"
"reflect"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
runtime "k8s.io/apimachinery/pkg/runtime"
@@ -63,7 +64,7 @@ func (u *Unstructured) DeepCopy() *Unstructured {
}
out := new(Unstructured)
*out = *u
out.Object = runtime.DeepCopyJSON(u.Object)
out.Object = deepCopyJSONValue(u.Object).(map[string]interface{})
return out
}
@@ -72,6 +73,57 @@ func (u *Unstructured) DeepCopyInto(out *Unstructured) {
*out = *clone
}
// Copied from:
//
// runtime.DeepCopyJSON(u.Object)
//
// BUT this avoids panic on int
func deepCopyJSONValue(x interface{}) interface{} {
switch x := x.(type) {
case map[string]interface{}:
if x == nil {
// Typed nil - an interface{} that contains a type map[string]interface{} with a value of nil
return x
}
clone := make(map[string]interface{}, len(x))
for k, v := range x {
clone[k] = deepCopyJSONValue(v)
}
return clone
case []interface{}:
if x == nil {
// Typed nil - an interface{} that contains a type []interface{} with a value of nil
return x
}
clone := make([]interface{}, len(x))
for i, v := range x {
clone[i] = deepCopyJSONValue(v)
}
return clone
case string, int64, bool, float64, nil, json.Number:
return x
// Keep more numbers
case int, int8, int16, int32, float32, uint, uint16, uint32, uint64, uint8:
return x
case runtime.Object:
return x.DeepCopyObject()
default:
// fallback to reflection
val := reflect.ValueOf(x).Elem()
cpy := reflect.New(val.Type())
cpy.Elem().Set(val)
// Using the <obj>, <ok> for the type conversion ensures that it doesn't panic if it can't be converted
if obj, ok := cpy.Interface().(runtime.Object); ok {
return obj
}
return x
}
}
func (u *Unstructured) Set(field string, value interface{}) {
if u.Object == nil {
u.Object = make(map[string]interface{})

View File

@@ -0,0 +1,44 @@
package v0alpha1
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestDeepCopyJSON(t *testing.T) {
obj := &Unstructured{
Object: map[string]interface{}{
"int": int(2),
"int16": int16(2),
"int32": int32(2),
"uint64": uint64(2),
"ref": &ObjectReference{
Resource: "x",
},
"array": []any{
int(1), int64(2), "hello",
},
"string": "hello",
"bool": true,
"map": map[string]any{
"x": &ObjectReference{
Resource: "x",
},
},
"object": &v1.APIGroup{
Name: "HELLO",
},
},
}
before, err := json.Marshal(obj)
require.NoError(t, err)
clone := obj.DeepCopy()
after, err := json.Marshal(clone)
require.NoError(t, err)
require.JSONEq(t, string(before), string(after))
}