-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(webhook): add validating webhook skeleton #5678
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| /* | ||
| Copyright 2021 The Fluid Authors. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package validating | ||
|
|
||
| import ( | ||
| "context" | ||
|
|
||
| v1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1" | ||
| "sigs.k8s.io/controller-runtime/pkg/webhook/admission" | ||
| ) | ||
|
|
||
| // ValidatingHandler implements admission webhook for validating Fluid CRDs. | ||
| type ValidatingHandler struct { | ||
| decoder *admission.Decoder | ||
| } | ||
|
|
||
| func NewValidatingHandler() *ValidatingHandler { | ||
| return &ValidatingHandler{} | ||
| } | ||
|
|
||
| func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response { | ||
| // DELETE requests may have an empty Object.Raw; allow them through. | ||
| if len(req.Object.Raw) == 0 { | ||
| return admission.Allowed("no object to validate") | ||
| } | ||
|
|
||
| // Decode into a Dataset when possible. | ||
| var ds v1alpha1.Dataset | ||
| if h.decoder != nil { | ||
| if err := h.decoder.Decode(req, &ds); err == nil { | ||
|
Check warning on line 44 in pkg/webhook/handler/validating/validating_handler.go
|
||
| return h.validateDataset(&ds) | ||
| } | ||
| } | ||
|
|
||
| // For unknown types, allow through (skeleton — extend as needed). | ||
| return admission.Allowed("validation passed") | ||
| } | ||
|
|
||
| func (h *ValidatingHandler) validateDataset(ds *v1alpha1.Dataset) admission.Response { | ||
| // Mounts is optional (e.g. Vineyard runtime), so we only validate | ||
| // individual entries when present. | ||
| for _, m := range ds.Spec.Mounts { | ||
| if m.MountPoint == "" { | ||
| return admission.Denied("mount.mountPoint must not be empty") | ||
| } | ||
| } | ||
|
|
||
| // Validate runtime entries when present. | ||
| for _, r := range ds.Spec.Runtimes { | ||
| if r.Name == "" || r.Namespace == "" { | ||
| return admission.Denied("runtime entries must include name and namespace") | ||
| } | ||
| } | ||
|
|
||
| // Require metadata.name (also covers generateName-only submissions). | ||
| if ds.Name == "" { | ||
| return admission.Denied("metadata.name is required") | ||
| } | ||
|
|
||
| return admission.Allowed("dataset validation passed") | ||
| } | ||
|
|
||
| func (h *ValidatingHandler) InjectDecoder(d *admission.Decoder) error { | ||
| h.decoder = d | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| /* | ||
| Copyright 2021 The Fluid Authors. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package validating | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| admissionv1 "k8s.io/api/admission/v1" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/runtime" | ||
| "sigs.k8s.io/controller-runtime/pkg/webhook/admission" | ||
|
|
||
| v1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1" | ||
| ) | ||
|
|
||
| func TestValidatingHandler_EmptyRaw(t *testing.T) { | ||
|
Check warning on line 33 in pkg/webhook/handler/validating/validating_handler_test.go
|
||
| h := newHandler(t) | ||
|
|
||
| // DELETE requests may have empty Object.Raw — should be allowed. | ||
| req := admission.Request{ | ||
| AdmissionRequest: admissionv1.AdmissionRequest{ | ||
| Object: runtime.RawExtension{Raw: nil}, | ||
| }, | ||
| } | ||
| resp := h.Handle(context.Background(), req) | ||
| assert.True(t, resp.Allowed) | ||
| } | ||
|
|
||
| func TestValidatingHandler_Dataset(t *testing.T) { | ||
|
Check warning on line 46 in pkg/webhook/handler/validating/validating_handler_test.go
|
||
| h := newHandler(t) | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| ds *v1alpha1.Dataset | ||
| allowed bool | ||
| message string | ||
| }{ | ||
| { | ||
| name: "empty mounts and runtimes is allowed (mounts are optional)", | ||
| ds: &v1alpha1.Dataset{ObjectMeta: metav1.ObjectMeta{Name: "ds"}}, | ||
| allowed: true, | ||
| }, | ||
| { | ||
| name: "valid mount is allowed", | ||
| ds: &v1alpha1.Dataset{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: "ds"}, | ||
| Spec: v1alpha1.DatasetSpec{Mounts: []v1alpha1.Mount{{MountPoint: "/data"}}}, | ||
| }, | ||
| allowed: true, | ||
| }, | ||
| { | ||
| name: "empty mountPoint is denied", | ||
| ds: &v1alpha1.Dataset{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: "ds"}, | ||
| Spec: v1alpha1.DatasetSpec{Mounts: []v1alpha1.Mount{{MountPoint: ""}}}, | ||
| }, | ||
| allowed: false, | ||
| message: "mount.mountPoint must not be empty", | ||
| }, | ||
| { | ||
| name: "runtime missing namespace is denied", | ||
| ds: &v1alpha1.Dataset{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: "ds"}, | ||
| Spec: v1alpha1.DatasetSpec{Runtimes: []v1alpha1.Runtime{{Name: "alluxio"}}}, | ||
| }, | ||
| allowed: false, | ||
| message: "runtime entries must include name and namespace", | ||
| }, | ||
| { | ||
| name: "missing metadata.name is denied", | ||
| ds: &v1alpha1.Dataset{}, | ||
| allowed: false, | ||
| message: "metadata.name is required", | ||
| }, | ||
| } | ||
|
|
||
| for _, tc := range tests { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| // Set TypeMeta so the decoder can recognize the GVK. | ||
| tc.ds.TypeMeta = metav1.TypeMeta{ | ||
| APIVersion: "data.fluid.io/v1alpha1", | ||
| Kind: "Dataset", | ||
| } | ||
| raw, err := json.Marshal(tc.ds) | ||
| assert.NoError(t, err) | ||
|
|
||
| req := admission.Request{ | ||
| AdmissionRequest: admissionv1.AdmissionRequest{ | ||
| Object: runtime.RawExtension{Raw: raw}, | ||
| }, | ||
| } | ||
| resp := h.Handle(context.Background(), req) | ||
| assert.Equal(t, tc.allowed, resp.Allowed, "test %q", tc.name) | ||
| if tc.message != "" { | ||
| assert.Contains(t, resp.Result.Message, tc.message) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| // newHandler creates a ValidatingHandler wired with a decoder for v1alpha1. | ||
| func newHandler(t *testing.T) *ValidatingHandler { | ||
| t.Helper() | ||
| scheme := runtime.NewScheme() | ||
| assert.NoError(t, v1alpha1.AddToScheme(scheme)) | ||
| h := NewValidatingHandler() | ||
| h.InjectDecoder(admission.NewDecoder(scheme)) | ||
| return h | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,38 @@ | ||||||||
| /* | ||||||||
| Copyright 2021 The Fluid Authors. | ||||||||
|
|
||||||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||||||
| you may not use this file except in compliance with the License. | ||||||||
| You may obtain a copy of the License at | ||||||||
|
|
||||||||
| http://www.apache.org/licenses/LICENSE-2.0 | ||||||||
|
|
||||||||
| Unless required by applicable law or agreed to in writing, software | ||||||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||
| See the License for the specific language governing permissions and | ||||||||
| limitations under the License. | ||||||||
| */ | ||||||||
|
|
||||||||
| package validating | ||||||||
|
|
||||||||
| import ( | ||||||||
| "github.com/fluid-cloudnative/fluid/pkg/common" | ||||||||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||||||||
|
Comment on lines
+17
to
+21
|
||||||||
| "sigs.k8s.io/controller-runtime/pkg/webhook/admission" | ||||||||
| ) | ||||||||
|
|
||||||||
|
||||||||
| //+kubebuilder:webhook:path=/validate,mutating=false,sideEffects=None,failurePolicy=Fail,groups=*,resources=*,verbs=create;update;delete,versions=*,name=fluid-validating-webhook.fluid.io,admissionReviewVersions=v1 |
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
metadata.namerequirement is only enforced in the JSON fallback path. When Dataset decoding succeeds, the handler can allow a Dataset with an emptymetadata.name(or onlygenerateName) without running the name check, which conflicts with the PR description. Consider validating name in the typed branch (or doing the generic metadata validation first).