github.com/oam-dev/kubevela@v1.9.11/pkg/webhook/core.oam.dev/v1beta1/traitdefinition/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 traitdefinition 18 19 import ( 20 "context" 21 "fmt" 22 "net/http" 23 24 "github.com/pkg/errors" 25 admissionv1 "k8s.io/api/admission/v1" 26 "k8s.io/klog/v2" 27 "sigs.k8s.io/controller-runtime/pkg/client" 28 "sigs.k8s.io/controller-runtime/pkg/manager" 29 "sigs.k8s.io/controller-runtime/pkg/runtime/inject" 30 "sigs.k8s.io/controller-runtime/pkg/webhook" 31 "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 32 33 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 34 "github.com/oam-dev/kubevela/pkg/appfile" 35 controller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev" 36 "github.com/oam-dev/kubevela/pkg/oam" 37 webhookutils "github.com/oam-dev/kubevela/pkg/webhook/utils" 38 ) 39 40 const ( 41 errValidateDefRef = "error occurs when validating definition reference" 42 43 failInfoDefRefOmitted = "if definition reference is omitted, patch or output with GVK is required" 44 ) 45 46 var traitDefGVR = v1beta1.SchemeGroupVersion.WithResource("traitdefinitions") 47 48 // ValidatingHandler handles validation of trait definition 49 type ValidatingHandler struct { 50 Client client.Client 51 52 // Decoder decodes object 53 Decoder *admission.Decoder 54 // Validators validate objects 55 Validators []TraitDefValidator 56 } 57 58 // TraitDefValidator validate trait definition 59 type TraitDefValidator interface { 60 Validate(context.Context, v1beta1.TraitDefinition) error 61 } 62 63 // TraitDefValidatorFn implements TraitDefValidator 64 type TraitDefValidatorFn func(context.Context, v1beta1.TraitDefinition) error 65 66 // Validate implements TraitDefValidator method 67 func (fn TraitDefValidatorFn) Validate(ctx context.Context, td v1beta1.TraitDefinition) error { 68 return fn(ctx, td) 69 } 70 71 var _ admission.Handler = &ValidatingHandler{} 72 73 // Handle validate trait definition 74 func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response { 75 obj := &v1beta1.TraitDefinition{} 76 if req.Resource.String() != traitDefGVR.String() { 77 return admission.Errored(http.StatusBadRequest, fmt.Errorf("expect resource to be %s", traitDefGVR)) 78 } 79 80 if req.Operation == admissionv1.Create || req.Operation == admissionv1.Update { 81 err := h.Decoder.Decode(req, obj) 82 if err != nil { 83 return admission.Errored(http.StatusBadRequest, err) 84 } 85 klog.Info("validating ", " name: ", obj.Name, " operation: ", string(req.Operation)) 86 for _, validator := range h.Validators { 87 if err := validator.Validate(ctx, *obj); err != nil { 88 klog.Info("validation failed ", " name: ", obj.Name, " errMsgi: ", err.Error()) 89 return admission.Denied(err.Error()) 90 } 91 } 92 93 // validate cueTemplate 94 if obj.Spec.Schematic != nil && obj.Spec.Schematic.CUE != nil { 95 err = webhookutils.ValidateCueTemplate(obj.Spec.Schematic.CUE.Template) 96 if err != nil { 97 return admission.Denied(err.Error()) 98 } 99 } 100 101 revisionName := obj.GetAnnotations()[oam.AnnotationDefinitionRevisionName] 102 if len(revisionName) != 0 { 103 defRevName := fmt.Sprintf("%s-v%s", obj.Name, revisionName) 104 err = webhookutils.ValidateDefinitionRevision(ctx, h.Client, obj, client.ObjectKey{Namespace: obj.Namespace, Name: defRevName}) 105 if err != nil { 106 return admission.Denied(err.Error()) 107 } 108 } 109 klog.Info("validation passed ", " name: ", obj.Name, " operation: ", string(req.Operation)) 110 } 111 return admission.ValidationResponse(true, "") 112 } 113 114 var _ inject.Client = &ValidatingHandler{} 115 116 // InjectClient injects the client into the ValidatingHandler 117 func (h *ValidatingHandler) InjectClient(c client.Client) error { 118 h.Client = c 119 return nil 120 } 121 122 var _ admission.DecoderInjector = &ValidatingHandler{} 123 124 // InjectDecoder injects the decoder into the ValidatingHandler 125 func (h *ValidatingHandler) InjectDecoder(d *admission.Decoder) error { 126 h.Decoder = d 127 return nil 128 } 129 130 // RegisterValidatingHandler will register TraitDefinition validation to webhook 131 func RegisterValidatingHandler(mgr manager.Manager, _ controller.Args) { 132 server := mgr.GetWebhookServer() 133 server.Register("/validating-core-oam-dev-v1alpha2-traitdefinitions", &webhook.Admission{Handler: &ValidatingHandler{ 134 Validators: []TraitDefValidator{ 135 TraitDefValidatorFn(ValidateDefinitionReference), 136 // add more validators here 137 }, 138 }}) 139 } 140 141 // ValidateDefinitionReference validates whether the trait definition is valid if 142 // its `.spec.reference` field is unset. 143 // It's valid if 144 // it has at least one output, and all outputs must have GVK 145 // or it has no output but has a patch 146 // or it has a patch and outputs, and all outputs must have GVK 147 // TODO(roywang) currently we only validate whether it contains CUE template. 148 // Further validation, e.g., output with GVK, valid patch, etc, remains to be done. 149 func ValidateDefinitionReference(_ context.Context, td v1beta1.TraitDefinition) error { 150 if len(td.Spec.Reference.Name) > 0 { 151 return nil 152 } 153 capability, err := appfile.ConvertTemplateJSON2Object(td.Name, td.Spec.Extension, td.Spec.Schematic) 154 if err != nil { 155 return errors.WithMessage(err, errValidateDefRef) 156 } 157 if capability.CueTemplate == "" { 158 return errors.New(failInfoDefRefOmitted) 159 160 } 161 return nil 162 }