diff --git a/pkg/stores/proxy/proxy_store.go b/pkg/stores/proxy/proxy_store.go index 52b0cc76..fb01dd81 100644 --- a/pkg/stores/proxy/proxy_store.go +++ b/pkg/stores/proxy/proxy_store.go @@ -88,18 +88,20 @@ type Store struct { // NewProxyStore returns a wrapped types.Store. func NewProxyStore(clientGetter ClientGetter, notifier RelationshipNotifier, lookup accesscontrol.AccessSetLookup, namespaceCache corecontrollers.NamespaceCache) types.Store { return &errorStore{ - Store: &WatchRefresh{ - Store: partition.NewStore( - &rbacPartitioner{ - proxyStore: &Store{ - clientGetter: clientGetter, - notifier: notifier, + Store: &unformatterStore{ + Store: &WatchRefresh{ + Store: partition.NewStore( + &rbacPartitioner{ + proxyStore: &Store{ + clientGetter: clientGetter, + notifier: notifier, + }, }, - }, - lookup, - namespaceCache, - ), - asl: lookup, + lookup, + namespaceCache, + ), + asl: lookup, + }, }, } } diff --git a/pkg/stores/proxy/unformatter.go b/pkg/stores/proxy/unformatter.go new file mode 100644 index 00000000..eb70aae9 --- /dev/null +++ b/pkg/stores/proxy/unformatter.go @@ -0,0 +1,66 @@ +package proxy + +import ( + "github.com/rancher/apiserver/pkg/types" + "github.com/rancher/wrangler/pkg/data" + "github.com/rancher/wrangler/pkg/data/convert" +) + +// unformatterStore removes fields added by the formatter that kubernetes cannot recognize. +type unformatterStore struct { + types.Store +} + +// ByID looks up a single object by its ID. +func (u *unformatterStore) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { + return u.Store.ByID(apiOp, schema, id) +} + +// List returns a list of resources. +func (u *unformatterStore) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) { + return u.Store.List(apiOp, schema) +} + +// Create creates a single object in the store. +func (u *unformatterStore) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (types.APIObject, error) { + return u.Store.Create(apiOp, schema, data) +} + +// Update updates a single object in the store. +func (u *unformatterStore) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (types.APIObject, error) { + data = unformat(data) + return u.Store.Update(apiOp, schema, data, id) +} + +// Delete deletes an object from a store. +func (u *unformatterStore) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { + return u.Store.Delete(apiOp, schema, id) + +} + +// Watch returns a channel of events for a list or resource. +func (u *unformatterStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types.WatchRequest) (chan types.APIEvent, error) { + return u.Store.Watch(apiOp, schema, wr) +} + +func unformat(obj types.APIObject) types.APIObject { + unst, ok := obj.Object.(map[string]interface{}) + if !ok { + return obj + } + data.RemoveValue(unst, "metadata", "fields") + data.RemoveValue(unst, "metadata", "relationships") + data.RemoveValue(unst, "metadata", "state") + conditions, ok := data.GetValue(unst, "status", "conditions") + if ok { + conditionsSlice := convert.ToMapSlice(conditions) + for i := range conditionsSlice { + data.RemoveValue(conditionsSlice[i], "error") + data.RemoveValue(conditionsSlice[i], "transitioning") + data.RemoveValue(conditionsSlice[i], "lastUpdateTime") + } + data.PutValue(unst, conditionsSlice, "status", "conditions") + } + obj.Object = unst + return obj +} diff --git a/pkg/stores/proxy/unformatter_test.go b/pkg/stores/proxy/unformatter_test.go new file mode 100644 index 00000000..da559a1e --- /dev/null +++ b/pkg/stores/proxy/unformatter_test.go @@ -0,0 +1,112 @@ +package proxy + +import ( + "testing" + + "github.com/rancher/apiserver/pkg/types" + "github.com/stretchr/testify/assert" +) + +func Test_unformat(t *testing.T) { + tests := []struct { + name string + obj types.APIObject + want types.APIObject + }{ + { + name: "noop", + obj: types.APIObject{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "noop", + }, + }, + }, + want: types.APIObject{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "noop", + }, + }, + }, + }, + { + name: "remove fields", + obj: types.APIObject{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "foo", + "fields": []string{ + "name", + "address", + "phonenumber", + }, + "relationships": []map[string]interface{}{ + { + "toId": "bar", + "rel": "uses", + }, + }, + "state": map[string]interface{}{ + "error": false, + }, + }, + "status": map[string]interface{}{ + "conditions": []map[string]interface{}{ + { + "type": "Ready", + "status": "True", + "lastUpdateTime": "a minute ago", + "transitioning": false, + "error": false, + }, + { + "type": "Initialized", + "status": "True", + "lastUpdateTime": "yesterday", + "transitioning": false, + "error": false, + }, + }, + }, + }, + }, + want: types.APIObject{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "foo", + }, + "status": map[string]interface{}{ + "conditions": []map[string]interface{}{ + { + "type": "Ready", + "status": "True", + }, + { + "type": "Initialized", + "status": "True", + }, + }, + }, + }, + }, + }, + { + name: "unrecognized object", + obj: types.APIObject{ + Object: "object", + }, + want: types.APIObject{ + Object: "object", + }, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + got := unformat(test.obj) + assert.Equal(t, test.want, got) + }) + } +}