Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop unrecognized fields before update #112

Merged
merged 1 commit into from
Jul 17, 2023

Conversation

cmurphy
Copy link
Contributor

@cmurphy cmurphy commented Jun 16, 2023

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.

The store drops the entire status field before updating. Steve stores only support client-go's Update and not UpdateStatus[5], so updating the status has no effect anyway.
[1]

data.PutValue(object, cells, "metadata", "fields")

[2]
data.PutValue(unstr.Object, rel, "metadata", "relationships")

[3]
data.PutValue(unstr.Object, map[string]interface{}{

[4]
summary.NormalizeConditions(unstr)

[5] https://pkg.go.dev/k8s.io/client-go/dynamic#ResourceInterface

rancher/rancher#41772

@cmurphy cmurphy requested review from KevinJoiner and a team June 16, 2023 21:45
data.RemoveValue(unst, "metadata", "fields")
data.RemoveValue(unst, "metadata", "relationships")
data.RemoveValue(unst, "metadata", "state")
data.RemoveValue(unst, "status") // client-go UpdateStatus is not supported
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the impression that the Update call from ResourceInterface includes the Status since it is a part of the object. And the UpdateStatus call is used to update the status only.

If this is not true and Update calls to K8s will not update the status even if it differs from the stored object then this should be dropped.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's the case. Here's what the docs say:

PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza.

Here's a brief reproducer showing this: https://gist.github.com/cmurphy/1512fa9482f8eba5cfed352f5d809894

Even though the status isn't updated, the API server still validates its fields, which is why we see the warnings about .status.conditions in the response in steve. So the status object still needs to be valid even though it's not being used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the example! It was easy to run and worked exactly as you said. I didn't know about that behavior before this. Though I did notice that the behavior depends on status being defined as a subresource in the CRD definition. If I removed the status subresource line in crds.yaml, then changes to that status field persisted which concerns me because some of our resources such as v3.User do not have status defined as a subresource`

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  creationTimestamp: "2023-06-16T14:15:37Z"
  generation: 1
  name: users.management.cattle.io
  resourceVersion: "743"
  uid: 06aa895c-bfbf-4f71-8a4f-43e31cd2bfcd
spec:
  conversion:
    strategy: None
  group: management.cattle.io
  names:
    kind: User
    listKind: UserList
    plural: users
    singular: user
  scope: Cluster
  versions:
  - name: v3
    schema:
      openAPIV3Schema:
        type: object
        x-kubernetes-preserve-unknown-fields: true
    served: true
    storage: true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that's unfortunate that our CRDs don't conform to k8s standards. I'll rework this to drop the individual condition fields in the condition slice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may be able to fix most of these with our crd generation changes.

@cmurphy cmurphy requested review from KevinJoiner and a team June 30, 2023 20:25
@cmurphy cmurphy force-pushed the unformatter-store branch 2 times, most recently from b79be6f to aca26c2 Compare July 5, 2023 20:56
@cmurphy cmurphy requested a review from maxsokolovsky July 7, 2023 15:31
)

func Test_unformat(t *testing.T) {
tests := []struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's always good to start a test with t.Parallel() and also run subtests in parallel, unless there is global state.

for _, test := range tests {
	test := test
	t.Run(test.name, func(t *testing.T) {
		t.Parallel()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the test itself can also be run in parallel:

Suggested change
tests := []struct {
func TestUnformat(t *testing.T) {
t.Parallel()

"github.com/stretchr/testify/assert"
)

func Test_unformat(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better if this tested the exported method Update on the type instead?
unformat is just an implementation detail and might change, while we want to be certain that the public interface works as expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case I don't find a lot of value in it. I would have to implement a mock store to hold the underlying types.Store, so it would be adding a lot of boilerplate to test the entire .Update() flow, when all that really matters is this one function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I just thought there might already be a simple mock store for testing...

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
@cmurphy cmurphy force-pushed the unformatter-store branch from aca26c2 to adaa391 Compare July 14, 2023 21:54
@cmurphy cmurphy merged commit d040cff into rancher:master Jul 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants