pkg/webhook/admission_controller_interface.go (87 lines of code) (raw):
// Copyright (c) 2021, 2023, Oracle and/or its affiliates.
//
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
package webhook
import (
"fmt"
"regexp"
admissionv1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
klog "k8s.io/klog/v2"
)
type admissionController interface {
getGVR() *metav1.GroupVersionResource
getGVK() *schema.GroupVersionKind
newObject() runtime.Object
// validate functions should validate the request and return a AdmissionResponse
validateCreate(reqUID types.UID, obj runtime.Object) *admissionv1.AdmissionResponse
validateUpdate(reqUID types.UID, obj runtime.Object, oldObj runtime.Object) *admissionv1.AdmissionResponse
// mutate function should return the JSONPatch that needs to be applied to the resource
mutate(obj runtime.Object) *jsonPatchOperations
}
func unsupportedValidatorOperation(reqUID types.UID, operation admissionv1.Operation) *admissionv1.AdmissionResponse {
errMsg := fmt.Sprintf("validating a %s operation not supported", operation)
klog.Error(errMsg)
return requestDeniedBad(reqUID, errMsg)
}
type requestExecutor func(req *admissionv1.AdmissionRequest, ac admissionController) *admissionv1.AdmissionResponse
func validate(req *admissionv1.AdmissionRequest, ac admissionController) *admissionv1.AdmissionResponse {
// Verify right resource is passed
resource := ac.getGVR()
if req.Resource != *resource {
errMsg := fmt.Sprintf("expected resource %v but got %v", *resource, req.Resource)
return requestDeniedBad(req.UID, errMsg)
}
// Handle operation
defaultGVK := ac.getGVK()
decoder := scheme.Codecs.UniversalDeserializer()
switch req.Operation {
case admissionv1.Create:
// retrieve new object and validate it
obj, _, err := decoder.Decode(req.Object.Raw, defaultGVK, ac.newObject())
if err != nil {
return requestDeniedBad(req.UID, err.Error())
}
klog.V(5).Info(fmt.Sprintf("Retrieved new object : %v", obj))
return ac.validateCreate(req.UID, obj)
case admissionv1.Update:
// any updates made from the ndb-operator can be accepted without validation
if updateFromNdbOperator, _ := regexp.MatchString(
"system:serviceaccount:.*:ndb-operator", req.UserInfo.Username); updateFromNdbOperator {
klog.Info("Skipping validation for an update from ndb-operator")
return requestAllowed(req.UID)
}
// retrieve new and old objects
obj, _, err := decoder.Decode(req.Object.Raw, defaultGVK, ac.newObject())
if err != nil {
return requestDeniedBad(req.UID, err.Error())
}
klog.V(5).Info(fmt.Sprintf("Retrieved new object : %v", obj))
oldObject, _, err := decoder.Decode(req.OldObject.Raw, defaultGVK, ac.newObject())
if err != nil {
return requestDeniedBad(req.UID, err.Error())
}
klog.V(5).Info(fmt.Sprintf("Retrieved old object : %v", oldObject))
// validate the update
return ac.validateUpdate(req.UID, obj, oldObject)
default:
return unsupportedValidatorOperation(req.UID, req.Operation)
}
}
func mutate(req *admissionv1.AdmissionRequest, ac admissionController) *admissionv1.AdmissionResponse {
// Verify right resource is passed
resource := ac.getGVR()
if req.Resource != *resource {
errMsg := fmt.Sprintf("expected resource %v but got %v", *resource, req.Resource)
return requestDeniedBad(req.UID, errMsg)
}
// Decode the object and mutate
defaultGVK := ac.getGVK()
decoder := scheme.Codecs.UniversalDeserializer()
// retrieve new object and mutate it
obj, _, err := decoder.Decode(req.Object.Raw, defaultGVK, ac.newObject())
if err != nil {
return requestDeniedBad(req.UID, err.Error())
}
// Call the admissions controller's mutate method
patchOps := ac.mutate(obj)
if patchOps.empty() {
// Nothing to do
return requestAllowed(req.UID)
}
// A patch is available for mutation
patch, err := patchOps.getPatch()
if err != nil {
klog.Error("Failed to encode json patch :", err.Error())
return requestDeniedBad(req.UID, err.Error())
}
klog.Infof("JSONPatch `%s` will be applied to resource '%s/%s'", string(patch), req.Namespace, req.Name)
return requestAllowedWithPatch(req.UID, patch)
}