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

skip_serializing_if on omitempty container types makes PUT operations not update set fields back to empty #103

Closed
cassandracomar opened this issue Jun 25, 2021 · 11 comments

Comments

@cassandracomar
Copy link

noticed this with Container::command in an integration test that sets command: ["false"] to force a Pod down then attempts to restore command back to the default by unsetting it. as serialization skips the updated command, the PUT operation does not update command back to the empty list.

I believe this affects all fields updated by #95.

in order to allow fields to be restored to default, we either need to emit the empty list or emit an explicit null. for omitempty container types, these should be equivalent. I've asked on the k8s slack about this to determine if this is expected behavior from upstream.

@Arnavion
Copy link
Owner

That's unfortunate. It's the kind of thing I was worried about when I was making that change.

/cc @clux @MikailBag

@clux
Copy link

clux commented Jun 25, 2021

Oof. That's not good :(

(after some double checking: command serialization differences in 0.12 and 0.11)

So we were emitting nulls before because of option wrapping, and now we are only emitting vectors if the vector is non-empty.

Not sure if there are any feasible fixes here except reverting option unpacking? I assume we cannot simply just emit the empty vector in the omitempty cases, because we don't have access to that annotation, and it sounds possibly scarier to emit it in the blanket case.

@Arnavion
Copy link
Owner

Will the inverse work? Always emit empty maps as {} and empty arrays as [] ?

@cassandracomar
Copy link
Author

cassandracomar commented Jun 25, 2021 via email

@Arnavion
Copy link
Owner

Actually, won't it cause a problem for this kind of usage?

struct Foo {
    bar: Vec<Bar>,
    baz: String,
}

// Goal: set foo.baz without affecting foo.bar
let foo = { baz: "newbaz", ..Foo::default() };
replace_namespaced_foo(&foo);

This would've worked before #95, and still works after #95, because in both cases the request body doesn't contain the "bar" field. But if we emit the default empty Vec as [], then foo in the cluster will end up having an empty bar

@cassandracomar
Copy link
Author

cassandracomar commented Jun 25, 2021 via email

@clux
Copy link

clux commented Jun 26, 2021

Yeah, that is a PATCH use case, but you wouldn't be able to run a patch by using Foo's own serialization if the struct requires bar. You'd have to use serde_json::json!{"baz": "newbaz"} as the patch body, and rely on the fact that bar is actually optional on the server side.

Not sure that's a problem though. If you have the full struct Foo and you are doing a full replace, then you should have all the values, and patch with the full struct should probably be interpreted as: change all values.

A lot of the "change parts of the spec" type patch operations need to use json! or json_patch already.

@Arnavion
Copy link
Owner

Okay, I thought replace_ (PUT) allows you to specify a subset of the fields, but yes I was misremembering.


Unfortunately, always emitting [] / {} breaks at least the CRD test, specifically creating a CRD spec with a CustomResourceValidation:

let (request, response_body) =
apiextensions::CustomResourceDefinition::create_custom_resource_definition(&custom_resource_definition, Default::default())
.expect("couldn't create custom resource definition");
let response = client.execute(request);

... because it ends up serializing the JSONSchemaProps::dependencies map as {}, which the API server rejects:

only [Description Type Format Title Maximum ExclusiveMaximum Minimum ExclusiveMinimum MaxLength MinLength Pattern MaxItems MinItems UniqueItems MultipleOf Required Items Properties ExternalDocs Example XPreserveUnknownFields] fields are allowed at the root of the schema if the status subresource is enabled, spec.validation.openAPIV3Schema.dependencies: Forbidden: dependencies is not supported, spec.validation.openAPIV3Schema.properties[spec].dependencies: Forbidden: dependencies is not supported, spec.validation.openAPIV3Schema.properties[spec].properties[prop3].dependencies: Forbidden: dependencies is not supported, spec.validation.openAPIV3Schema.properties[spec].properties[prop1].dependencies: Forbidden: dependencies is not supported, spec.validation.openAPIV3Schema.properties[spec].properties[prop2].dependencies: Forbidden: dependencies is not supported, spec.validation.openAPIV3Schema.properties[spec].properties[prop2].items.dependencies: Forbidden: dependencies is not supported

Indeed, the API server always rejects the spec if this field is set: https://github.com/kubernetes/apiextensions-apiserver/blob/92e6c85f057fe3cc1c2d6ce0ef0b43c9b6b0c142/pkg/apis/apiextensions/validation/validation.go#L1037

@cassandracomar
Copy link
Author

cassandracomar commented Jun 26, 2021 via email

@Arnavion
Copy link
Owner

So there's no way other than reverting to the pre-#95 behavior?

@clux
Copy link

clux commented Jul 26, 2021

I can't think of any way to do it. The annotations that would let us do this safely (for a subset of vecs and btreemaps) are in the api repo and not in the schema. Unless we did some kind of first-pass of the api repo to create a file of specific whitelist paths or something crazy.

I think it's probably smarter to revert it for now until/if we can come up with a way to partially enable it.

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

No branches or pull requests

3 participants