| // Copyright Istio 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 validate |
| |
| import ( |
| "fmt" |
| "reflect" |
| ) |
| |
| import ( |
| "google.golang.org/protobuf/types/known/structpb" |
| "istio.io/api/operator/v1alpha1" |
| ) |
| |
| import ( |
| operator_v1alpha1 "github.com/apache/dubbo-go-pixiu/operator/pkg/apis/istio/v1alpha1" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/metrics" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/tpath" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/util" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/labels" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/mesh" |
| "github.com/apache/dubbo-go-pixiu/pkg/util/protomarshal" |
| ) |
| |
| var ( |
| // DefaultValidations maps a data path to a validation function. |
| DefaultValidations = map[string]ValidatorFunc{ |
| "Values": func(path util.Path, i interface{}) util.Errors { |
| return CheckValues(i) |
| }, |
| "MeshConfig": validateMeshConfig, |
| "Hub": validateHub, |
| "Tag": validateTag, |
| "Revision": validateRevision, |
| "Components.IngressGateways": validateGatewayName, |
| "Components.EgressGateways": validateGatewayName, |
| } |
| // requiredValues lists all the values that must be non-empty. |
| requiredValues = map[string]bool{} |
| ) |
| |
| // CheckIstioOperator validates the operator CR. |
| func CheckIstioOperator(iop *operator_v1alpha1.IstioOperator, checkRequiredFields bool) error { |
| if iop == nil { |
| return nil |
| } |
| |
| errs := CheckIstioOperatorSpec(iop.Spec, checkRequiredFields) |
| return errs.ToError() |
| } |
| |
| // CheckIstioOperatorSpec validates the values in the given Installer spec, using the field map DefaultValidations to |
| // call the appropriate validation function. checkRequiredFields determines whether missing mandatory fields generate |
| // errors. |
| func CheckIstioOperatorSpec(is *v1alpha1.IstioOperatorSpec, checkRequiredFields bool) (errs util.Errors) { |
| if is == nil { |
| return util.Errors{} |
| } |
| |
| return Validate2(DefaultValidations, is) |
| } |
| |
| func Validate2(validations map[string]ValidatorFunc, iop *v1alpha1.IstioOperatorSpec) (errs util.Errors) { |
| for path, validator := range validations { |
| v, f, _ := tpath.GetFromStructPath(iop, path) |
| if f { |
| errs = append(errs, validator(util.PathFromString(path), v)...) |
| } |
| } |
| return |
| } |
| |
| // Validate function below is used by third party for integrations and has to be public |
| |
| // Validate validates the values of the tree using the supplied Func. |
| func Validate(validations map[string]ValidatorFunc, structPtr interface{}, path util.Path, checkRequired bool) (errs util.Errors) { |
| scope.Debugf("validate with path %s, %v (%T)", path, structPtr, structPtr) |
| if structPtr == nil { |
| return nil |
| } |
| if util.IsStruct(structPtr) { |
| scope.Debugf("validate path %s, skipping struct type %T", path, structPtr) |
| return nil |
| } |
| if !util.IsPtr(structPtr) { |
| metrics.CRValidationErrorTotal.Increment() |
| return util.NewErrs(fmt.Errorf("validate path %s, value: %v, expected ptr, got %T", path, structPtr, structPtr)) |
| } |
| structElems := reflect.ValueOf(structPtr).Elem() |
| if !util.IsStruct(structElems) { |
| metrics.CRValidationErrorTotal.Increment() |
| return util.NewErrs(fmt.Errorf("validate path %s, value: %v, expected struct, got %T", path, structElems, structElems)) |
| } |
| |
| if util.IsNilOrInvalidValue(structElems) { |
| return |
| } |
| |
| for i := 0; i < structElems.NumField(); i++ { |
| fieldName := structElems.Type().Field(i).Name |
| fieldValue := structElems.Field(i) |
| if !fieldValue.CanInterface() { |
| continue |
| } |
| kind := structElems.Type().Field(i).Type.Kind() |
| if a, ok := structElems.Type().Field(i).Tag.Lookup("json"); ok && a == "-" { |
| continue |
| } |
| |
| scope.Debugf("Checking field %s", fieldName) |
| switch kind { |
| case reflect.Struct: |
| errs = util.AppendErrs(errs, Validate(validations, fieldValue.Addr().Interface(), append(path, fieldName), checkRequired)) |
| case reflect.Map: |
| newPath := append(path, fieldName) |
| errs = util.AppendErrs(errs, validateLeaf(validations, newPath, fieldValue.Interface(), checkRequired)) |
| for _, key := range fieldValue.MapKeys() { |
| nnp := append(newPath, key.String()) |
| errs = util.AppendErrs(errs, validateLeaf(validations, nnp, fieldValue.MapIndex(key), checkRequired)) |
| } |
| case reflect.Slice: |
| for i := 0; i < fieldValue.Len(); i++ { |
| newValue := fieldValue.Index(i).Interface() |
| newPath := append(path, indexPathForSlice(fieldName, i)) |
| if util.IsStruct(newValue) || util.IsPtr(newValue) { |
| errs = util.AppendErrs(errs, Validate(validations, newValue, newPath, checkRequired)) |
| } else { |
| errs = util.AppendErrs(errs, validateLeaf(validations, newPath, newValue, checkRequired)) |
| } |
| } |
| case reflect.Ptr: |
| if util.IsNilOrInvalidValue(fieldValue.Elem()) { |
| continue |
| } |
| newPath := append(path, fieldName) |
| if fieldValue.Elem().Kind() == reflect.Struct { |
| errs = util.AppendErrs(errs, Validate(validations, fieldValue.Interface(), newPath, checkRequired)) |
| } else { |
| errs = util.AppendErrs(errs, validateLeaf(validations, newPath, fieldValue, checkRequired)) |
| } |
| default: |
| if structElems.Field(i).CanInterface() { |
| errs = util.AppendErrs(errs, validateLeaf(validations, append(path, fieldName), fieldValue.Interface(), checkRequired)) |
| } |
| } |
| } |
| if len(errs) > 0 { |
| metrics.CRValidationErrorTotal.Increment() |
| } |
| return errs |
| } |
| |
| func validateLeaf(validations map[string]ValidatorFunc, path util.Path, val interface{}, checkRequired bool) util.Errors { |
| pstr := path.String() |
| msg := fmt.Sprintf("validate %s:%v(%T) ", pstr, val, val) |
| if util.IsValueNil(val) || util.IsEmptyString(val) { |
| if checkRequired && requiredValues[pstr] { |
| return util.NewErrs(fmt.Errorf("field %s is required but not set", util.ToYAMLPathString(pstr))) |
| } |
| msg += fmt.Sprintf("validate %s: OK (empty value)", pstr) |
| scope.Debug(msg) |
| return nil |
| } |
| |
| vf, ok := getValidationFuncForPath(validations, path) |
| if !ok { |
| msg += fmt.Sprintf("validate %s: OK (no validation)", pstr) |
| scope.Debug(msg) |
| // No validation defined. |
| return nil |
| } |
| scope.Debug(msg) |
| return vf(path, val) |
| } |
| |
| func validateMeshConfig(path util.Path, root interface{}) util.Errors { |
| vs, err := util.ToYAMLGeneric(root) |
| if err != nil { |
| return util.Errors{err} |
| } |
| // ApplyMeshConfigDefaults allows unknown fields, so we first check for unknown fields |
| if err := protomarshal.ApplyYAMLStrict(string(vs), mesh.DefaultMeshConfig()); err != nil { |
| return util.Errors{fmt.Errorf("failed to unmarshall mesh config: %v", err)} |
| } |
| // This method will also perform validation automatically |
| if _, validErr := mesh.ApplyMeshConfigDefaults(string(vs)); validErr != nil { |
| return util.Errors{validErr} |
| } |
| return nil |
| } |
| |
| func validateHub(path util.Path, val interface{}) util.Errors { |
| if val == "" { |
| return nil |
| } |
| return validateWithRegex(path, val, ReferenceRegexp) |
| } |
| |
| func validateTag(path util.Path, val interface{}) util.Errors { |
| return validateWithRegex(path, val.(*structpb.Value).GetStringValue(), TagRegexp) |
| } |
| |
| func validateRevision(_ util.Path, val interface{}) util.Errors { |
| if val == "" { |
| return nil |
| } |
| if !labels.IsDNS1123Label(val.(string)) { |
| err := fmt.Errorf("invalid revision specified: %s", val.(string)) |
| return util.Errors{err} |
| } |
| return nil |
| } |
| |
| func validateGatewayName(path util.Path, val interface{}) (errs util.Errors) { |
| v := val.([]*v1alpha1.GatewaySpec) |
| for _, n := range v { |
| errs = append(errs, validateWithRegex(path, n.Name, ObjectNameRegexp)...) |
| } |
| return |
| } |