-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
⚠️ Simplify webhook wiring with Defaulter and Validator interfaces #328
Conversation
cbd3775
to
81c3025
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good.
pkg/webhook/server.go
Outdated
if wh == nil { | ||
continue | ||
} | ||
s.Register("path", wh) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can generate the path using object's GVK and name. And, the default path generation should probably be shared with controller-tools so that generator is in sync with this code ?
pkg/webhook/server.go
Outdated
once sync.Once | ||
// For registers defaulting and validation webhooks for the provided types that implement either | ||
// webhooktuil.Validator or webhookutil.Defaulter. | ||
func (s *Server) For(objects ...runtime.Object) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For
name itself doesn't convey the intent. Wondering if we should give it a more friendly name like EnableValidationFor
or EnableDefaultsFor
?
81c3025
to
5264408
Compare
Addressed comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change looks good, have a few comments.
out := new(FirstMateStatus) | ||
in.DeepCopyInto(out) | ||
return out | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have seen these example projects add too much noise to the repo and introduce additional maintenance burden. Is there a way to avoid it ?
pkg/builder/build.go
Outdated
if err != nil { | ||
return nil, err | ||
} | ||
err = svr.EnableValidationFor(blder.mgr.GetScheme(), blder.apiType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't svr
have access to the scheme ? If yes, then we can get rid of scheme parameter from EnableDefaultsFor
and EnableValidationFor
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EnableValidationFor
happens at setup time. But the setup time is earlier than the inject time.
But I just realized that there is another way to do it.
pkg/webhook/server.go
Outdated
return err | ||
} | ||
if len(gvks) != 1 { | ||
return fmt.Errorf("expected only GVK returned by scheme.ObjectKinds") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should some webhook related context to the error message and also include all the GVKs to help in debugging.
pkg/webhook/server.go
Outdated
s.Register("/validate-"+generatePath(gvks[0]), wh) | ||
} | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am sort of wondering if we should enable validation/defaulting by default when webhook server starts ? (This will save people one step if they want to enable validation/defaulting). And I am thinking of doing the same thing for conversion.
5264408
to
8c836d7
Compare
Addressed comments. PTAL at the interfaces.
@DirectXMan12 WDYT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good to me. Will defer it to @DirectXMan12 for the final LGTM because two issues that you pointed out still need to be resolved.
pkg/builder/build.go
Outdated
// If we get a NotImplementDefaulterValidatorInterfacesError, we discord the Server instance. | ||
// If other type of error, surface it. | ||
if err == nil { | ||
e := blder.mgr.Add(svr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
quick question: shouldn't this be done before calling svr.EnableWebhooksFor
anyways to ensure mgr injects dependencies for webhooks (mainly scheme) ?
pkg/webhook/defaulter.go
Outdated
func (h *mutatingHandler) InjectScheme(s *runtime.Scheme) error { | ||
h.scheme = s | ||
var err error | ||
h.decoder, err = admission.NewDecoder(h.scheme) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on setting decoder this way.
crdexample/controller.go
Outdated
var fmLog = logutil.Log.WithName("firstmate-reconciler") | ||
|
||
// FirstMateController reconciles ReplicaSets | ||
type FirstMateController struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name this something useful -- while "firstmatecontroller" is fun, it's not particularly descriptive
crdexample/main.go
Outdated
|
||
appsv1 "k8s.io/api/apps/v1" | ||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" | ||
"sigs.k8s.io/controller-runtime/crdexample/logutil" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets switch these over to using the alias package (ctrl "sigs.k8s.io/controller-runtime"
) as opposed to a bajillion separate packages.
pkg/webhook/admission/decode.go
Outdated
@@ -35,7 +35,7 @@ func NewDecoder(scheme *runtime.Scheme) (*Decoder, error) { | |||
} | |||
|
|||
// Decode decodes the inlined object in the AdmissionRequest into the passed-in runtime.Object. | |||
func (d *Decoder) Decode(req Request, into runtime.Object) error { | |||
func (d *Decoder) Decode(rawObj runtime.RawExtension, into runtime.Object) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add an additional method for this -- the plain old decode latest from admission request
behavior is really nice.
pkg/webhook/server.go
Outdated
// neither the Defaulter nor the Validator interfaces. | ||
// It returns a NotImplementDefaulterValidatorInterfacesError error if a provided object implements | ||
// neither the Defaulter interface nor the Validator interface. | ||
func (s *Server) EnableWebhooksFor(objects ...runtime.Object) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced this needs to be a method (as opposed to a helper or helpers). The helper makes it more composable, easier to test individually, and lets us actually have a type signature that requires an object that implements the interfaces (removing the error path in favor of compile-time checking).
pkg/webhook/server.go
Outdated
|
||
func generatePath(gvk schema.GroupVersionKind) string { | ||
var pathItems []string | ||
splittedGroup := strings.Split(gvk.Group, ".") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why aren't we just strings.Replace(gvk.Group, ".", "-")
or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curret behavior doesn't include domain name in the path:
Group: apps.example.com
Kind: Foo
I will get /mutate-apps-foo
instead of /mutate-apps-example-com-foo
. IMO it's more concise, but there may be a small risk of collision. e.g. with
Group: apps.bar.com
Kind: Foo
I don't have a strong opinion about if we should have domain name.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think conciseness here is valuable (only ever have to write this once, and it's usually going to be autogenerated), and I'd rather avoid collisions
pkg/webhook/server.go
Outdated
return nil | ||
} | ||
|
||
mwh := newDefaultingWebhookFor(object) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should opportunistically merge validating and defaulting hooks into a single hook when possible, to avoid the extra round-trip
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should opportunistically merge validating and defaulting hooks into a single hook when possible, to avoid the extra round-trip
This means you want to put defaulting and validating hooks in one mutatingWebhook. But doing it this way is not safe, since there may be another mutating webhook changes the object after your validation logic. The safe way to do it is making validation logic in a validating webhook, which is always run after all mutating webhooks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point 👍
6c379c1
to
49dca2b
Compare
|
pkg/builder/build.go
Outdated
@@ -218,3 +240,60 @@ func (blder *Builder) doController(r reconcile.Reconciler) error { | |||
blder.ctrl, err = newController(name, blder.mgr, controller.Options{Reconciler: r}) | |||
return err | |||
} | |||
|
|||
func (blder *Builder) doWebhook() error { | |||
if blder.mgr.GetScheme() == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we default the scheme, no? This should never be nil
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was being more defensive here. But if you think it's not necessary at all, I will drop it.
examples/crd/main.go
Outdated
|
||
appsv1 "k8s.io/api/apps/v1" | ||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" | ||
"sigs.k8s.io/controller-runtime/examples/crd/pkg" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's use ctrl "sigs.k8s.io/controller-runtime"
instead of most of these in the examples
examples/crd/Dockerfile
Outdated
@@ -0,0 +1,17 @@ | |||
# Build the manager binary |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't think we should have the deployment stuff here -- this should be only code. Deployment examples can live in KB
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack. Will polish the CRD example and remote deployment bits in the example.
examples/crd/pkg/resource.go
Outdated
var _ admission.Validator = &FirstMate{} | ||
|
||
// ValidateCreate implements webhookutil.validator so a webhook will be registered for the type | ||
func (f *FirstMate) ValidateCreate() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should have webhook generation annotations?
PTAL |
Rebased on HEAD to resolved the conflicts and squashed some commits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor nits inline
pkg/builder/build.go
Outdated
gvk.Version + "-" + strings.ToLower(gvk.Kind) | ||
|
||
defaulter, isDefaulter := blder.apiType.(admission.Defaulter) | ||
if isDefaulter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: collapse this into a compound statement.
pkg/builder/build.go
Outdated
} | ||
} | ||
|
||
validator, isValidator := blder.apiType.(admission.Validator) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: collapse this into a compound statement
pkg/builder/build.go
Outdated
vwh := admission.ValidatingWebhookFor(validator) | ||
if vwh != nil { | ||
path := "/validate-" + partialPath | ||
log.Info("registering a validating webhook", "path", path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
log the GVK?
/lgtm Can you follow up with a PR with slightly more comprehensive tests? |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: DirectXMan12, mengqiy 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 |
Sure! |
If user defines a CRD type and make it implement the Defaulter and (or) the Validator interface, it will automatically wire a webhook server for the admission webhooks.
New changes are detected. LGTM label has been removed. |
Forgot to remove the Re-applying the label per #328 (comment), since there is no actual code changes. |
If a CRD type implements the
Defaulter
andValidator
interface, we will generate the admission webhook for the CRD type.For non-CRD type, we provide methods to pass in path and handlers to create the mutating|validting webhook.
godoc and tests are on the way.