Validate CustomResource

* convert our types to openAPI types
* update strategy to include crd
* use strategy to validate customresource
* add helper funcs
* Fix conversion of empty ref field
* add validation for forbidden fields
* add defaulting for schema field
* Validate CRD Schema
This commit is contained in:
Nikhita Raghunath 2017-06-23 05:02:23 +05:30
parent 64948dfc80
commit fd09c3dbb6
10 changed files with 524 additions and 9 deletions

View File

@ -24,6 +24,7 @@ go_library(
],
deps = [
"//vendor/github.com/gogo/protobuf/proto:go_default_library",
"//vendor/github.com/gogo/protobuf/sortkeys:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",

View File

@ -10,10 +10,13 @@ go_library(
name = "go_default_library",
srcs = ["validation.go"],
deps = [
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)

View File

@ -20,10 +20,15 @@ import (
"fmt"
"strings"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"github.com/go-openapi/spec"
genericvalidation "k8s.io/apimachinery/pkg/api/validation"
validationutil "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
)
// ValidateCustomResourceDefinition statically validates
@ -100,6 +105,12 @@ func ValidateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefi
allErrs = append(allErrs, ValidateCustomResourceDefinitionNames(&spec.Names, fldPath.Child("names"))...)
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceValidation) {
allErrs = append(allErrs, ValidateCustomResourceDefinitionValidation(spec.Validation, fldPath.Child("validation"))...)
} else if spec.Validation != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("validation"), "disabled by feature-gate"))
}
return allErrs
}
@ -158,3 +169,162 @@ func ValidateCustomResourceDefinitionNames(names *apiextensions.CustomResourceDe
return allErrs
}
// specStandardValidator applies validations for different OpenAPI specfication versions.
type specStandardValidator interface {
validate(spec *apiextensions.JSONSchemaProps, fldPath *field.Path) field.ErrorList
}
// ValidateCustomResourceDefinitionValidation statically validates
func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiextensions.CustomResourceValidation, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if customResourceValidation == nil {
return allErrs
}
if customResourceValidation.OpenAPIV3Schema != nil {
openAPIV3Schema := &specStandardValidatorV3{}
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(customResourceValidation.OpenAPIV3Schema, fldPath.Child("openAPIV3Schema"), openAPIV3Schema)...)
}
return allErrs
}
// ValidateCustomResourceDefinitionOpenAPISchema statically validates
func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSchemaProps, fldPath *field.Path, ssv specStandardValidator) field.ErrorList {
allErrs := field.ErrorList{}
if schema == nil {
return allErrs
}
allErrs = append(allErrs, ssv.validate(schema, fldPath)...)
if schema.UniqueItems == true {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("uniqueItems"), "uniqueItems cannot be set to true since the runtime complexity becomes quadratic"))
}
// additionalProperties contradicts Kubernetes API convention to ignore unknown fields
if schema.AdditionalProperties != nil {
if schema.AdditionalProperties.Allows == false {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "additionalProperties cannot be set to false"))
}
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema.AdditionalProperties.Schema, fldPath.Child("additionalProperties"), ssv)...)
}
if schema.Ref != nil {
openapiRef, err := spec.NewRef(*schema.Ref)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("ref"), *schema.Ref, err.Error()))
}
if !openapiRef.IsValidURI() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("ref"), *schema.Ref, "ref does not point to a valid URI"))
}
}
if schema.AdditionalItems != nil {
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema.AdditionalItems.Schema, fldPath.Child("additionalItems"), ssv)...)
}
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema.Not, fldPath.Child("not"), ssv)...)
if len(schema.AllOf) != 0 {
for _, jsonSchema := range schema.AllOf {
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(&jsonSchema, fldPath.Child("allOf"), ssv)...)
}
}
if len(schema.OneOf) != 0 {
for _, jsonSchema := range schema.OneOf {
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(&jsonSchema, fldPath.Child("oneOf"), ssv)...)
}
}
if len(schema.AnyOf) != 0 {
for _, jsonSchema := range schema.AnyOf {
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(&jsonSchema, fldPath.Child("anyOf"), ssv)...)
}
}
if len(schema.Properties) != 0 {
for property, jsonSchema := range schema.Properties {
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(&jsonSchema, fldPath.Child("properties").Key(property), ssv)...)
}
}
if len(schema.PatternProperties) != 0 {
for property, jsonSchema := range schema.PatternProperties {
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(&jsonSchema, fldPath.Child("patternProperties").Key(property), ssv)...)
}
}
if len(schema.Definitions) != 0 {
for definition, jsonSchema := range schema.Definitions {
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(&jsonSchema, fldPath.Child("definitions").Key(definition), ssv)...)
}
}
if schema.Items != nil {
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema.Items.Schema, fldPath.Child("items"), ssv)...)
if len(schema.Items.JSONSchemas) != 0 {
for _, jsonSchema := range schema.Items.JSONSchemas {
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(&jsonSchema, fldPath.Child("items"), ssv)...)
}
}
}
if schema.Dependencies != nil {
for dependency, jsonSchemaPropsOrStringArray := range schema.Dependencies {
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(jsonSchemaPropsOrStringArray.Schema, fldPath.Child("dependencies").Key(dependency), ssv)...)
}
}
return allErrs
}
type specStandardValidatorV3 struct{}
// validate validates against OpenAPI Schema v3.
func (v *specStandardValidatorV3) validate(schema *apiextensions.JSONSchemaProps, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if schema == nil {
return allErrs
}
if schema.Default != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), "default is not supported"))
}
if schema.ID != "" {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("id"), "id is not supported"))
}
if schema.AdditionalItems != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalItems"), "additionalItems is not supported"))
}
if len(schema.PatternProperties) != 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("patternProperties"), "patternProperties is not supported"))
}
if len(schema.Definitions) != 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("definitions"), "definitions is not supported"))
}
if schema.Dependencies != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("dependencies"), "dependencies is not supported"))
}
if schema.Type == "null" {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("type"), "type cannot be set to null"))
}
if schema.Items != nil && len(schema.Items.JSONSchemas) != 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("items"), "items must be a schema object and not an array"))
}
return allErrs
}

View File

@ -14,10 +14,14 @@ go_library(
"customresource_handler.go",
],
deps = [
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/go-openapi/strfmt:go_default_library",
"//vendor/github.com/go-openapi/validate:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions:go_default_library",
@ -68,6 +72,9 @@ filegroup(
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
srcs = [
":package-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -171,6 +171,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
groupDiscoveryHandler,
s.GenericAPIServer.RequestContextMapper(),
s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions().Lister(),
s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(),
delegateHandler,
c.CRDRESTOptionsGetter,
c.GenericConfig.AdmissionControl,

View File

@ -26,6 +26,11 @@ import (
"sync/atomic"
"time"
openapispec "github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"github.com/golang/glog"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -44,8 +49,11 @@ import (
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/client-go/discovery"
cache "k8s.io/client-go/tools/cache"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
"k8s.io/apiextensions-apiserver/pkg/controller/finalizer"
"k8s.io/apiextensions-apiserver/pkg/registry/customresource"
@ -84,6 +92,7 @@ func NewCustomResourceDefinitionHandler(
groupDiscoveryHandler *groupDiscoveryHandler,
requestContextMapper apirequest.RequestContextMapper,
crdLister listers.CustomResourceDefinitionLister,
crdInformer informers.CustomResourceDefinitionInformer,
delegate http.Handler,
restOptionsGetter generic.RESTOptionsGetter,
admission admission.Interface) *crdHandler {
@ -98,6 +107,10 @@ func NewCustomResourceDefinitionHandler(
admission: admission,
}
crdInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
UpdateFunc: ret.updateCustomResourceDefinition,
})
ret.customStorage.Store(crdStorageMap{})
return ret
}
@ -155,7 +168,12 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
terminating := apiextensions.IsCRDConditionTrue(crd, apiextensions.Terminating)
crdInfo := r.getServingInfoFor(crd)
crdInfo, err := r.getServingInfoFor(crd)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
storage := crdInfo.storage
requestScope := crdInfo.requestScope
minRequestTimeout := 1 * time.Minute
@ -250,15 +268,18 @@ func (r *crdHandler) removeDeadStorage() {
// GetCustomResourceListerCollectionDeleter returns the ListerCollectionDeleter for
// the given uid, or nil if one does not exist.
func (r *crdHandler) GetCustomResourceListerCollectionDeleter(crd *apiextensions.CustomResourceDefinition) finalizer.ListerCollectionDeleter {
info := r.getServingInfoFor(crd)
info, err := r.getServingInfoFor(crd)
if err != nil {
utilruntime.HandleError(err)
}
return info.storage
}
func (r *crdHandler) getServingInfoFor(crd *apiextensions.CustomResourceDefinition) *crdInfo {
func (r *crdHandler) getServingInfoFor(crd *apiextensions.CustomResourceDefinition) (*crdInfo, error) {
storageMap := r.customStorage.Load().(crdStorageMap)
ret, ok := storageMap[crd.UID]
if ok {
return ret
return ret, nil
}
r.customStorageLock.Lock()
@ -266,7 +287,7 @@ func (r *crdHandler) getServingInfoFor(crd *apiextensions.CustomResourceDefiniti
ret, ok = storageMap[crd.UID]
if ok {
return ret
return ret, nil
}
// In addition to Unstructured objects (Custom Resources), we also may sometimes need to
@ -287,6 +308,17 @@ func (r *crdHandler) getServingInfoFor(crd *apiextensions.CustomResourceDefiniti
unstructuredTyper: discovery.NewUnstructuredObjectTyper(nil),
}
creator := unstructuredCreator{}
// convert CRD schema to openapi schema
openapiSchema := &openapispec.Schema{}
if err := apiservervalidation.ConvertToOpenAPITypes(crd, openapiSchema); err != nil {
return nil, err
}
if err := openapispec.ExpandSchema(openapiSchema, nil, nil); err != nil {
return nil, err
}
validator := validate.NewSchemaValidator(openapiSchema, nil, "", strfmt.Default)
storage := customresource.NewREST(
schema.GroupResource{Group: crd.Spec.Group, Resource: crd.Spec.Names.Plural},
schema.GroupVersionKind{Group: crd.Spec.Group, Version: crd.Spec.Version, Kind: crd.Spec.Names.ListKind},
@ -295,6 +327,7 @@ func (r *crdHandler) getServingInfoFor(crd *apiextensions.CustomResourceDefiniti
typer,
crd.Spec.Scope == apiextensions.NamespaceScoped,
kind,
validator,
),
r.restOptionsGetter,
)
@ -354,7 +387,27 @@ func (r *crdHandler) getServingInfoFor(crd *apiextensions.CustomResourceDefiniti
storageMap2[crd.UID] = ret
r.customStorage.Store(storageMap2)
return ret
return ret, nil
}
func (c *crdHandler) updateCustomResourceDefinition(oldObj, _ interface{}) {
oldCRD := oldObj.(*apiextensions.CustomResourceDefinition)
glog.V(4).Infof("Updating customresourcedefinition %s", oldCRD.Name)
c.customStorageLock.Lock()
defer c.customStorageLock.Unlock()
storageMap := c.customStorage.Load().(crdStorageMap)
storageMap2 := make(crdStorageMap, len(storageMap))
// Copy because we cannot write to storageMap without a race
// as it is used without locking elsewhere
for k, v := range storageMap {
storageMap2[k] = v
}
delete(storageMap2, oldCRD.UID)
c.customStorage.Store(storageMap2)
}
type unstructuredNegotiatedSerializer struct {

View File

@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["validation.go"],
tags = ["automanaged"],
deps = [
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/go-openapi/validate:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,217 @@
/*
Copyright 2017 The Kubernetes 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 validation
import (
"github.com/go-openapi/spec"
"github.com/go-openapi/validate"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
)
// ValidateCustomResource validates the Custom Resource against the schema in the CustomResourceDefinition.
// CustomResource is a JSON data structure.
func ValidateCustomResource(customResource interface{}, validator *validate.SchemaValidator) error {
result := validator.Validate(customResource)
if result.AsError() != nil {
return result.AsError()
}
return nil
}
// ConvertToOpenAPITypes is used to convert internal types to go-openapi types.
func ConvertToOpenAPITypes(in *apiextensions.CustomResourceDefinition, out *spec.Schema) error {
if in.Spec.Validation != nil {
if err := convertJSONSchemaProps(in.Spec.Validation.OpenAPIV3Schema, out); err != nil {
return err
}
}
return nil
}
func convertJSONSchemaProps(in *apiextensions.JSONSchemaProps, out *spec.Schema) error {
if in == nil {
return nil
}
out.ID = in.ID
out.Schema = spec.SchemaURL(in.Schema)
out.Description = in.Description
if in.Type != "" {
out.Type = spec.StringOrArray([]string{in.Type})
}
out.Format = in.Format
out.Title = in.Title
out.ExclusiveMaximum = in.ExclusiveMaximum
out.Minimum = in.Minimum
out.ExclusiveMinimum = in.ExclusiveMinimum
out.MaxLength = in.MaxLength
out.MinLength = in.MinLength
out.Pattern = in.Pattern
out.MaxItems = in.MaxItems
out.MinItems = in.MinItems
out.UniqueItems = in.UniqueItems
out.MultipleOf = in.MultipleOf
out.MaxProperties = in.MaxProperties
out.MinProperties = in.MinProperties
out.Required = in.Required
if in.Default != nil {
out.Default = *(in.Default)
}
if in.Example != nil {
out.Example = *(in.Example)
}
out.Enum = make([]interface{}, len(in.Enum))
for k, v := range in.Enum {
out.Enum[k] = v
}
if err := convertJSONSchemaPropsOrArray(in.Items, out.Items); err != nil {
return err
}
if err := convertSliceOfJSONSchemaProps(&in.AllOf, &out.AllOf); err != nil {
return err
}
if err := convertSliceOfJSONSchemaProps(&in.OneOf, &out.OneOf); err != nil {
return err
}
if err := convertSliceOfJSONSchemaProps(&in.AnyOf, &out.AnyOf); err != nil {
return err
}
if err := convertJSONSchemaProps(in.Not, out.Not); err != nil {
return err
}
var err error
out.Properties, err = convertMapOfJSONSchemaProps(in.Properties)
if err != nil {
return err
}
out.PatternProperties, err = convertMapOfJSONSchemaProps(in.PatternProperties)
if err != nil {
return err
}
if in.Ref != nil {
out.Ref, err = spec.NewRef(*in.Ref)
if err != nil {
return err
}
}
if err := convertJSONSchemaPropsorBool(in.AdditionalProperties, out.AdditionalProperties); err != nil {
return err
}
if err := convertJSONSchemaPropsorBool(in.AdditionalItems, out.AdditionalItems); err != nil {
return err
}
if err := convertJSONSchemaDependencies(in.Dependencies, out.Dependencies); err != nil {
return err
}
out.Definitions, err = convertMapOfJSONSchemaProps(in.Definitions)
if err != nil {
return err
}
if in.ExternalDocs != nil {
out.ExternalDocs = &spec.ExternalDocumentation{}
out.ExternalDocs.Description = in.ExternalDocs.Description
out.ExternalDocs.URL = in.ExternalDocs.URL
}
return nil
}
func convertSliceOfJSONSchemaProps(in *[]apiextensions.JSONSchemaProps, out *[]spec.Schema) error {
if in != nil {
for _, jsonSchemaProps := range *in {
schema := spec.Schema{}
if err := convertJSONSchemaProps(&jsonSchemaProps, &schema); err != nil {
return err
}
*out = append(*out, schema)
}
}
return nil
}
func convertMapOfJSONSchemaProps(in map[string]apiextensions.JSONSchemaProps) (map[string]spec.Schema, error) {
out := make(map[string]spec.Schema)
if len(in) != 0 {
for k, jsonSchemaProps := range in {
schema := spec.Schema{}
if err := convertJSONSchemaProps(&jsonSchemaProps, &schema); err != nil {
return nil, err
}
out[k] = schema
}
}
return out, nil
}
func convertJSONSchemaPropsOrArray(in *apiextensions.JSONSchemaPropsOrArray, out *spec.SchemaOrArray) error {
if in != nil {
out.Schema = &spec.Schema{}
if err := convertJSONSchemaProps(in.Schema, out.Schema); err != nil {
return err
}
}
return nil
}
func convertJSONSchemaPropsorBool(in *apiextensions.JSONSchemaPropsOrBool, out *spec.SchemaOrBool) error {
if in != nil {
out = &spec.SchemaOrBool{}
out.Allows = in.Allows
out.Schema = &spec.Schema{}
if err := convertJSONSchemaProps(in.Schema, out.Schema); err != nil {
return err
}
}
return nil
}
func convertJSONSchemaPropsOrStringArray(in *apiextensions.JSONSchemaPropsOrStringArray, out *spec.SchemaOrStringArray) error {
if in != nil {
out.Property = in.Property
out.Schema = &spec.Schema{}
if err := convertJSONSchemaProps(in.Schema, out.Schema); err != nil {
return err
}
}
return nil
}
func convertJSONSchemaDependencies(in apiextensions.JSONSchemaDependencies, out spec.Dependencies) error {
if in != nil {
for k, v := range in {
schemaOrArray := spec.SchemaOrStringArray{}
if err := convertJSONSchemaPropsOrStringArray(&v, &schemaOrArray); err != nil {
return err
}
out[k] = schemaOrArray
}
}
return nil
}

View File

@ -12,6 +12,8 @@ go_library(
"strategy.go",
],
deps = [
"//vendor/github.com/go-openapi/validate:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",

View File

@ -19,9 +19,12 @@ package customresource
import (
"fmt"
"github.com/go-openapi/validate"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@ -30,6 +33,8 @@ import (
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names"
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
)
type customResourceDefinitionStorageStrategy struct {
@ -40,7 +45,7 @@ type customResourceDefinitionStorageStrategy struct {
validator customResourceValidator
}
func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.GroupVersionKind) customResourceDefinitionStorageStrategy {
func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.GroupVersionKind, validator *validate.SchemaValidator) customResourceDefinitionStorageStrategy {
return customResourceDefinitionStorageStrategy{
ObjectTyper: typer,
NameGenerator: names.SimpleNameGenerator,
@ -48,6 +53,7 @@ func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.Gr
validator: customResourceValidator{
namespaceScoped: namespaceScoped,
kind: kind,
validator: validator,
},
}
}
@ -113,6 +119,7 @@ func (a customResourceDefinitionStorageStrategy) MatchCustomResourceDefinitionSt
type customResourceValidator struct {
namespaceScoped bool
kind schema.GroupVersionKind
validator *validate.SchemaValidator
}
func (a customResourceValidator) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
@ -131,6 +138,17 @@ func (a customResourceValidator) Validate(ctx genericapirequest.Context, obj run
return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))}
}
customResourceObject, ok := obj.(*unstructured.Unstructured)
// this will never happen.
if !ok {
return field.ErrorList{field.Invalid(field.NewPath(""), customResourceObject, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", customResourceObject))}
}
customResource := customResourceObject.UnstructuredContent()
if err = apiservervalidation.ValidateCustomResource(customResource, a.validator); err != nil {
return field.ErrorList{field.Invalid(field.NewPath(""), customResource, err.Error())}
}
return validation.ValidateObjectMetaAccessor(accessor, a.namespaceScoped, validation.NameIsDNSSubdomain, field.NewPath("metadata"))
}
@ -154,5 +172,16 @@ func (a customResourceValidator) ValidateUpdate(ctx genericapirequest.Context, o
return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))}
}
customResourceObject, ok := obj.(*unstructured.Unstructured)
// this will never happen.
if !ok {
return field.ErrorList{field.Invalid(field.NewPath(""), customResourceObject, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", customResourceObject))}
}
customResource := customResourceObject.UnstructuredContent()
if err = apiservervalidation.ValidateCustomResource(customResource, a.validator); err != nil {
return field.ErrorList{field.Invalid(field.NewPath(""), customResource, err.Error())}
}
return validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))
}