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

✨ Allow webhooks to register custom validators/defaulter types #1676

Merged
merged 1 commit into from Sep 29, 2021

Conversation

vincepri
Copy link
Member

This changeset allows our webhook builder to take in a handler any other
struct other than a runtime.Object.

Today having an object as the primary source of truth for both
Defaulting and Validators makes API types carry a lot of information and
business logic alongside their definitions.

Moreover, lots of folks in the past have asked for ways to have an
external type to handle these operations and use a controller runtime
client for validations.

This change brings a new way to register webhooks, which admission.For
handler any type (struct) can be a defaulting or validating handler for
a runtime Object.

Signed-off-by: Vince Prignano vincepri@vmware.com

@vincepri
Copy link
Member Author

/hold

@k8s-ci-robot k8s-ci-robot added do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Sep 27, 2021
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: vincepri

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Sep 27, 2021
@vincepri vincepri force-pushed the admission-for branch 2 times, most recently from bc58c4b to 9fd7870 Compare September 27, 2021 19:27
Copy link
Member

@fabriziopandini fabriziopandini left a comment

Choose a reason for hiding this comment

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

I see two aspect for this PR

  • Allowing registering webhook implemented outside of the API types
  • Improving validator and default interface with context and always returning error.

For the first part, I have the impression that what we need is just somethings that implements the two methods defined in runtime.Object, but I still need to investigate this a little bit.
For the second part, might be worth also considering a breaking change, given that those seems best practices to me

pkg/webhook/admission/defaulter_handler.go Outdated Show resolved Hide resolved
@vincepri
Copy link
Member Author

For the first part, I have the impression that what we need is just somethings that implements the two methods defined in runtime.Object, but I still need to investigate this a little bit.

Would that be a preferred solution to this problem? It seems like that overwriting the deep copy methods is a hacky way to force an external type into the current webhooks builder logic.

@vincepri
Copy link
Member Author

vincepri commented Sep 27, 2021

For the second part, might be worth also considering a breaking change, given that those seems best practices to me

This is something we can consider breaking in a future release, once we can streamline the interface as well

@fabriziopandini
Copy link
Member

For the first part, I have the impression that what we need is just somethings that implements the two methods defined in runtime.Object, but I still need to investigate this a little bit.

Would that be a preferred solution to this problem? It seems like that overwriting the deep copy methods is a hacky way to force an external type into the current webhooks builder logic.

That's right, I'm trying to understand if we can be explicit using HandlerFor in the builder but avoiding to duplicate the Handle code in validation/defaulter handler

//
// If the given object implements the admission.DefaulterFor interface, a MutatingWebhook will be wired for this type.
// If the given object implements the admission.ValidatorFor interface, a ValidatingWebhook will be wired for this type.
func (blder *WebhookBuilder) HandlerFor(forType admission.For) *WebhookBuilder {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would something like WebhookFor be better? I worry that Handler is such a generic term.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure if that's clear either, wouldn't the WebhookBuilder be in charge of returning the webhook?

Copy link
Member

Choose a reason for hiding this comment

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

We don't document or advertise that approach, but its definitely possible to do this external to the api package with the existing interface by embedding the api type into the webhook implementation

Copy link
Member

Choose a reason for hiding this comment

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

@alvaroaleman I tried to do that, but I couldn't get it to work with the DeepCopy and subsequent Decode. (because the webhook struct is not registered as type for the decoding). Do you have a link to an existing implementation?

Copy link
Member

Choose a reason for hiding this comment

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

ah ok, I didn't test it I just assumed that it should work.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, I just thought maybe there is a way and I just couldn't find it :)

