From cd10f28ac19136bb3b532a1aa83644e52f8dd3e9 Mon Sep 17 00:00:00 2001 From: "Dr. Stefan Schimanski" Date: Thu, 14 Jun 2018 12:12:03 +0200 Subject: [PATCH] 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. --- .../pkg/registry/customresource/etcd.go | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go index e82ff07e568..d9e8fd97cbd 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go @@ -23,6 +23,7 @@ import ( autoscalingv1 "k8s.io/api/autoscaling/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -106,6 +107,59 @@ func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, st // Implement CategoriesProvider 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. func (r *REST) Categories() []string { return r.categories