Skip to content

feat(webhook): add validating webhook skeleton#5678

Open
Rajneesh180 wants to merge 3 commits intofluid-cloudnative:masterfrom
Rajneesh180:feat/validation-webhook-skeleton
Open

feat(webhook): add validating webhook skeleton#5678
Rajneesh180 wants to merge 3 commits intofluid-cloudnative:masterfrom
Rajneesh180:feat/validation-webhook-skeleton

Conversation

@Rajneesh180
Copy link

Adds a basic validating admission webhook handler skeleton with a simple validation check (metadata.name required) and unit tests. This is the first step toward a full validating webhook for Fluid CRDs.

@fluid-e2e-bot
Copy link

fluid-e2e-bot bot commented Mar 4, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign zwwhdls for approval by writing /assign @zwwhdls in a comment. For more information see:The Kubernetes Code Review Process.

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

Details 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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request lays the foundational groundwork for integrating validating admission webhooks into the Fluid project. It establishes a basic webhook handler capable of performing initial validation checks on incoming Kubernetes resources, specifically ensuring that all objects possess a 'metadata.name'. This is a crucial first step towards implementing comprehensive validation for Fluid's Custom Resource Definitions, enhancing data integrity and operational stability.

Highlights

  • Validating Webhook Skeleton: Introduced a basic validating admission webhook handler for Fluid CRDs, including a simple check to ensure 'metadata.name' is present.
  • Initial Validation Logic: Implemented a generic validation check that denies admission requests if the object's 'metadata.name' field is missing or empty.
  • Unit Tests: Added unit tests to verify the webhook's behavior for both valid objects and objects missing the required 'metadata.name'.
Changelog
  • pkg/webhook/handler/validating/validating_handler.go
    • Implemented the core logic for the 'ValidatingHandler', which processes admission requests and performs basic validation.
    • Added 'NewValidatingHandler' constructor and 'InjectDecoder' method for dependency injection.
  • pkg/webhook/handler/validating/validating_handler_test.go
    • Introduced unit tests for 'ValidatingHandler' to confirm correct behavior for valid and invalid input objects.
  • pkg/webhook/handler/validating/webhook.go
    • Defined the 'Handler' struct and 'HandlerMap' to register the validating webhook with the controller-runtime framework.
    • Implemented the 'Setup' method to initialize the 'ValidatingHandler' and inject the decoder.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@fluid-e2e-bot
Copy link

fluid-e2e-bot bot commented Mar 4, 2026

Hi @Rajneesh180. Thanks for your PR.

I'm waiting for a fluid-cloudnative member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

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.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a skeleton for a validating admission webhook. The implementation is straightforward, but it can be improved by better leveraging the controller-runtime framework. Specifically, the handler should use the injected decoder for object deserialization instead of manual JSON parsing. This change simplifies the code and makes it more robust. I've also suggested an update to the unit test to accommodate this change.

)

