apiextensions: fix concurrent map access copying items' ObjectMeta in Unstructured
The list+get endpoints sets the self-link. If we do not create a (shallow) copy of ObjectMeta this will mutate the cached objects.
This commit is contained in:
		| @@ -23,6 +23,7 @@ import ( | |||||||
|  |  | ||||||
| 	autoscalingv1 "k8s.io/api/autoscaling/v1" | 	autoscalingv1 "k8s.io/api/autoscaling/v1" | ||||||
| 	apierrors "k8s.io/apimachinery/pkg/api/errors" | 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||||
|  | 	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
| @@ -106,6 +107,59 @@ func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, st | |||||||
| // Implement CategoriesProvider | // Implement CategoriesProvider | ||||||
| var _ rest.CategoriesProvider = &REST{} | var _ rest.CategoriesProvider = &REST{} | ||||||
|  |  | ||||||
|  | // List returns a list of items matching labels and field according to the store's PredicateFunc. | ||||||
|  | func (e *REST) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { | ||||||
|  | 	l, err := e.Store.List(ctx, options) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Shallow copy ObjectMeta in returned list for each item. Native types have `Items []Item` fields and therefore | ||||||
|  | 	// implicitly shallow copy ObjectMeta. The generic store sets the self-link for each item. So this is necessary | ||||||
|  | 	// to avoid mutation of the objects from the cache. | ||||||
|  | 	if ul, ok := l.(*unstructured.UnstructuredList); ok { | ||||||
|  | 		for i := range ul.Items { | ||||||
|  | 			shallowCopyObjectMeta(&ul.Items[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return l, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { | ||||||
|  | 	o, err := r.Store.Get(ctx, name, options) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if u, ok := o.(*unstructured.Unstructured); ok { | ||||||
|  | 		shallowCopyObjectMeta(u) | ||||||
|  | 	} | ||||||
|  | 	return o, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func shallowCopyObjectMeta(u runtime.Unstructured) { | ||||||
|  | 	obj := shallowMapDeepCopy(u.UnstructuredContent()) | ||||||
|  | 	if metadata, ok := obj["metadata"]; ok { | ||||||
|  | 		if metadata, ok := metadata.(map[string]interface{}); ok { | ||||||
|  | 			obj["metadata"] = shallowMapDeepCopy(metadata) | ||||||
|  | 			u.SetUnstructuredContent(obj) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func shallowMapDeepCopy(in map[string]interface{}) map[string]interface{} { | ||||||
|  | 	if in == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	out := make(map[string]interface{}, len(in)) | ||||||
|  | 	for k, v := range in { | ||||||
|  | 		out[k] = v | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  |  | ||||||
| // Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of. | // Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of. | ||||||
| func (r *REST) Categories() []string { | func (r *REST) Categories() []string { | ||||||
| 	return r.categories | 	return r.categories | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Dr. Stefan Schimanski
					Dr. Stefan Schimanski