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 }