diff --git a/pkg/config/dynamic/plugins.go b/pkg/config/dynamic/plugins.go index a44236695..6df68b242 100644 --- a/pkg/config/dynamic/plugins.go +++ b/pkg/config/dynamic/plugins.go @@ -1,22 +1,26 @@ package dynamic -import "k8s.io/apimachinery/pkg/runtime" +import ( + "encoding/json" + "fmt" + "reflect" +) // +k8s:deepcopy-gen=false // PluginConf holds the plugin configuration. -type PluginConf map[string]interface{} +type PluginConf map[string]any -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +// DeepCopyInto is a deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PluginConf) DeepCopyInto(out *PluginConf) { if in == nil { *out = nil } else { - *out = runtime.DeepCopyJSON(*in) + *out = deepCopyJSON(*in) } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginConf. +// DeepCopy is a deepcopy function, copying the receiver, creating a new PluginConf. func (in *PluginConf) DeepCopy() *PluginConf { if in == nil { return nil @@ -25,3 +29,49 @@ func (in *PluginConf) DeepCopy() *PluginConf { in.DeepCopyInto(out) return out } + +// inspired by https://github.com/kubernetes/apimachinery/blob/53ecdf01b997ca93c7db7615dfe7b27ad8391983/pkg/runtime/converter.go#L607 +func deepCopyJSON(x map[string]any) map[string]any { + return deepCopyJSONValue(x).(map[string]any) +} + +func deepCopyJSONValue(x any) any { + switch x := x.(type) { + case map[string]any: + if x == nil { + // Typed nil - an any that contains a type map[string]any with a value of nil + return x + } + clone := make(map[string]any, len(x)) + for k, v := range x { + clone[k] = deepCopyJSONValue(v) + } + return clone + case []any: + if x == nil { + // Typed nil - an any that contains a type []any with a value of nil + return x + } + clone := make([]any, len(x)) + for i, v := range x { + clone[i] = deepCopyJSONValue(v) + } + return clone + case string, int64, bool, float64, nil, json.Number: + return x + default: + v := reflect.ValueOf(x) + + if v.NumMethod() == 0 { + panic(fmt.Errorf("cannot deep copy %T", x)) + } + + method := v.MethodByName("DeepCopy") + if method.Kind() == reflect.Invalid { + panic(fmt.Errorf("cannot deep copy %T", x)) + } + + call := method.Call(nil) + return call[0].Interface() + } +} diff --git a/pkg/config/dynamic/plugins_test.go b/pkg/config/dynamic/plugins_test.go new file mode 100644 index 000000000..6021362dd --- /dev/null +++ b/pkg/config/dynamic/plugins_test.go @@ -0,0 +1,75 @@ +package dynamic + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type FakeConfig struct { + Name string `json:"name"` +} + +// DeepCopyInto is a deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FakeConfig) DeepCopyInto(out *FakeConfig) { + *out = *in +} + +// DeepCopy is a deepcopy function, copying the receiver, creating a new AddPrefix. +func (in *FakeConfig) DeepCopy() *FakeConfig { + if in == nil { + return nil + } + out := new(FakeConfig) + in.DeepCopyInto(out) + return out +} + +type Foo struct { + Name string +} + +func TestPluginConf_DeepCopy_mapOfStruct(t *testing.T) { + f := &FakeConfig{Name: "bir"} + p := PluginConf{ + "fii": f, + } + + clone := p.DeepCopy() + assert.Equal(t, &p, clone) + + f.Name = "bur" + + assert.NotEqual(t, &p, clone) +} + +func TestPluginConf_DeepCopy_map(t *testing.T) { + m := map[string]interface{}{ + "name": "bar", + } + p := PluginConf{ + "config": map[string]interface{}{ + "foo": m, + }, + } + + clone := p.DeepCopy() + assert.Equal(t, &p, clone) + + p["one"] = "a" + m["two"] = "b" + + assert.NotEqual(t, &p, clone) +} + +func TestPluginConf_DeepCopy_panic(t *testing.T) { + p := &PluginConf{ + "config": map[string]interface{}{ + "foo": &Foo{Name: "gigi"}, + }, + } + + assert.Panics(t, func() { + p.DeepCopy() + }) +}