//
// If the given object implements the admission.DefaulterFor interface, a MutatingWebhook will be wired for this type.
// If the given object implements the admission.ValidatorFor interface, a ValidatingWebhook will be wired for this type.
func (blder *WebhookBuilder) HandlerFor(forType admission.For) *WebhookBuilder {
Copy link
Member

Choose a reason for hiding this comment

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

We don't document or advertise that approach, but its definitely possible to do this external to the api package with the existing interface by embedding the api type into the webhook implementation

pkg/builder/webhook.go Outdated Show resolved Hide resolved
pkg/webhook/admission/validator_handler.go Outdated Show resolved Hide resolved
pkg/builder/webhook.go Outdated Show resolved Hide resolved
pkg/builder/webhook.go Outdated Show resolved Hide resolved
@vincepri vincepri force-pushed the admission-for branch 4 times, most recently from bbaed32 to cd7d01a Compare September 28, 2021 14:47
pkg/builder/webhook.go Outdated Show resolved Hide resolved
pkg/builder/webhook.go Outdated Show resolved Hide resolved
pkg/webhook/admission/defaulter_handler.go Outdated Show resolved Hide resolved
pkg/webhook/admission/defaulter_handler.go Outdated Show resolved Hide resolved
pkg/webhook/admission/defaulter_handler.go Outdated Show resolved Hide resolved
pkg/webhook/admission/validator_handler.go Outdated Show resolved Hide resolved
pkg/webhook/admission/validator_handler.go Outdated Show resolved Hide resolved

// WithValidator defines functions for validating an operation.
type WithValidator interface {
ValidateCreate(ctx context.Context, obj runtime.Object) error
Copy link
Member

Choose a reason for hiding this comment

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

Can we use client.Object for these?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not really, unless we make it a breaking change? I was trying to change any interface until 0.11, so we can backport this change

Copy link
Member

Choose a reason for hiding this comment

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

These are new interfaces, how would using client.Object here be breaking?

I was thinking that this would make usage slightly better, i.E. you wouldn't be able to pass a List object in here

Copy link
Member Author

Choose a reason for hiding this comment

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

While the interfaces are new, the object passed in comes from the webhook builder, we'd have to cast it before passing creating the webhook. Moreover, when we use DeepCopyObject() that returns a runtime.Object, which we'd have to cast again if we want to use client.Object everywhere


// Get the object in the request
obj := h.object.DeepCopyObject()
if req.Operation == v1.Create {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: We could switch on the operation to be clear that these are mutually excluse (and maybe have a erorring default clause). Also, the error final error handling can be moved out of the conditions and deduplicated

Copy link
Member Author

Choose a reason for hiding this comment

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

Done! ptal

@vincepri vincepri force-pushed the admission-for branch 3 times, most recently from 9844bd4 to 6b01655 Compare September 28, 2021 20:36
@vincepri vincepri force-pushed the admission-for branch 4 times, most recently from 6052973 to f99b738 Compare September 28, 2021 21:37
pkg/builder/webhook.go Show resolved Hide resolved
}

err = h.validator.ValidateDelete(ctx, obj)
}
Copy link
Member

Choose a reason for hiding this comment

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

Do we want to return an error in the default case?

pkg/webhook/alias.go Outdated Show resolved Hide resolved
pkg/webhook/admission/defaulter_custom.go Outdated Show resolved Hide resolved
pkg/webhook/admission/defaulter_custom.go Outdated Show resolved Hide resolved
This changeset allows our webhook builder to take in a handler any other
struct other than a runtime.Object.

Today having an object as the primary source of truth for both
Defaulting and Validators makes API types carry a lot of information and
business logic alongside their definitions.

Moreover, lots of folks in the past have asked for ways to have an
external type to handle these operations and use a controller runtime
client for validations.

This change brings a new way to register webhooks, which admission.For
handler any type (struct) can be a defaulting or validating handler for
a runtime Object.

Signed-off-by: Vince Prignano <vincepri@vmware.com>
@vincepri
Copy link
Member Author

/hold cancel

@k8s-ci-robot k8s-ci-robot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Sep 29, 2021
@sbueringer
Copy link
Member

No more nits from my side :)

Thank you very much for implementing this!!

@sbueringer
Copy link
Member

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Sep 29, 2021
@sbueringer
Copy link
Member

sbueringer commented Sep 29, 2021

/hold
@vincepri Not sure if you want to wait for feedback from others. (tide would have merged otherwise)

@k8s-ci-robot k8s-ci-robot added do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. and removed do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. labels Sep 29, 2021
@vincepri
Copy link
Member Author

vincepri commented Sep 29, 2021

/hold for @alvaroaleman

@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Sep 29, 2021
@alvaroaleman
Copy link
Member

/hold cancel
Thanks for your work!

@k8s-ci-robot k8s-ci-robot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Sep 29, 2021
@k8s-ci-robot k8s-ci-robot merged commit dba3220 into kubernetes-sigs:master Sep 29, 2021
@k8s-ci-robot k8s-ci-robot added this to the v0.10.x milestone Sep 29, 2021
@vincepri
Copy link
Member Author

/cherrypick release-0.10

@k8s-infra-cherrypick-robot

@vincepri: new pull request created: #1679

In response to this:

/cherrypick release-0.10

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants