| // 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 crd |
| |
| import ( |
| "fmt" |
| "io" |
| "os" |
| "path/filepath" |
| ) |
| |
| import ( |
| "github.com/hashicorp/go-multierror" |
| "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" |
| apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" |
| apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" |
| structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" |
| structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting" |
| "k8s.io/apiextensions-apiserver/pkg/apiserver/validation" |
| "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apimachinery/pkg/runtime/schema" |
| kubeyaml "k8s.io/apimachinery/pkg/util/yaml" |
| "k8s.io/kube-openapi/pkg/validation/validate" |
| "sigs.k8s.io/yaml" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/pkg/test" |
| "github.com/apache/dubbo-go-pixiu/pkg/test/env" |
| "github.com/apache/dubbo-go-pixiu/pkg/test/util/yml" |
| ) |
| |
| // Validator returns a new validator for custom resources |
| // Warning: this is meant for usage in tests only |
| type Validator struct { |
| byGvk map[schema.GroupVersionKind]*validate.SchemaValidator |
| structural map[schema.GroupVersionKind]*structuralschema.Structural |
| // If enabled, resources without a validator will be ignored. Otherwise, they will fail. |
| SkipMissing bool |
| } |
| |
| func (v *Validator) ValidateCustomResourceYAML(data string) error { |
| var errs *multierror.Error |
| for _, item := range yml.SplitString(data) { |
| obj := &unstructured.Unstructured{} |
| if err := yaml.Unmarshal([]byte(item), obj); err != nil { |
| return err |
| } |
| errs = multierror.Append(errs, v.ValidateCustomResource(obj)) |
| } |
| return errs.ErrorOrNil() |
| } |
| |
| func (v *Validator) ValidateCustomResource(o runtime.Object) error { |
| content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(o) |
| if err != nil { |
| return err |
| } |
| |
| un := &unstructured.Unstructured{Object: content} |
| vd, f := v.byGvk[un.GroupVersionKind()] |
| if !f { |
| if v.SkipMissing { |
| return nil |
| } |
| return fmt.Errorf("failed to validate type %v: no validator found", un.GroupVersionKind()) |
| } |
| // Fill in defaults |
| structuraldefaulting.Default(un.Object, v.structural[un.GroupVersionKind()]) |
| if err := validation.ValidateCustomResource(nil, un.Object, vd).ToAggregate(); err != nil { |
| return fmt.Errorf("%v/%v/%v: %v", un.GroupVersionKind().Kind, un.GetName(), un.GetNamespace(), err) |
| } |
| return nil |
| } |
| |
| func NewValidatorFromFiles(files ...string) (*Validator, error) { |
| crds := []apiextensions.CustomResourceDefinition{} |
| closers := make([]io.Closer, 0, len(files)) |
| defer func() { |
| for _, closer := range closers { |
| closer.Close() |
| } |
| }() |
| for _, file := range files { |
| data, err := os.Open(file) |
| if err != nil { |
| return nil, fmt.Errorf("failed to read input yaml file: %v", err) |
| } |
| closers = append(closers, data) |
| |
| yamlDecoder := kubeyaml.NewYAMLOrJSONDecoder(data, 512*1024) |
| for { |
| un := &unstructured.Unstructured{} |
| err = yamlDecoder.Decode(&un) |
| if err == io.EOF { |
| break |
| } |
| if err != nil { |
| return nil, err |
| } |
| crd := apiextensions.CustomResourceDefinition{} |
| switch un.GroupVersionKind() { |
| case schema.GroupVersionKind{ |
| Group: "apiextensions.k8s.io", |
| Version: "v1", |
| Kind: "CustomResourceDefinition", |
| }: |
| crdv1 := apiextensionsv1.CustomResourceDefinition{} |
| if err := runtime.DefaultUnstructuredConverter. |
| FromUnstructured(un.UnstructuredContent(), &crdv1); err != nil { |
| return nil, err |
| } |
| if err := apiextensionsv1.Convert_v1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(&crdv1, &crd, nil); err != nil { |
| return nil, err |
| } |
| case schema.GroupVersionKind{ |
| Group: "apiextensions.k8s.io", |
| Version: "v1beta1", |
| Kind: "CustomResourceDefinition", |
| }: |
| crdv1beta1 := apiextensionsv1beta1.CustomResourceDefinition{} |
| if err := runtime.DefaultUnstructuredConverter. |
| FromUnstructured(un.UnstructuredContent(), &crdv1beta1); err != nil { |
| return nil, err |
| } |
| if err := apiextensionsv1beta1.Convert_v1beta1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(&crdv1beta1, &crd, nil); err != nil { |
| return nil, err |
| } |
| default: |
| return nil, fmt.Errorf("unknown CRD type: %v", un.GroupVersionKind()) |
| } |
| crds = append(crds, crd) |
| } |
| } |
| return NewValidatorFromCRDs(crds...) |
| } |
| |
| func NewValidatorFromCRDs(crds ...apiextensions.CustomResourceDefinition) (*Validator, error) { |
| v := &Validator{ |
| byGvk: map[schema.GroupVersionKind]*validate.SchemaValidator{}, |
| structural: map[schema.GroupVersionKind]*structuralschema.Structural{}, |
| } |
| for _, crd := range crds { |
| versions := crd.Spec.Versions |
| if len(versions) == 0 { |
| versions = []apiextensions.CustomResourceDefinitionVersion{{Name: crd.Spec.Version}} // nolint: staticcheck |
| } |
| for _, ver := range versions { |
| gvk := schema.GroupVersionKind{ |
| Group: crd.Spec.Group, |
| Version: ver.Name, |
| Kind: crd.Spec.Names.Kind, |
| } |
| crdSchema := ver.Schema |
| if crdSchema == nil { |
| crdSchema = crd.Spec.Validation |
| } |
| if crdSchema == nil { |
| return nil, fmt.Errorf("crd did not have validation defined") |
| } |
| |
| schemaValidator, _, err := validation.NewSchemaValidator(crdSchema) |
| if err != nil { |
| return nil, err |
| } |
| structural, err := structuralschema.NewStructural(crdSchema.OpenAPIV3Schema) |
| if err != nil { |
| return nil, err |
| } |
| |
| v.byGvk[gvk] = schemaValidator |
| v.structural[gvk] = structural |
| } |
| } |
| |
| return v, nil |
| } |
| |
| func NewIstioValidator(t test.Failer) *Validator { |
| v, err := NewValidatorFromFiles( |
| filepath.Join(env.IstioSrc, "tests/integration/pilot/testdata/gateway-api-crd.yaml"), |
| filepath.Join(env.IstioSrc, "manifests/charts/base/crds/crd-all.gen.yaml")) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return v |
| } |