-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Drop unrecognized fields before update
Add a nested store to the proxy store to strip non-Kubernetes fields from the object being updated. The steve formatter and proxy store adds fields to objects when it outputs them to the client, for usability by the UI. It adds the object's fields[1], relationships to other objects[2], a summary of the object's state[3], and additional information in the conditions[4]. These fields are not native to Kubernetes, so when a client submits the object back as an update, Kubernetes reports a warning that they are unrecognized. This change ensures the extra fields are removed before submitting the update. [1] https://github.com/rancher/steve/blob/bf2e9655f5dde8f55b23f67e64f0186fc68789d7/pkg/stores/proxy/proxy_store.go#L189 [2] https://github.com/rancher/steve/blob/bf2e9655f5dde8f55b23f67e64f0186fc68789d7/pkg/resources/common/formatter.go#L106 [3] https://github.com/rancher/steve/blob/bf2e9655f5dde8f55b23f67e64f0186fc68789d7/pkg/resources/common/formatter.go#L100 [4] https://github.com/rancher/steve/blob/bf2e9655f5dde8f55b23f67e64f0186fc68789d7/pkg/resources/common/formatter.go#L108
- Loading branch information
Showing
3 changed files
with
189 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
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 { | ||
t.Run(test.name, func(t *testing.T) { | ||
got := unformat(test.obj) | ||
assert.Equal(t, test.want, got) | ||
}) | ||
} | ||
} |