buehler / dotnet-operator-sdk

KubeOps is a kubernetes operator sdk in dotnet. Strongly inspired by kubebuilder.
https://buehler.github.io/dotnet-operator-sdk/
Apache License 2.0
233 stars 61 forks source link

feat: Add migration webhooks #137

Closed buehler closed 9 months ago

buehler commented 3 years ago

https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#configure-customresourcedefinition-to-use-conversion-webhooks

buehler commented 3 years ago

https://book.kubebuilder.io/multiversion-tutorial/conversion-concepts.html

anekdoti commented 2 years ago

Hello Christoph, is there currently a schedule to implement conversion webhooks? All the best!

buehler commented 2 years ago

Hey @anekdoti

Currently not. I'm not certain how I'd implement them at the moment. (And I'm on vacation roght now :)

The thing with the webhooks is, that they need one store version and they must convert all other versions from and to that storageversion. Currently O've no idea on how I conceptually implement this.

Regards

anekdoti commented 2 years ago

Hello @buehler, I hope you enjoyed your well-deserved vacation!

I also thought about starting to implement conversion webhooks - but I just did not find the time for it until now.

Wouldn't it maybe be the easiest to define the interface for a conversion webhook with an argument of type object, and then to case distinctions based on runtime type information in the implementation?

buehler commented 2 years ago

Yes, I thought about that too.

It may be a feasible idea to create an interface like the other webhooks. I thought about something like in Newtonsoft JSON or yamldotnet: an interface that defines "can convert" on an object.

interface IConversionWebhook<TEntity>
{
  bool CanConvert(Type type);
  // ...
}

Now as I understand the matter, the conversion always takes place when a version is received that is not in the "storage version".

So, another possible option could be: Let us assume we have an entity V1CustomUser that contains a username and password field. The V2CustomUser version renames those fields to "user" and "pass" and V3CustomUser removes "pass" for security reasons. The storage version is v3.

If (by documentation and convention) we create a base interface for all those entities (e.g. ICustomUser), we could implement a conversion webhook that handles all types that inherit ICustomUser and furthermore can reflect the current storage type. Problem is, that we do not know the exact way of translating the versions.

As far as I know, if we want to translate a v1 to v2 (in the example above), Kubernetes translates the v1 to v3 (since v3 is the storage version) and then converts it to v2 again.

https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion

buehler commented 2 years ago

Further reading in the Kubernetes docs; there is a possible scenario written:

  1. The storage version is v1beta1. You create an object. It is persisted in storage at version v1beta1
  2. You add version v1 to your CustomResourceDefinition and designate it as the storage version.
  3. You read your object at version v1beta1, then you read the object again at version v1. Both returned objects are identical except for the apiVersion field.
  4. You create a new object. It is persisted in storage at version v1. You now have two objects, one of which is at v1beta1, and the other of which is at v1.
  5. You update the first object. It is now persisted at version v1 since that is the current storage version.
anekdoti commented 2 years ago

As I understood, this version in the middle (in the Kubebuilder documentation called "hub") does not even have to be the storage version - but maybe this restriction is typical.

How about the following approach: the user can supply implementations of an interface IConvert<From, To>. The conversion webhook itself is not implemented by the developer of the operator but provided in KubeOps and it uses DI / assembly scanning to find implementations of IConvert. It's conversion method gets the resource to be converted as a plain object or even a JSON fragment, casts it approriately, and calls two converters with the storage version or some other chosen version as the hub.

buehler commented 2 years ago

Hmm that may be an option.

Another idea that came to my mind last evening: If I understand the documentation correctly, Kubernetes is not required to convert everything to the "stored version". It tries to store a newly created object, or an updated one in the "storage version". What I don't understand is this section:

 webhook:
      # conversionReviewVersions indicates what ConversionReview versions are understood/preferred by the webhook.
      # The first version in the list understood by the API server is sent to the webhook.
      # The webhook must respond with a ConversionReview object in the same version it received.
      conversionReviewVersions: ["v1","v1beta1"]

(souce: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion)

Does this mean, we could "trick" Kubernetes to always use a dummy/"know-it-all" object and convert them to other versions? (e.g. one would create an artificial entity VwhateverObject that contains all fields of other versions?)

Or does this field just configure which versions the webhook is able to convert? If so, it could be possible to let the developer implement IConversionWebhook<FROM,TO> or IConversionWebhook<Entity> and generate the possible list from those webhooks per entity.

anekdoti commented 2 years ago

I think this is not about the versions of the custom resource definition, but about the supported versions of the ConversionReview object that is used to call the webhook.

anekdoti commented 2 years ago

I think the source and target versions in ConversionReview object sent to the webhook can be arbitrary (because e.g. there are situations where a custom resource is requested in a different version than the storage version. The artifical entity with all fields you mention could be something like the "Hub" I mentioned above, but I think it would be more one efficient strategy to implement a conversion webhook in a specific operator.

buehler commented 10 months ago

This is now implemented in #639.

I need some feedback about the way I implemented it. Maybe there is a nicer way to register these webhooks