func TestValidatingHandler_Basic(t *testing.T) {
h := NewValidatingHandler()
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Given the suggested change to use the decoder in validating_handler.go, this test will fail with a nil pointer dereference because the decoder is not initialized. You should create and inject a decoder into the handler to ensure the test can run successfully and validate the handler's logic.

Suggested change
h := NewValidatingHandler()
h := NewValidatingHandler()
decoder := admission.NewDecoder(runtime.NewScheme())
err := h.InjectDecoder(decoder)
assert.NoError(t, err)

Comment on lines +22 to +34
var obj map[string]interface{}
if err := json.Unmarshal(req.Object.Raw, &obj); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

metadata, ok := obj["metadata"].(map[string]interface{})
if !ok {
return admission.Denied("metadata.name is required")
}
name, ok := metadata["name"].(string)
if !ok || name == "" {
return admission.Denied("metadata.name is required")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The Handle function manually unmarshals the JSON payload into a map[string]interface{}. A more idiomatic and robust approach is to use the injected decoder to unmarshal the request object into an unstructured object. This leverages the controller-runtime framework's capabilities, simplifies the code, and makes it less error-prone.

By using unstructured.Unstructured, you can easily access metadata fields like the object name with obj.GetName().

You will also need to adjust your imports:

- "encoding/json"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    obj := &unstructured.Unstructured{}
    if err := h.decoder.Decode(req, obj); err != nil {
        return admission.Errored(http.StatusBadRequest, err)
    }

    if obj.GetName() == "" {
        return admission.Denied("metadata.name is required")
    }

Copilot AI review requested due to automatic review settings March 6, 2026 01:49
@Rajneesh180 Rajneesh180 force-pushed the feat/validation-webhook-skeleton branch from 362e705 to 31f75a0 Compare March 6, 2026 01:49
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an initial validating admission webhook handler for Fluid resources, intended as the starting point for validating Fluid CRDs.

Changes:

  • Introduces a new validating webhook handler skeleton with basic generic validation and Dataset-specific validation logic.
  • Adds unit tests for the validating handler behavior.
  • Registers the validating handler map in the webhook handler registry.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 11 comments.

File Description
pkg/webhook/handler/validating/webhook.go Adds validating webhook handler map and setup wrapper.
pkg/webhook/handler/validating/validating_handler.go Implements validating admission handler with Dataset-typed and generic JSON fallback validation.
pkg/webhook/handler/validating/validating_handler_test.go Adds unit tests covering generic and Dataset validation paths.
pkg/webhook/handler/register.go Enables registering validating handlers during init.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +27 to +30
// Require either Mounts or Runtimes to be present
if len(ds.Spec.Mounts) == 0 && len(ds.Spec.Runtimes) == 0 {
return admission.Denied("dataset.spec must contain at least one mount or runtime")
}
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

This denies Datasets when both spec.mounts and spec.runtimes are empty, but DatasetSpec.Mounts is explicitly documented as optional and may be empty for some runtimes (see api/v1alpha1/dataset_types.go). As-is, this webhook would reject some Datasets that the API types/CRD allow; please align the validation rule with the API contract (or scope the rule to only the cases where mounts/runtimes are required).

Suggested change
// Require either Mounts or Runtimes to be present
if len(ds.Spec.Mounts) == 0 && len(ds.Spec.Runtimes) == 0 {
return admission.Denied("dataset.spec must contain at least one mount or runtime")
}

Copilot uses AI. Check for mistakes.

// Fallback generic validation: ensure object contains metadata.name
var obj map[string]interface{}
if err := json.Unmarshal(req.Object.Raw, &obj); err != nil {
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

req.Object.Raw can be empty (e.g., DELETE admission requests commonly use OldObject). In that case this code returns a 400 error, which can unintentionally block deletes if the webhook is configured for them. Consider explicitly handling req.Operation == Delete and/or using req.OldObject when Object.Raw is empty.

Suggested change
if err := json.Unmarshal(req.Object.Raw, &obj); err != nil {
raw := req.Object.Raw
if len(raw) == 0 {
raw = req.OldObject.Raw
}
if len(raw) == 0 {
// No object payload available to validate; do not block the request
return admission.Allowed("no object to validate")
}
if err := json.Unmarshal(raw, &obj); err != nil {

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +53
ds := &v1alpha1.Dataset{}
raw, _ := json.Marshal(ds)
req := admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Object: runtime.RawExtension{Raw: raw},
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

These admission payloads are built by marshaling a v1alpha1.Dataset{} without setting apiVersion/kind (TypeMeta) or metadata.name. admission.Decoder.Decode relies on GVK in the raw object, so decoding may fail and the handler will fall back to the generic JSON path, making the test non-representative. Set TypeMeta/ObjectMeta (or craft raw JSON including them) so the test deterministically exercises the typed Dataset validation branch.

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +73
ds.Spec.Mounts = []v1alpha1.Mount{{MountPoint: "abc"}}
raw, _ = json.Marshal(ds)
req.AdmissionRequest.Object.Raw = raw
resp = h.Handle(context.Background(), req)
assert.False(t, resp.Allowed)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

The current webhook only rejects empty mountPoint, so this test case expecting denial for mountPoint: "abc" will fail. Either enforce the intended constraint in the webhook (e.g., align with the CRD’s MinLength=5 on MountPoint) or adjust/remove this test case.

Copilot uses AI. Check for mistakes.

// Dataset missing mounts and runtimes -> denied
ds := &v1alpha1.Dataset{}
raw, _ := json.Marshal(ds)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

json.Marshal errors are ignored here. Please check the returned error and fail the test if it’s non-nil to avoid hiding unexpected issues.

Copilot uses AI. Check for mistakes.
// Dataset with invalid mount (too short) -> denied
ds = &v1alpha1.Dataset{}
ds.Spec.Mounts = []v1alpha1.Mount{{MountPoint: "abc"}}
raw, _ = json.Marshal(ds)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

json.Marshal errors are ignored here. Please check the returned error and fail the test if it’s non-nil to avoid hiding unexpected issues.

Copilot uses AI. Check for mistakes.
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

This new webhook entrypoint is missing a +kubebuilder:webhook marker (like pkg/webhook/handler/mutating/webhook.go) so controller-gen won’t generate a ValidatingWebhookConfiguration for it. Add the marker with the intended path/resources/verbs (e.g., Dataset CRDs) so the webhook can actually be installed.

Suggested change
//+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

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +5
package validating

import (
"github.com/fluid-cloudnative/fluid/pkg/common"
"sigs.k8s.io/controller-runtime/pkg/client"
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

Repository files under pkg/webhook/handler typically include the Apache 2.0 license header (e.g. pkg/webhook/handler/register.go, pkg/webhook/handler/mutating/webhook.go). Consider adding the standard header to this new source file for consistency and licensing compliance.

Copilot uses AI. Check for mistakes.
// Try to decode into a known type (Dataset) when possible
var ds v1alpha1.Dataset
if h.decoder != nil {
if err := h.decoder.Decode(req, &ds); err == nil {
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

The metadata.name requirement is only enforced in the JSON fallback path. When Dataset decoding succeeds, the handler can allow a Dataset with an empty metadata.name (or only generateName) 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).

Suggested change
if err := h.decoder.Decode(req, &ds); err == nil {
if err := h.decoder.Decode(req, &ds); err == nil {
// Ensure metadata.name is present in the typed Dataset path
if ds.Name == "" {
return admission.Denied("metadata.name is required")
}

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +13
"testing"

"encoding/json"

"github.com/stretchr/testify/assert"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
v1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

Import grouping/order isn’t gofmt’d (e.g., encoding/json is separated from other stdlib imports). Please run gofmt/goimports so formatting checks don’t fail in CI.

Suggested change
"testing"
"encoding/json"
"github.com/stretchr/testify/assert"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
v1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
"encoding/json"
"testing"
v1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
"github.com/stretchr/testify/assert"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

Copilot uses AI. Check for mistakes.
…t validation, table-driven tests

- Add Apache 2.0 license headers to all files
- Add kubebuilder:webhook marker for config generation
- Remove incorrect mounts+runtimes required check (Mounts is optional)
- Remove raw JSON fallback path in favor of decoder-only flow
- Handle DELETE requests with empty Object.Raw gracefully
- Validate individual mount entries and runtime entries
- Rewrite tests as table-driven with proper TypeMeta/ObjectMeta
@Rajneesh180
Copy link
Author

Updated with a round of fixes based on the review feedback:

  • Added Apache 2.0 license headers to all three files
  • Added +kubebuilder:webhook marker on webhook.go so the validating webhook config can be generated
  • Removed the incorrect check that denied datasets with empty mounts — Mounts is +optional in the spec (e.g. Vineyard runtime doesn't need external storage)
  • Dropped the raw-JSON fallback path; the handler now uses the decoder exclusively
  • Added a guard for DELETE requests where Object.Raw is empty
  • Rewrote tests as table-driven with proper TypeMeta/ObjectMeta — all 6 passing

Could someone add ok-to-test so CI can run? Happy to make any further changes.

@cheyang @TrafalgarZZZ

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 8, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants