Skip to content

Commit 31f75a0

Browse files
committed
feat(webhook): dataset validation rules + tests
1 parent a3ea57c commit 31f75a0

File tree

3 files changed

+97
-26
lines changed

3 files changed

+97
-26
lines changed

pkg/webhook/handler/register.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package handler
1919
import (
2020
"github.com/fluid-cloudnative/fluid/pkg/common"
2121
"github.com/fluid-cloudnative/fluid/pkg/webhook/handler/mutating"
22+
"github.com/fluid-cloudnative/fluid/pkg/webhook/handler/validating"
2223
"github.com/go-logr/logr"
2324
"k8s.io/apimachinery/pkg/util/sets"
2425
ctrl "sigs.k8s.io/controller-runtime"
@@ -41,7 +42,7 @@ var (
4142

4243
func init() {
4344
addHandlers(mutating.HandlerMap)
44-
// addHandlers(validating.HandlerMap)
45+
addHandlers(validating.HandlerMap)
4546
}
4647

4748
// Register registers the handlers to the manager
Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,71 @@
11
package validating
22

33
import (
4-
"context"
5-
"encoding/json"
6-
"net/http"
4+
"context"
5+
"encoding/json"
6+
"net/http"
77

8-
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
8+
v1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
9+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
910
)
1011

1112
// ValidatingHandler implements admission webhook for validating Fluid CRDs.
1213
type ValidatingHandler struct {
13-
decoder *admission.Decoder
14+
decoder *admission.Decoder
1415
}
1516

1617
func NewValidatingHandler() *ValidatingHandler {
17-
return &ValidatingHandler{}
18+
return &ValidatingHandler{}
1819
}
1920

2021
func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
21-
// Generic validation: ensure object contains metadata.name
22-
var obj map[string]interface{}
23-
if err := json.Unmarshal(req.Object.Raw, &obj); err != nil {
24-
return admission.Errored(http.StatusBadRequest, err)
25-
}
26-
27-
metadata, ok := obj["metadata"].(map[string]interface{})
28-
if !ok {
29-
return admission.Denied("metadata.name is required")
30-
}
31-
name, ok := metadata["name"].(string)
32-
if !ok || name == "" {
33-
return admission.Denied("metadata.name is required")
34-
}
35-
36-
// Passed basic validation
37-
return admission.Allowed("validation passed")
22+
// Try to decode into a known type (Dataset) when possible
23+
var ds v1alpha1.Dataset
24+
if h.decoder != nil {
25+
if err := h.decoder.Decode(req, &ds); err == nil {
26+
// Perform Dataset-specific validations
27+
// Require either Mounts or Runtimes to be present
28+
if len(ds.Spec.Mounts) == 0 && len(ds.Spec.Runtimes) == 0 {
29+
return admission.Denied("dataset.spec must contain at least one mount or runtime")
30+
}
31+
32+
// Validate mounts
33+
for _, m := range ds.Spec.Mounts {
34+
if m.MountPoint == "" {
35+
return admission.Denied("mount.mountPoint must not be empty")
36+
}
37+
}
38+
39+
// Validate runtimes
40+
for _, r := range ds.Spec.Runtimes {
41+
if r.Name == "" || r.Namespace == "" {
42+
return admission.Denied("runtime entries must include name and namespace")
43+
}
44+
}
45+
46+
return admission.Allowed("dataset validation passed")
47+
}
48+
}
49+
50+
// Fallback generic validation: ensure object contains metadata.name
51+
var obj map[string]interface{}
52+
if err := json.Unmarshal(req.Object.Raw, &obj); err != nil {
53+
return admission.Errored(http.StatusBadRequest, err)
54+
}
55+
56+
metadata, ok := obj["metadata"].(map[string]interface{})
57+
if !ok {
58+
return admission.Denied("metadata.name is required")
59+
}
60+
name, ok := metadata["name"].(string)
61+
if !ok || name == "" {
62+
return admission.Denied("metadata.name is required")
63+
}
64+
65+
return admission.Allowed("validation passed")
3866
}
3967

4068
func (h *ValidatingHandler) InjectDecoder(d *admission.Decoder) error {
41-
h.decoder = d
42-
return nil
69+
h.decoder = d
70+
return nil
4371
}

pkg/webhook/handler/validating/validating_handler_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import (
44
"context"
55
"testing"
66

7+
"encoding/json"
8+
79
"github.com/stretchr/testify/assert"
810
admissionv1 "k8s.io/api/admission/v1"
911
"k8s.io/apimachinery/pkg/runtime"
1012
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
13+
v1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
1114
)
1215

1316
func TestValidatingHandler_Basic(t *testing.T) {
@@ -30,3 +33,42 @@ func TestValidatingHandler_Basic(t *testing.T) {
3033
assert.False(t, resp.Allowed)
3134
assert.Contains(t, resp.Result.Message, "metadata.name is required")
3235
}
36+
37+
func TestValidatingHandler_Dataset(t *testing.T) {
38+
h := NewValidatingHandler()
39+
// prepare decoder with scheme so that typed decoding works
40+
scheme := runtime.NewScheme()
41+
err := v1alpha1.AddToScheme(scheme)
42+
if err != nil {
43+
t.Fatalf("failed to add v1alpha1 to scheme: %v", err)
44+
}
45+
dec := admission.NewDecoder(scheme)
46+
h.InjectDecoder(dec)
47+
48+
// Dataset missing mounts and runtimes -> denied
49+
ds := &v1alpha1.Dataset{}
50+
raw, _ := json.Marshal(ds)
51+
req := admission.Request{
52+
AdmissionRequest: admissionv1.AdmissionRequest{
53+
Object: runtime.RawExtension{Raw: raw},
54+
},
55+
}
56+
resp := h.Handle(context.Background(), req)
57+
assert.False(t, resp.Allowed)
58+
59+
// Dataset with a valid mount -> allowed
60+
ds = &v1alpha1.Dataset{}
61+
ds.Spec.Mounts = []v1alpha1.Mount{{MountPoint: "/data"}}
62+
raw, _ = json.Marshal(ds)
63+
req.AdmissionRequest.Object.Raw = raw
64+
resp = h.Handle(context.Background(), req)
65+
assert.True(t, resp.Allowed)
66+
67+
// Dataset with invalid mount (too short) -> denied
68+
ds = &v1alpha1.Dataset{}
69+
ds.Spec.Mounts = []v1alpha1.Mount{{MountPoint: "abc"}}
70+
raw, _ = json.Marshal(ds)
71+
req.AdmissionRequest.Object.Raw = raw
72+
resp = h.Handle(context.Background(), req)
73+
assert.False(t, resp.Allowed)
74+
}

0 commit comments

Comments
 (0)