github.com/oam-dev/kubevela@v1.9.11/pkg/webhook/core.oam.dev/v1beta1/componentdefinition/validating_handler.go (about)

     1  /*
     2   Copyright 2021. The KubeVela Authors.
     3  
     4   Licensed under the Apache License, Version 2.0 (the "License");
     5   you may not use this file except in compliance with the License.
     6   You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10   Unless required by applicable law or agreed to in writing, software
    11   distributed under the License is distributed on an "AS IS" BASIS,
    12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   See the License for the specific language governing permissions and
    14   limitations under the License.
    15  */
    16  
    17  package componentdefinition
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net/http"
    23  
    24  	admissionv1 "k8s.io/api/admission/v1"
    25  	"k8s.io/apimachinery/pkg/api/meta"
    26  	"sigs.k8s.io/controller-runtime/pkg/client"
    27  	"sigs.k8s.io/controller-runtime/pkg/manager"
    28  	"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
    29  	"sigs.k8s.io/controller-runtime/pkg/webhook"
    30  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    31  
    32  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    33  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    34  	"github.com/oam-dev/kubevela/pkg/oam"
    35  	"github.com/oam-dev/kubevela/pkg/oam/util"
    36  	webhookutils "github.com/oam-dev/kubevela/pkg/webhook/utils"
    37  )
    38  
    39  var componentDefGVR = v1beta1.SchemeGroupVersion.WithResource("componentdefinitions")
    40  
    41  // ValidatingHandler handles validation of component definition
    42  type ValidatingHandler struct {
    43  	// Decoder decodes object
    44  	Decoder *admission.Decoder
    45  	Client  client.Client
    46  }
    47  
    48  var _ inject.Client = &ValidatingHandler{}
    49  
    50  // InjectClient injects the client into the ApplicationValidateHandler
    51  func (h *ValidatingHandler) InjectClient(c client.Client) error {
    52  	if h.Client != nil {
    53  		return nil
    54  	}
    55  	h.Client = c
    56  	return nil
    57  }
    58  
    59  var _ admission.Handler = &ValidatingHandler{}
    60  
    61  // Handle validate component definition
    62  func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
    63  	obj := &v1beta1.ComponentDefinition{}
    64  	if req.Resource.String() != componentDefGVR.String() {
    65  		return admission.Errored(http.StatusBadRequest, fmt.Errorf("expect resource to be %s", componentDefGVR))
    66  	}
    67  
    68  	if req.Operation == admissionv1.Create || req.Operation == admissionv1.Update {
    69  		err := h.Decoder.Decode(req, obj)
    70  		if err != nil {
    71  			return admission.Errored(http.StatusBadRequest, err)
    72  		}
    73  		err = ValidateWorkload(h.Client.RESTMapper(), obj)
    74  		if err != nil {
    75  			return admission.Denied(err.Error())
    76  		}
    77  
    78  		// validate cueTemplate
    79  		if obj.Spec.Schematic != nil && obj.Spec.Schematic.CUE != nil {
    80  			err = webhookutils.ValidateCueTemplate(obj.Spec.Schematic.CUE.Template)
    81  			if err != nil {
    82  				return admission.Denied(err.Error())
    83  			}
    84  		}
    85  
    86  		revisionName := obj.GetAnnotations()[oam.AnnotationDefinitionRevisionName]
    87  		if len(revisionName) != 0 {
    88  			defRevName := fmt.Sprintf("%s-v%s", obj.Name, revisionName)
    89  			err = webhookutils.ValidateDefinitionRevision(ctx, h.Client, obj, client.ObjectKey{Namespace: obj.Namespace, Name: defRevName})
    90  			if err != nil {
    91  				return admission.Denied(err.Error())
    92  			}
    93  		}
    94  	}
    95  	return admission.ValidationResponse(true, "")
    96  }
    97  
    98  var _ admission.DecoderInjector = &ValidatingHandler{}
    99  
   100  // InjectDecoder injects the decoder into the ValidatingHandler
   101  func (h *ValidatingHandler) InjectDecoder(d *admission.Decoder) error {
   102  	h.Decoder = d
   103  	return nil
   104  }
   105  
   106  // RegisterValidatingHandler will register ComponentDefinition validation to webhook
   107  func RegisterValidatingHandler(mgr manager.Manager) {
   108  	server := mgr.GetWebhookServer()
   109  	server.Register("/validating-core-oam-dev-v1beta1-componentdefinitions", &webhook.Admission{Handler: &ValidatingHandler{}})
   110  }
   111  
   112  // ValidateWorkload validates whether the Workload field is valid
   113  func ValidateWorkload(mapper meta.RESTMapper, cd *v1beta1.ComponentDefinition) error {
   114  
   115  	// If the Type and Definition are all empty, it will be rejected.
   116  	if cd.Spec.Workload.Type == "" && cd.Spec.Workload.Definition == (common.WorkloadGVK{}) {
   117  		return fmt.Errorf("neither the type nor the definition of the workload field in the ComponentDefinition %s can be empty", cd.Name)
   118  	}
   119  
   120  	// if Type and Definitiondonā€˜t point to the same workloaddefinition, it will be rejected.
   121  	if cd.Spec.Workload.Type != "" && cd.Spec.Workload.Definition != (common.WorkloadGVK{}) {
   122  		defRef, err := util.ConvertWorkloadGVK2Definition(mapper, cd.Spec.Workload.Definition)
   123  		if err != nil {
   124  			return err
   125  		}
   126  		if defRef.Name != cd.Spec.Workload.Type {
   127  			return fmt.Errorf("the type and the definition of the workload field in ComponentDefinition %s should represent the same workload", cd.Name)
   128  		}
   129  	}
   130  	return nil
   131  }