Change:
What happened: In I introduced an optimization - do a deep copy instead of to and from JSON roundtrip to convert anything that implements . I then discovered that the method that is used there in both and may mutate the original object. This is problematic because previously (before ) there was no mutation and because this is unexpected and may lead to data races - it is bad behaviour to mutate original object when you just want a copy of it. This issue was fixed by . However the underlying issue is not fixed here - is brittle and dangerous. Method name does not imply that it mutates data when you call it. And godoc does not mention that either: Something needs to be done about it IMO. This method is misused in these places (mutation side effect is not intended): Also implementation in does not implement the behaviour required by godoc in . What you expected to happen: There should be a method that returns the current unstructured contents without mutation and perhaps not providing the write-through effect. /kind bug /sig api-machinery
/cc
Is this still required and could I work on it? I'm looking for something to work on as a new contributor :)
Yes and yes! :)
Perfect, I'll get started on it!
I've got out. I'd appreciate a review :)
Issues go stale after 90d of inactivity. Mark the issue as fresh with . Stale issues rot after an additional 30d of inactivity and eventually close. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle stale
/remove-lifecycle stale

Discussion:
srcs = [ "helpers_test.go", "unstructured_list_test.go", <ins> "unstructured_test.go", </ins> ], embed = [":go_default_library"], deps = [

} // UnstructuredContent returns a map contain an overlay of the Items field onto <del> // the Object field. Items always overwrites overlay. Changing "items" in the // returned object will affect items in the underlying Items field, but changing // the "items" slice itself will have no effect. // TODO: expose SetUnstructuredContent on runtime.Unstructured that allows // items to be changed. </del> <ins> // the Object field. Items always overwrites overlay. </ins> func (u *UnstructuredList) UnstructuredContent() map[string]interface{} { <del> out := u.Object if out == nil { out = make(map[string]interface{}) </del> <ins> out := make(map[string]interface{}, len(u.Object)+1) // shallow copy every property for k, v := range u.Object { out[k] = v </ins> } items := make([]interface{}, len(u.Items)) for i, item := range u.Items { <del> items[i] = item.Object </del> <ins> items[i] = item.UnstructuredContent() </ins> } out["items"] = items return out

<ins> /* 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 unstructured import ( "testing" "github.com/stretchr/testify/assert" ) func TestNilUnstructuredContent(t *testing.T) { var u Unstructured uCopy := u.DeepCopy() content := u.UnstructuredContent() expContent := make(map[string]interface{}) assert.EqualValues(t, expContent, content) assert.Equal(t, uCopy, &u) } </ins>

var u map[string]interface{} var err error if unstr, ok := obj.(Unstructured); ok { <del> // UnstructuredContent() mutates the object so we need to make a copy first u = unstr.DeepCopyObject().(Unstructured).UnstructuredContent() </del> <ins> u = unstr.UnstructuredContent() </ins> } else { t := reflect.TypeOf(obj) value := reflect.ValueOf(obj)

func DeepCopyJSONValue(x interface{}) interface{} { switch x := x.(type) { case map[string]interface{}: <ins> if x == nil { // Typed nil - an interface{} that contains a type map[string]interface{} with a value of nil return x } </ins> clone := make(map[string]interface{}, len(x)) for k, v := range x { clone[k] = DeepCopyJSONValue(v) } return clone case []interface{}: <ins> if x == nil { // Typed nil - an interface{} that contains a type []interface{} with a value of nil return x } </ins> clone := make([]interface{}, len(x)) for i, v := range x { clone[i] = DeepCopyJSONValue(v)

// to JSON allowed. type Unstructured interface { Object <del> // UnstructuredContent returns a non-nil, mutable map of the contents of this object. Values may be </del> <ins> // UnstructuredContent returns a non-nil map with this object's contents. Values may be </ins> // []interface{}, map[string]interface{}, or any primitive type. Contents are typically serialized to <del> // and from JSON. </del> <ins> // and from JSON. SetUnstructuredContent should be used to mutate the contents. </ins> UnstructuredContent() map[string]interface{} // SetUnstructuredContent updates the object content to match the provided map. SetUnstructuredContent(map[string]interface{})

func (obj *Unstructured) UnstructuredContent() map[string]interface{} { if obj.Object == nil { <del> obj.Object = make(map[string]interface{}) </del> <ins> return make(map[string]interface{}) </ins> } return obj.Object }