Skip to content

Commit

Permalink
feat: new features about support warning with webhook
Browse files Browse the repository at this point in the history
Signed-off-by: STRRL <im@strrl.dev>
  • Loading branch information
STRRL committed Sep 29, 2022
1 parent 7399a3a commit 86c19e8
Show file tree
Hide file tree
Showing 5 changed files with 634 additions and 0 deletions.
54 changes: 54 additions & 0 deletions pkg/webhook/admission/admissiontest/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ package admissiontest
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

var _ runtime.Object = (*FakeValidator)(nil)
var _ schema.ObjectKind = (*FakeValidator)(nil)
var _ webhook.Validator = (*FakeValidator)(nil)

// FakeValidator provides fake validating webhook functionality for testing
// It implements the admission.Validator interface and
// rejects all requests with the same configured error
Expand Down Expand Up @@ -64,3 +69,52 @@ func (v *FakeValidator) GroupVersionKind() schema.GroupVersionKind {
func (v *FakeValidator) SetGroupVersionKind(gvk schema.GroupVersionKind) {
v.GVKToReturn = gvk
}

var _ runtime.Object = (*FakeValidatorWarn)(nil)
var _ schema.ObjectKind = (*FakeValidatorWarn)(nil)
var _ webhook.ValidatorWarn = (*FakeValidatorWarn)(nil)

// FakeValidatorWarn provides fake validating webhook functionality for testing
// It implements the admission.ValidatorWarn interface and
// rejects all requests with the same configured error
// or passes if ErrorToReturn is nil.
// And it would always return configured warning messages WarningsToReturn.
type FakeValidatorWarn struct {
// ErrorToReturn is the error for which the FakeValidatorWarn rejects all requests
ErrorToReturn error `json:"ErrorToReturn,omitempty"`
// GVKToReturn is the GroupVersionKind that the webhook operates on
GVKToReturn schema.GroupVersionKind
// WarningsToReturn is the warnings for FakeValidatorWarn returns to all requests
WarningsToReturn []string
}

func (v *FakeValidatorWarn) ValidateCreate() (err error, warnings []string) {
return v.ErrorToReturn, v.WarningsToReturn
}

func (v *FakeValidatorWarn) ValidateUpdate(old runtime.Object) (err error, warnings []string) {
return v.ErrorToReturn, v.WarningsToReturn
}

func (v *FakeValidatorWarn) ValidateDelete() (err error, warnings []string) {
return v.ErrorToReturn, v.WarningsToReturn
}

func (v *FakeValidatorWarn) SetGroupVersionKind(kind schema.GroupVersionKind) {
v.GVKToReturn = kind
}

func (v *FakeValidatorWarn) GroupVersionKind() schema.GroupVersionKind {
return v.GVKToReturn
}

func (v *FakeValidatorWarn) GetObjectKind() schema.ObjectKind {
return v
}

func (v *FakeValidatorWarn) DeepCopyObject() runtime.Object {
return &FakeValidatorWarn{ErrorToReturn: v.ErrorToReturn,
GVKToReturn: v.GVKToReturn,
WarningsToReturn: v.WarningsToReturn,
}
}
123 changes: 123 additions & 0 deletions pkg/webhook/admission/validator_warn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
Copyright 2022 The Kubernetes 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 admission

import (
"context"
goerrors "errors"
"net/http"

v1 "k8s.io/api/admission/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
)

// ValidatorWarn works like Validator, but it allows to return warnings.
type ValidatorWarn interface {
runtime.Object
ValidateCreate() (err error, warnings []string)
ValidateUpdate(old runtime.Object) (err error, warnings []string)
ValidateDelete() (err error, warnings []string)
}

func ValidatingWebhookWithWarningFor(validatorWarning ValidatorWarn) *Webhook {
return nil
}

var _ Handler = &validatingWarnHandler{}
var _ DecoderInjector = &validatingWarnHandler{}

type validatingWarnHandler struct {
validatorWarn ValidatorWarn
decoder *Decoder
}

// InjectDecoder injects the decoder into a validatingWarnHandler.
func (h *validatingWarnHandler) InjectDecoder(decoder *Decoder) error {
h.decoder = decoder
return nil
}

// Handle handles admission requests.
func (h *validatingWarnHandler) Handle(ctx context.Context, req Request) Response {
if h.validatorWarn == nil {
panic("validatorWarn should never be nil")
}

var allWarnings []string

// Get the object in the request
obj := h.validatorWarn.DeepCopyObject().(ValidatorWarn)
if req.Operation == v1.Create {
err := h.decoder.Decode(req, obj)
if err != nil {
return Errored(http.StatusBadRequest, err)
}

err, warnings := obj.ValidateCreate()
allWarnings = append(allWarnings, warnings...)
if err != nil {
var apiStatus apierrors.APIStatus
if goerrors.As(err, &apiStatus) {
return validationResponseFromStatus(false, apiStatus.Status())
}
return Denied(err.Error()).WithWarnings(allWarnings...)
}
}

if req.Operation == v1.Update {
oldObj := obj.DeepCopyObject()

err := h.decoder.DecodeRaw(req.Object, obj)
if err != nil {
return Errored(http.StatusBadRequest, err)
}
err = h.decoder.DecodeRaw(req.OldObject, oldObj)
if err != nil {
return Errored(http.StatusBadRequest, err)
}
err, warnings := obj.ValidateUpdate(oldObj)
allWarnings = append(allWarnings, warnings...)
if err != nil {
var apiStatus apierrors.APIStatus
if goerrors.As(err, &apiStatus) {
return validationResponseFromStatus(false, apiStatus.Status())
}
return Denied(err.Error()).WithWarnings(allWarnings...)
}
}

if req.Operation == v1.Delete {
// In reference to PR: https://github.com/kubernetes/kubernetes/pull/76346
// OldObject contains the object being deleted
err := h.decoder.DecodeRaw(req.OldObject, obj)
if err != nil {
return Errored(http.StatusBadRequest, err)
}

err, warnings := obj.ValidateDelete()
allWarnings = append(allWarnings, warnings...)
if err != nil {
var apiStatus apierrors.APIStatus
if goerrors.As(err, &apiStatus) {
return validationResponseFromStatus(false, apiStatus.Status())
}
return Denied(err.Error()).WithWarnings(allWarnings...)
}
}
return Allowed("").WithWarnings(allWarnings...)
}
112 changes: 112 additions & 0 deletions pkg/webhook/admission/validator_warn_custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
Copyright 2022 The Kubernetes 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 admission

import (
"context"
"errors"
"fmt"
"net/http"

v1 "k8s.io/api/admission/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
)

// CustomValidatorWarn works like CustomValidator, but it allows to return warnings.
type CustomValidatorWarn interface {
ValidateCreate(ctx context.Context, obj runtime.Object) (err error, warnings []string)
ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (err error, warnings []string)
ValidateDelete(ctx context.Context, obj runtime.Object) (err error, warnings []string)
}

// WithCustomValidatorWarn creates a new Webhook for validating the provided type.
func WithCustomValidatorWarn(obj runtime.Object, validatorWarn CustomValidatorWarn) *Webhook {
return &Webhook{
Handler: &validatorWarnForType{object: obj, validatorWarn: validatorWarn},
}
}

var _ Handler = (*validatorWarnForType)(nil)
var _ DecoderInjector = (*validatorWarnForType)(nil)

type validatorWarnForType struct {
validatorWarn CustomValidatorWarn
object runtime.Object
decoder *Decoder
}

func (h *validatorWarnForType) InjectDecoder(d *Decoder) error {
h.decoder = d
return nil
}

func (h *validatorWarnForType) Handle(ctx context.Context, req Request) Response {
if h.validatorWarn == nil {
panic("validatorWarn should never be nil")
}
if h.object == nil {
panic("object should never be nil")
}

ctx = NewContextWithRequest(ctx, req)

obj := h.object.DeepCopyObject()

var err error
var warnings []string
switch req.Operation {
case v1.Create:
if err := h.decoder.Decode(req, obj); err != nil {
return Errored(http.StatusBadRequest, err)
}

err, warnings = h.validatorWarn.ValidateCreate(ctx, obj)
case v1.Update:
oldObj := obj.DeepCopyObject()
if err := h.decoder.DecodeRaw(req.Object, obj); err != nil {
return Errored(http.StatusBadRequest, err)
}
if err := h.decoder.DecodeRaw(req.OldObject, oldObj); err != nil {
return Errored(http.StatusBadRequest, err)
}

err, warnings = h.validatorWarn.ValidateUpdate(ctx, oldObj, obj)
case v1.Delete:
// In reference to PR: https://github.com/kubernetes/kubernetes/pull/76346
// OldObject contains the object being deleted
if err := h.decoder.DecodeRaw(req.OldObject, obj); err != nil {
return Errored(http.StatusBadRequest, err)
}

err, warnings = h.validatorWarn.ValidateDelete(ctx, obj)
default:
return Errored(http.StatusBadRequest, fmt.Errorf("unknown operation request %q", req.Operation))
}

// Check the error message first.
if err != nil {
var apiStatus apierrors.APIStatus
if errors.As(err, &apiStatus) {
return validationResponseFromStatus(false, apiStatus.Status())
}
return Denied(err.Error()).WithWarnings(warnings...)
}

// Return allowed if everything succeeded.
return Allowed("").WithWarnings(warnings...)
}
Loading

0 comments on commit 86c19e8

Please sign in to comment.