Merge pull request #47263 from nikhita/crd-01-validation-types

Automatic merge from submit-queue

apiextensions: validation for customresources

- [x] Add types for validation of CustomResources
- [x] Fix conversion-gen: #49747
- [x] Fix defaulter-gen: kubernetes/gengo#61
- [x] Convert to OpenAPI types
- [x] Validate CR using go-openapi
- [x] Validate CRD Schema
- [x] Add integration tests
- [x] Fix round trip tests: #51204 
- [x] Add custom fuzzer functions
- [x] Add custom conversion functions
- [x] Fix data race while updating CRD: #50098 
- [x] Add feature gate for CustomResourceValidation
- [x] Fix protobuf generation

Proposal: https://github.com/kubernetes/community/pull/708
Additional discussion: https://github.com/kubernetes/kubernetes/issues/49879, https://github.com/kubernetes/kubernetes/pull/50625

**Release note**:

```release-note
Add validation for CustomResources via JSON Schema.
```

/cc @sttts @deads2k
This commit is contained in:
Kubernetes Submit Queue 2017-08-29 18:37:10 -07:00 committed by GitHub
commit 4457e43e7b
43 changed files with 7360 additions and 813 deletions

View File

@ -548,6 +548,7 @@ staging/src/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/
staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server
staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer
staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status
staging/src/k8s.io/apiextensions-apiserver/pkg/features
staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource
staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition
staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver

View File

@ -9,6 +9,7 @@ go_library(
name = "go_default_library", name = "go_default_library",
srcs = ["kube_features.go"], srcs = ["kube_features.go"],
deps = [ deps = [
"//vendor/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
"//vendor/k8s.io/apiserver/pkg/features:go_default_library", "//vendor/k8s.io/apiserver/pkg/features:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
], ],

View File

@ -17,6 +17,7 @@ limitations under the License.
package features package features
import ( import (
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
genericfeatures "k8s.io/apiserver/pkg/features" genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
) )
@ -44,13 +45,6 @@ const (
// alpha: v1.4 // alpha: v1.4
DynamicKubeletConfig utilfeature.Feature = "DynamicKubeletConfig" DynamicKubeletConfig utilfeature.Feature = "DynamicKubeletConfig"
// owner: tallclair
// alpha: v1.5
//
// StreamingProxyRedirects controls whether the apiserver should intercept (and follow)
// redirects from the backend (Kubelet) for streaming requests (exec/attach/port-forward).
StreamingProxyRedirects utilfeature.Feature = genericfeatures.StreamingProxyRedirects
// owner: @pweil- // owner: @pweil-
// alpha: v1.5 // alpha: v1.5
// //
@ -158,11 +152,16 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
DebugContainers: {Default: false, PreRelease: utilfeature.Alpha}, DebugContainers: {Default: false, PreRelease: utilfeature.Alpha},
PodPriority: {Default: false, PreRelease: utilfeature.Alpha}, PodPriority: {Default: false, PreRelease: utilfeature.Alpha},
EnableEquivalenceClassCache: {Default: false, PreRelease: utilfeature.Alpha}, EnableEquivalenceClassCache: {Default: false, PreRelease: utilfeature.Alpha},
TaintNodesByCondition: {Default: false, PreRelease: utilfeature.Alpha},
// inherited features from generic apiserver, relisted here to get a conflict if it is changed // inherited features from generic apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side: // unintentionally on either side:
StreamingProxyRedirects: {Default: true, PreRelease: utilfeature.Beta}, genericfeatures.StreamingProxyRedirects: {Default: true, PreRelease: utilfeature.Beta},
genericfeatures.AdvancedAuditing: {Default: false, PreRelease: utilfeature.Alpha}, genericfeatures.AdvancedAuditing: {Default: false, PreRelease: utilfeature.Alpha},
TaintNodesByCondition: {Default: false, PreRelease: utilfeature.Alpha}, genericfeatures.APIResponseCompression: {Default: false, PreRelease: utilfeature.Alpha},
genericfeatures.Initializers: {Default: false, PreRelease: utilfeature.Alpha}, genericfeatures.Initializers: {Default: false, PreRelease: utilfeature.Alpha},
// inherited features from apiextensions-apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side:
apiextensionsfeatures.CustomResourceValidation: {Default: false, PreRelease: utilfeature.Alpha},
} }

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@ syntax = 'proto2';
package k8s.io.api.core.v1; package k8s.io.api.core.v1;
import "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto";
import "k8s.io/apimachinery/pkg/api/resource/generated.proto"; import "k8s.io/apimachinery/pkg/api/resource/generated.proto";
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto"; import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/generated.proto"; import "k8s.io/apimachinery/pkg/runtime/generated.proto";

View File

@ -45,6 +45,7 @@ filegroup(
"//staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server:all-srcs", "//staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer:all-srcs", "//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status:all-srcs", "//staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/features:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource:all-srcs", "//staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition:all-srcs", "//staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition:all-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/test/integration:all-srcs", "//staging/src/k8s.io/apiextensions-apiserver/test/integration:all-srcs",

View File

@ -22,6 +22,10 @@
"ImportPath": "github.com/PuerkitoBio/urlesc", "ImportPath": "github.com/PuerkitoBio/urlesc",
"Rev": "5bd2802263f21d8788851d5305584c82a5c75d7e" "Rev": "5bd2802263f21d8788851d5305584c82a5c75d7e"
}, },
{
"ImportPath": "github.com/asaskevich/govalidator",
"Rev": "593d64559f7600f29581a3ee42177f5dbded27a9"
},
{ {
"ImportPath": "github.com/beorn7/perks/quantile", "ImportPath": "github.com/beorn7/perks/quantile",
"Rev": "3ac7bf7a47d159a033b107610db8a1b6575507a4" "Rev": "3ac7bf7a47d159a033b107610db8a1b6575507a4"
@ -110,6 +114,14 @@
"ImportPath": "github.com/ghodss/yaml", "ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee" "Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
}, },
{
"ImportPath": "github.com/go-openapi/analysis",
"Rev": "b44dc874b601d9e4e2f6e19140e794ba24bead3b"
},
{
"ImportPath": "github.com/go-openapi/errors",
"Rev": "d24ebc2075bad502fac3a8ae27aa6dd58e1952dc"
},
{ {
"ImportPath": "github.com/go-openapi/jsonpointer", "ImportPath": "github.com/go-openapi/jsonpointer",
"Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98" "Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98"
@ -118,14 +130,30 @@
"ImportPath": "github.com/go-openapi/jsonreference", "ImportPath": "github.com/go-openapi/jsonreference",
"Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272" "Rev": "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272"
}, },
{
"ImportPath": "github.com/go-openapi/loads",
"Rev": "18441dfa706d924a39a030ee2c3b1d8d81917b38"
},
{
"ImportPath": "github.com/go-openapi/runtime",
"Rev": "11e322eeecc1032d5a0a96c566ed53f2b5c26e22"
},
{ {
"ImportPath": "github.com/go-openapi/spec", "ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff" "Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
}, },
{
"ImportPath": "github.com/go-openapi/strfmt",
"Rev": "d65c7fdb29eca313476e529628176fe17e58c488"
},
{ {
"ImportPath": "github.com/go-openapi/swag", "ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72" "Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
}, },
{
"ImportPath": "github.com/go-openapi/validate",
"Rev": "deaf2c9013bc1a7f4c774662259a506ba874d80f"
},
{ {
"ImportPath": "github.com/gogo/protobuf/proto", "ImportPath": "github.com/gogo/protobuf/proto",
"Rev": "c0656edd0d9eab7c66d1eb0c568f9039345796f7" "Rev": "c0656edd0d9eab7c66d1eb0c568f9039345796f7"

View File

@ -9,10 +9,12 @@ load(
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"deepcopy.go",
"doc.go", "doc.go",
"helpers.go", "helpers.go",
"register.go", "register.go",
"types.go", "types.go",
"types_jsonschema.go",
"zz_generated.deepcopy.go", "zz_generated.deepcopy.go",
], ],
deps = [ deps = [

View File

@ -0,0 +1,279 @@
/*
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 apiextensions
// TODO: Update this after a tag is created for interface fields in DeepCopy
func (in *JSONSchemaProps) DeepCopy() *JSONSchemaProps {
if in == nil {
return nil
}
out := new(JSONSchemaProps)
*out = *in
if in.Default != nil {
defaultJSON := JSON(deepCopyJSON(*(in.Default)))
out.Default = &(defaultJSON)
} else {
out.Default = nil
}
if in.Example != nil {
exampleJSON := JSON(deepCopyJSON(*(in.Example)))
out.Example = &(exampleJSON)
} else {
out.Example = nil
}
if in.Ref != nil {
in, out := &in.Ref, &out.Ref
if *in == nil {
*out = nil
} else {
*out = new(string)
**out = **in
}
}
if in.Maximum != nil {
in, out := &in.Maximum, &out.Maximum
if *in == nil {
*out = nil
} else {
*out = new(float64)
**out = **in
}
}
if in.Minimum != nil {
in, out := &in.Minimum, &out.Minimum
if *in == nil {
*out = nil
} else {
*out = new(float64)
**out = **in
}
}
if in.MaxLength != nil {
in, out := &in.MaxLength, &out.MaxLength
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MinLength != nil {
in, out := &in.MinLength, &out.MinLength
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MaxItems != nil {
in, out := &in.MaxItems, &out.MaxItems
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MinItems != nil {
in, out := &in.MinItems, &out.MinItems
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MultipleOf != nil {
in, out := &in.MultipleOf, &out.MultipleOf
if *in == nil {
*out = nil
} else {
*out = new(float64)
**out = **in
}
}
if in.Enum != nil {
out.Enum = make([]JSON, len(in.Enum))
for i := range in.Enum {
out.Enum[i] = deepCopyJSON(in.Enum[i])
}
}
if in.MaxProperties != nil {
in, out := &in.MaxProperties, &out.MaxProperties
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MinProperties != nil {
in, out := &in.MinProperties, &out.MinProperties
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.Required != nil {
in, out := &in.Required, &out.Required
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Items != nil {
in, out := &in.Items, &out.Items
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaPropsOrArray)
(*in).DeepCopyInto(*out)
}
}
if in.AllOf != nil {
in, out := &in.AllOf, &out.AllOf
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.OneOf != nil {
in, out := &in.OneOf, &out.OneOf
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AnyOf != nil {
in, out := &in.AnyOf, &out.AnyOf
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Not != nil {
in, out := &in.Not, &out.Not
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaProps)
(*in).DeepCopyInto(*out)
}
}
if in.Properties != nil {
in, out := &in.Properties, &out.Properties
*out = make(map[string]JSONSchemaProps, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.AdditionalProperties != nil {
in, out := &in.AdditionalProperties, &out.AdditionalProperties
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaPropsOrBool)
(*in).DeepCopyInto(*out)
}
}
if in.PatternProperties != nil {
in, out := &in.PatternProperties, &out.PatternProperties
*out = make(map[string]JSONSchemaProps, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.Dependencies != nil {
in, out := &in.Dependencies, &out.Dependencies
*out = make(JSONSchemaDependencies, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.AdditionalItems != nil {
in, out := &in.AdditionalItems, &out.AdditionalItems
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaPropsOrBool)
(*in).DeepCopyInto(*out)
}
}
if in.Definitions != nil {
in, out := &in.Definitions, &out.Definitions
*out = make(JSONSchemaDefinitions, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.ExternalDocs != nil {
in, out := &in.ExternalDocs, &out.ExternalDocs
if *in == nil {
*out = nil
} else {
*out = new(ExternalDocumentation)
(*in).DeepCopyInto(*out)
}
}
return out
}
func deepCopyJSON(x interface{}) interface{} {
switch x := x.(type) {
case map[string]interface{}:
clone := make(map[string]interface{}, len(x))
for k, v := range x {
clone[k] = deepCopyJSON(v)
}
return clone
case []interface{}:
clone := make([]interface{}, len(x))
for i := range x {
clone[i] = deepCopyJSON(x[i])
}
return clone
default:
return x
}
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package fuzzer package fuzzer
import ( import (
"reflect"
"strings" "strings"
"github.com/google/gofuzz" "github.com/google/gofuzz"
@ -42,5 +43,65 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
obj.Names.ListKind = obj.Names.Kind + "List" obj.Names.ListKind = obj.Names.Kind + "List"
} }
}, },
func(obj *apiextensions.JSONSchemaProps, c fuzz.Continue) {
// we cannot use c.FuzzNoCustom because of the interface{} fields. So let's loop with reflection.
vobj := reflect.ValueOf(obj).Elem()
tobj := reflect.TypeOf(obj).Elem()
for i := 0; i < tobj.NumField(); i++ {
field := tobj.Field(i)
switch field.Name {
case "Default", "Enum", "Example":
continue
default:
isValue := true
switch field.Type.Kind() {
case reflect.Interface, reflect.Map, reflect.Slice, reflect.Ptr:
isValue = false
}
if isValue || c.Intn(5) == 0 {
c.Fuzz(vobj.Field(i).Addr().Interface())
}
}
}
if c.RandBool() {
validJSON := apiextensions.JSON(`{"some": {"json": "test"}, "string": 42}`)
obj.Default = &validJSON
}
if c.RandBool() {
obj.Enum = []apiextensions.JSON{c.Float64(), c.RandString(), c.RandBool()}
}
if c.RandBool() {
validJSON := apiextensions.JSON(`"foobarbaz"`)
obj.Example = &validJSON
}
},
func(obj *apiextensions.JSONSchemaPropsOrBool, c fuzz.Continue) {
if c.RandBool() {
obj.Schema = &apiextensions.JSONSchemaProps{}
c.Fuzz(obj.Schema)
} else {
obj.Allows = c.RandBool()
}
},
func(obj *apiextensions.JSONSchemaPropsOrArray, c fuzz.Continue) {
// disallow both Schema and JSONSchemas to be nil.
if c.RandBool() {
obj.Schema = &apiextensions.JSONSchemaProps{}
c.Fuzz(obj.Schema)
} else {
obj.JSONSchemas = make([]apiextensions.JSONSchemaProps, c.Intn(3)+1)
for i := range obj.JSONSchemas {
c.Fuzz(&obj.JSONSchemas[i])
}
}
},
func(obj *apiextensions.JSONSchemaPropsOrStringArray, c fuzz.Continue) {
if c.RandBool() {
obj.Schema = &apiextensions.JSONSchemaProps{}
c.Fuzz(obj.Schema)
} else {
c.Fuzz(&obj.Property)
}
},
} }
} }

View File

@ -26,9 +26,10 @@ type CustomResourceDefinitionSpec struct {
Version string Version string
// Names are the names used to describe this custom resource // Names are the names used to describe this custom resource
Names CustomResourceDefinitionNames Names CustomResourceDefinitionNames
// Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced // Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced
Scope ResourceScope Scope ResourceScope
// Validation describes the validation methods for CustomResources
Validation *CustomResourceValidation
} }
// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition // CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition
@ -139,3 +140,9 @@ type CustomResourceDefinitionList struct {
// Items individual CustomResourceDefinitions // Items individual CustomResourceDefinitions
Items []CustomResourceDefinition Items []CustomResourceDefinition
} }
// CustomResourceValidation is a list of validation methods for CustomResources.
type CustomResourceValidation struct {
// OpenAPIV3Schema is the OpenAPI v3 schema to be validated against.
OpenAPIV3Schema *JSONSchemaProps
}

View File

@ -0,0 +1,96 @@
/*
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 apiextensions
// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).
type JSONSchemaProps struct {
ID string
Schema JSONSchemaURL
Ref *string
Description string
Type string
Format string
Title string
Default *JSON
Maximum *float64
ExclusiveMaximum bool
Minimum *float64
ExclusiveMinimum bool
MaxLength *int64
MinLength *int64
Pattern string
MaxItems *int64
MinItems *int64
UniqueItems bool
MultipleOf *float64
Enum []JSON
MaxProperties *int64
MinProperties *int64
Required []string
Items *JSONSchemaPropsOrArray
AllOf []JSONSchemaProps
OneOf []JSONSchemaProps
AnyOf []JSONSchemaProps
Not *JSONSchemaProps
Properties map[string]JSONSchemaProps
AdditionalProperties *JSONSchemaPropsOrBool
PatternProperties map[string]JSONSchemaProps
Dependencies JSONSchemaDependencies
AdditionalItems *JSONSchemaPropsOrBool
Definitions JSONSchemaDefinitions
ExternalDocs *ExternalDocumentation
Example *JSON
}
// JSON represents any valid JSON value.
// These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil.
type JSON interface{}
// JSONSchemaURL represents a schema url.
type JSONSchemaURL string
// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps
// or an array of JSONSchemaProps. Mainly here for serialization purposes.
type JSONSchemaPropsOrArray struct {
Schema *JSONSchemaProps
JSONSchemas []JSONSchemaProps
}
// JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value.
// Defaults to true for the boolean property.
type JSONSchemaPropsOrBool struct {
Allows bool
Schema *JSONSchemaProps
}
// JSONSchemaDependencies represent a dependencies property.
type JSONSchemaDependencies map[string]JSONSchemaPropsOrStringArray
// JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array.
type JSONSchemaPropsOrStringArray struct {
Schema *JSONSchemaProps
Property []string
}
// JSONSchemaDefinitions contains the models explicitly defined in this spec.
type JSONSchemaDefinitions map[string]JSONSchemaProps
// ExternalDocumentation allows referencing an external resource for extended documentation.
type ExternalDocumentation struct {
Description string
URL string
}

View File

@ -3,27 +3,34 @@ package(default_visibility = ["//visibility:public"])
load( load(
"@io_bazel_rules_go//go:def.bzl", "@io_bazel_rules_go//go:def.bzl",
"go_library", "go_library",
"go_test",
) )
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"conversion.go",
"deepcopy.go",
"defaults.go", "defaults.go",
"doc.go", "doc.go",
"generated.pb.go", "generated.pb.go",
"marshal.go",
"register.go", "register.go",
"types.go", "types.go",
"types_jsonschema.go",
"zz_generated.conversion.go", "zz_generated.conversion.go",
"zz_generated.deepcopy.go", "zz_generated.deepcopy.go",
"zz_generated.defaults.go", "zz_generated.defaults.go",
], ],
deps = [ deps = [
"//vendor/github.com/gogo/protobuf/proto:go_default_library", "//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/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
], ],
) )
@ -45,3 +52,16 @@ filegroup(
srcs = ["generated.proto"], srcs = ["generated.proto"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
go_test(
name = "go_default_test",
srcs = [
"conversion_test.go",
"marshal_test.go",
],
library = ":go_default_library",
deps = [
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
],
)

View File

@ -0,0 +1,73 @@
/*
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 v1beta1
import (
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
)
func addConversionFuncs(scheme *runtime.Scheme) error {
// Add non-generated conversion functions
err := scheme.AddConversionFuncs(
Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps,
Convert_apiextensions_JSON_To_v1beta1_JSON,
Convert_v1beta1_JSON_To_apiextensions_JSON,
)
if err != nil {
return err
}
return nil
}
func Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(in *apiextensions.JSONSchemaProps, out *JSONSchemaProps, s conversion.Scope) error {
if err := autoConvert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(in, out, s); err != nil {
return err
}
if in.Default != nil && *(in.Default) == nil {
out.Default = nil
}
if in.Example != nil && *(in.Example) == nil {
out.Example = nil
}
return nil
}
func Convert_apiextensions_JSON_To_v1beta1_JSON(in *apiextensions.JSON, out *JSON, s conversion.Scope) error {
raw, err := json.Marshal(*in)
if err != nil {
return err
}
out.Raw = raw
return nil
}
func Convert_v1beta1_JSON_To_apiextensions_JSON(in *JSON, out *apiextensions.JSON, s conversion.Scope) error {
if in != nil {
var i interface{}
if err := json.Unmarshal(in.Raw, &i); err != nil {
return err
}
*out = i
} else {
out = nil
}
return nil
}

View File

@ -0,0 +1,113 @@
/*
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 v1beta1
import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
)
func TestJSONConversion(t *testing.T) {
nilJSON := apiextensions.JSON(nil)
nullJSON := apiextensions.JSON("null")
stringJSON := apiextensions.JSON("foo")
boolJSON := apiextensions.JSON(true)
sliceJSON := apiextensions.JSON([]string{"foo", "bar", "baz"})
testCases := map[string]struct {
input *apiextensions.JSONSchemaProps
expected *JSONSchemaProps
}{
"nil": {
input: &apiextensions.JSONSchemaProps{
Default: nil,
},
expected: &JSONSchemaProps{},
},
"aliased nil": {
input: &apiextensions.JSONSchemaProps{
Default: &nilJSON,
},
expected: &JSONSchemaProps{},
},
"null": {
input: &apiextensions.JSONSchemaProps{
Default: &nullJSON,
},
expected: &JSONSchemaProps{
Default: &JSON{
Raw: []byte(`"null"`),
},
},
},
"string": {
input: &apiextensions.JSONSchemaProps{
Default: &stringJSON,
},
expected: &JSONSchemaProps{
Default: &JSON{
Raw: []byte(`"foo"`),
},
},
},
"bool": {
input: &apiextensions.JSONSchemaProps{
Default: &boolJSON,
},
expected: &JSONSchemaProps{
Default: &JSON{
Raw: []byte(`true`),
},
},
},
"slice": {
input: &apiextensions.JSONSchemaProps{
Default: &sliceJSON,
},
expected: &JSONSchemaProps{
Default: &JSON{
Raw: []byte(`["foo","bar","baz"]`),
},
},
},
}
scheme := runtime.NewScheme()
// add internal and external types
if err := apiextensions.AddToScheme(scheme); err != nil {
t.Fatal(err)
}
if err := AddToScheme(scheme); err != nil {
t.Fatal(err)
}
for k, tc := range testCases {
external := &JSONSchemaProps{}
if err := scheme.Convert(tc.input, external, nil); err != nil {
t.Errorf("%s: unexpected error: %v", k, err)
}
if !reflect.DeepEqual(external, tc.expected) {
t.Errorf("%s: expected\n\t%#v, got \n\t%#v", k, tc.expected, external)
}
}
}

View File

@ -0,0 +1,257 @@
/*
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 v1beta1
// TODO: Update this after a tag is created for interface fields in DeepCopy
func (in *JSONSchemaProps) DeepCopy() *JSONSchemaProps {
if in == nil {
return nil
}
out := new(JSONSchemaProps)
*out = *in
if in.Ref != nil {
in, out := &in.Ref, &out.Ref
if *in == nil {
*out = nil
} else {
*out = new(string)
**out = **in
}
}
if in.Maximum != nil {
in, out := &in.Maximum, &out.Maximum
if *in == nil {
*out = nil
} else {
*out = new(float64)
**out = **in
}
}
if in.Minimum != nil {
in, out := &in.Minimum, &out.Minimum
if *in == nil {
*out = nil
} else {
*out = new(float64)
**out = **in
}
}
if in.MaxLength != nil {
in, out := &in.MaxLength, &out.MaxLength
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MinLength != nil {
in, out := &in.MinLength, &out.MinLength
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MaxItems != nil {
in, out := &in.MaxItems, &out.MaxItems
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MinItems != nil {
in, out := &in.MinItems, &out.MinItems
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MultipleOf != nil {
in, out := &in.MultipleOf, &out.MultipleOf
if *in == nil {
*out = nil
} else {
*out = new(float64)
**out = **in
}
}
if in.MaxProperties != nil {
in, out := &in.MaxProperties, &out.MaxProperties
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.MinProperties != nil {
in, out := &in.MinProperties, &out.MinProperties
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
if in.Required != nil {
in, out := &in.Required, &out.Required
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Items != nil {
in, out := &in.Items, &out.Items
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaPropsOrArray)
(*in).DeepCopyInto(*out)
}
}
if in.AllOf != nil {
in, out := &in.AllOf, &out.AllOf
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.OneOf != nil {
in, out := &in.OneOf, &out.OneOf
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AnyOf != nil {
in, out := &in.AnyOf, &out.AnyOf
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Not != nil {
in, out := &in.Not, &out.Not
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaProps)
(*in).DeepCopyInto(*out)
}
}
if in.Properties != nil {
in, out := &in.Properties, &out.Properties
*out = make(map[string]JSONSchemaProps, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.AdditionalProperties != nil {
in, out := &in.AdditionalProperties, &out.AdditionalProperties
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaPropsOrBool)
(*in).DeepCopyInto(*out)
}
}
if in.PatternProperties != nil {
in, out := &in.PatternProperties, &out.PatternProperties
*out = make(map[string]JSONSchemaProps, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.Dependencies != nil {
in, out := &in.Dependencies, &out.Dependencies
*out = make(JSONSchemaDependencies, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.AdditionalItems != nil {
in, out := &in.AdditionalItems, &out.AdditionalItems
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaPropsOrBool)
(*in).DeepCopyInto(*out)
}
}
if in.Definitions != nil {
in, out := &in.Definitions, &out.Definitions
*out = make(JSONSchemaDefinitions, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.ExternalDocs != nil {
in, out := &in.ExternalDocs, &out.ExternalDocs
if *in == nil {
*out = nil
} else {
*out = new(ExternalDocumentation)
(*in).DeepCopyInto(*out)
}
}
return out
}
func deepCopyJSON(x interface{}) interface{} {
switch x := x.(type) {
case map[string]interface{}:
clone := make(map[string]interface{}, len(x))
for k, v := range x {
clone[k] = deepCopyJSON(v)
}
return clone
case []interface{}:
clone := make([]interface{}, len(x))
for i := range x {
clone[i] = deepCopyJSON(x[i])
}
return clone
default:
return x
}
}

View File

@ -103,6 +103,11 @@ message CustomResourceDefinitionSpec {
// Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced // Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced
optional string scope = 4; optional string scope = 4;
// Validation describes the validation methods for CustomResources
// This field is alpha-level and should only be sent to servers that enable the CustomResourceValidation feature.
// +optional
optional CustomResourceValidation validation = 5;
} }
// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition // CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition
@ -115,3 +120,120 @@ message CustomResourceDefinitionStatus {
optional CustomResourceDefinitionNames acceptedNames = 2; optional CustomResourceDefinitionNames acceptedNames = 2;
} }
// CustomResourceValidation is a list of validation methods for CustomResources.
message CustomResourceValidation {
// OpenAPIV3Schema is the OpenAPI v3 schema to be validated against.
optional JSONSchemaProps openAPIV3Schema = 1;
}
// ExternalDocumentation allows referencing an external resource for extended documentation.
message ExternalDocumentation {
optional string description = 1;
optional string url = 2;
}
// JSON represents any valid JSON value.
// These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil.
message JSON {
optional bytes raw = 1;
}
// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).
message JSONSchemaProps {
optional string id = 1;
optional string schema = 2;
optional string ref = 3;
optional string description = 4;
optional string type = 5;
optional string format = 6;
optional string title = 7;
optional JSON default = 8;
optional double maximum = 9;
optional bool exclusiveMaximum = 10;
optional double minimum = 11;
optional bool exclusiveMinimum = 12;
optional int64 maxLength = 13;
optional int64 minLength = 14;
optional string pattern = 15;
optional int64 maxItems = 16;
optional int64 minItems = 17;
optional bool uniqueItems = 18;
optional double multipleOf = 19;
repeated JSON enum = 20;
optional int64 maxProperties = 21;
optional int64 minProperties = 22;
repeated string required = 23;
optional JSONSchemaPropsOrArray items = 24;
repeated JSONSchemaProps allOf = 25;
repeated JSONSchemaProps oneOf = 26;
repeated JSONSchemaProps anyOf = 27;
optional JSONSchemaProps not = 28;
map<string, JSONSchemaProps> properties = 29;
optional JSONSchemaPropsOrBool additionalProperties = 30;
map<string, JSONSchemaProps> patternProperties = 31;
map<string, JSONSchemaPropsOrStringArray> dependencies = 32;
optional JSONSchemaPropsOrBool additionalItems = 33;
map<string, JSONSchemaProps> definitions = 34;
optional ExternalDocumentation externalDocs = 35;
optional JSON example = 36;
}
// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps
// or an array of JSONSchemaProps. Mainly here for serialization purposes.
message JSONSchemaPropsOrArray {
optional JSONSchemaProps schema = 1;
repeated JSONSchemaProps jSONSchemas = 2;
}
// JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value.
// Defaults to true for the boolean property.
message JSONSchemaPropsOrBool {
optional bool allows = 1;
optional JSONSchemaProps schema = 2;
}
// JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array.
message JSONSchemaPropsOrStringArray {
optional JSONSchemaProps schema = 1;
repeated string property = 2;
}

View File

@ -0,0 +1,134 @@
/*
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 v1beta1
import (
"errors"
"k8s.io/apimachinery/pkg/util/json"
)
var jsTrue = []byte("true")
var jsFalse = []byte("false")
func (s JSONSchemaPropsOrBool) MarshalJSON() ([]byte, error) {
if s.Schema != nil {
return json.Marshal(s.Schema)
}
if s.Schema == nil && !s.Allows {
return jsFalse, nil
}
return jsTrue, nil
}
func (s *JSONSchemaPropsOrBool) UnmarshalJSON(data []byte) error {
var nw JSONSchemaPropsOrBool
switch {
case len(data) == 0:
case data[0] == '{':
var sch JSONSchemaProps
if err := json.Unmarshal(data, &sch); err != nil {
return err
}
nw.Schema = &sch
case len(data) == 4 && string(data) == "true":
nw.Allows = true
case len(data) == 5 && string(data) == "false":
nw.Allows = false
default:
return errors.New("boolean or JSON schema expected")
}
*s = nw
return nil
}
func (s JSONSchemaPropsOrStringArray) MarshalJSON() ([]byte, error) {
if len(s.Property) > 0 {
return json.Marshal(s.Property)
}
if s.Schema != nil {
return json.Marshal(s.Schema)
}
return []byte("null"), nil
}
func (s *JSONSchemaPropsOrStringArray) UnmarshalJSON(data []byte) error {
var first byte
if len(data) > 1 {
first = data[0]
}
var nw JSONSchemaPropsOrStringArray
if first == '{' {
var sch JSONSchemaProps
if err := json.Unmarshal(data, &sch); err != nil {
return err
}
nw.Schema = &sch
}
if first == '[' {
if err := json.Unmarshal(data, &nw.Property); err != nil {
return err
}
}
*s = nw
return nil
}
func (s JSONSchemaPropsOrArray) MarshalJSON() ([]byte, error) {
if len(s.JSONSchemas) > 0 {
return json.Marshal(s.JSONSchemas)
}
return json.Marshal(s.Schema)
}
func (s *JSONSchemaPropsOrArray) UnmarshalJSON(data []byte) error {
var nw JSONSchemaPropsOrArray
var first byte
if len(data) > 1 {
first = data[0]
}
if first == '{' {
var sch JSONSchemaProps
if err := json.Unmarshal(data, &sch); err != nil {
return err
}
nw.Schema = &sch
}
if first == '[' {
if err := json.Unmarshal(data, &nw.JSONSchemas); err != nil {
return err
}
}
*s = nw
return nil
}
func (s JSON) MarshalJSON() ([]byte, error) {
if len(s.Raw) > 0 {
return s.Raw, nil
}
return []byte("null"), nil
}
func (s *JSON) UnmarshalJSON(data []byte) error {
if len(data) > 0 && string(data) != "null" {
s.Raw = data
}
return nil
}

View File

@ -0,0 +1,150 @@
/*
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 v1beta1
import (
"encoding/json"
"reflect"
"testing"
)
type JSONSchemaPropsOrBoolHolder struct {
JSPoB JSONSchemaPropsOrBool `json:"val1"`
JSPoBOmitEmpty *JSONSchemaPropsOrBool `json:"val2,omitempty"`
}
func TestJSONSchemaPropsOrBoolUnmarshalJSON(t *testing.T) {
cases := []struct {
input string
result JSONSchemaPropsOrBoolHolder
}{
{`{}`, JSONSchemaPropsOrBoolHolder{}},
{`{"val1": {}}`, JSONSchemaPropsOrBoolHolder{JSPoB: JSONSchemaPropsOrBool{Schema: &JSONSchemaProps{}}}},
{`{"val1": {"type":"string"}}`, JSONSchemaPropsOrBoolHolder{JSPoB: JSONSchemaPropsOrBool{Schema: &JSONSchemaProps{Type: "string"}}}},
{`{"val1": false}`, JSONSchemaPropsOrBoolHolder{JSPoB: JSONSchemaPropsOrBool{}}},
{`{"val1": true}`, JSONSchemaPropsOrBoolHolder{JSPoB: JSONSchemaPropsOrBool{Allows: true}}},
{`{"val2": {}}`, JSONSchemaPropsOrBoolHolder{JSPoBOmitEmpty: &JSONSchemaPropsOrBool{Schema: &JSONSchemaProps{}}}},
{`{"val2": {"type":"string"}}`, JSONSchemaPropsOrBoolHolder{JSPoBOmitEmpty: &JSONSchemaPropsOrBool{Schema: &JSONSchemaProps{Type: "string"}}}},
{`{"val2": false}`, JSONSchemaPropsOrBoolHolder{JSPoBOmitEmpty: &JSONSchemaPropsOrBool{}}},
{`{"val2": true}`, JSONSchemaPropsOrBoolHolder{JSPoBOmitEmpty: &JSONSchemaPropsOrBool{Allows: true}}},
}
for _, c := range cases {
var result JSONSchemaPropsOrBoolHolder
if err := json.Unmarshal([]byte(c.input), &result); err != nil {
t.Errorf("Failed to unmarshal input '%v': %v", c.input, err)
}
if !reflect.DeepEqual(result, c.result) {
t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result)
}
}
}
func TestStringArrayOrStringMarshalJSON(t *testing.T) {
cases := []struct {
input JSONSchemaPropsOrBoolHolder
result string
}{
{JSONSchemaPropsOrBoolHolder{}, `{"val1":false}`},
{JSONSchemaPropsOrBoolHolder{JSPoB: JSONSchemaPropsOrBool{Schema: &JSONSchemaProps{}}}, `{"val1":{}}`},
{JSONSchemaPropsOrBoolHolder{JSPoB: JSONSchemaPropsOrBool{Schema: &JSONSchemaProps{Type: "string"}}}, `{"val1":{"type":"string"}}`},
{JSONSchemaPropsOrBoolHolder{JSPoB: JSONSchemaPropsOrBool{}}, `{"val1":false}`},
{JSONSchemaPropsOrBoolHolder{JSPoB: JSONSchemaPropsOrBool{Allows: true}}, `{"val1":true}`},
{JSONSchemaPropsOrBoolHolder{JSPoBOmitEmpty: &JSONSchemaPropsOrBool{Schema: &JSONSchemaProps{}}}, `{"val1":false,"val2":{}}`},
{JSONSchemaPropsOrBoolHolder{JSPoBOmitEmpty: &JSONSchemaPropsOrBool{Schema: &JSONSchemaProps{Type: "string"}}}, `{"val1":false,"val2":{"type":"string"}}`},
{JSONSchemaPropsOrBoolHolder{JSPoBOmitEmpty: &JSONSchemaPropsOrBool{}}, `{"val1":false,"val2":false}`},
{JSONSchemaPropsOrBoolHolder{JSPoBOmitEmpty: &JSONSchemaPropsOrBool{Allows: true}}, `{"val1":false,"val2":true}`},
}
for _, c := range cases {
result, err := json.Marshal(&c.input)
if err != nil {
t.Errorf("Unexpected error marshaling input '%v': %v", c.input, err)
}
if string(result) != c.result {
t.Errorf("Failed to marshal input '%v': expected: %q, got %q", c.input, c.result, string(result))
}
}
}
type JSONSchemaPropsOrArrayHolder struct {
JSPoA JSONSchemaPropsOrArray `json:"val1"`
JSPoAOmitEmpty *JSONSchemaPropsOrArray `json:"val2,omitempty"`
}
func TestJSONSchemaPropsOrArrayUnmarshalJSON(t *testing.T) {
cases := []struct {
input string
result JSONSchemaPropsOrArrayHolder
}{
{`{}`, JSONSchemaPropsOrArrayHolder{}},
{`{"val1": {}}`, JSONSchemaPropsOrArrayHolder{JSPoA: JSONSchemaPropsOrArray{Schema: &JSONSchemaProps{}}}},
{`{"val1": {"type":"string"}}`, JSONSchemaPropsOrArrayHolder{JSPoA: JSONSchemaPropsOrArray{Schema: &JSONSchemaProps{Type: "string"}}}},
{`{"val1": [{}]}`, JSONSchemaPropsOrArrayHolder{JSPoA: JSONSchemaPropsOrArray{JSONSchemas: []JSONSchemaProps{{}}}}},
{`{"val1": [{},{"type":"string"}]}`, JSONSchemaPropsOrArrayHolder{JSPoA: JSONSchemaPropsOrArray{JSONSchemas: []JSONSchemaProps{{}, {Type: "string"}}}}},
{`{"val2": {}}`, JSONSchemaPropsOrArrayHolder{JSPoAOmitEmpty: &JSONSchemaPropsOrArray{Schema: &JSONSchemaProps{}}}},
{`{"val2": {"type":"string"}}`, JSONSchemaPropsOrArrayHolder{JSPoAOmitEmpty: &JSONSchemaPropsOrArray{Schema: &JSONSchemaProps{Type: "string"}}}},
{`{"val2": [{}]}`, JSONSchemaPropsOrArrayHolder{JSPoAOmitEmpty: &JSONSchemaPropsOrArray{JSONSchemas: []JSONSchemaProps{{}}}}},
{`{"val2": [{},{"type":"string"}]}`, JSONSchemaPropsOrArrayHolder{JSPoAOmitEmpty: &JSONSchemaPropsOrArray{JSONSchemas: []JSONSchemaProps{{}, {Type: "string"}}}}},
}
for _, c := range cases {
var result JSONSchemaPropsOrArrayHolder
if err := json.Unmarshal([]byte(c.input), &result); err != nil {
t.Errorf("Failed to unmarshal input '%v': %v", c.input, err)
}
if !reflect.DeepEqual(result, c.result) {
t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result)
}
}
}
func TestJSONSchemaPropsOrArrayMarshalJSON(t *testing.T) {
cases := []struct {
input JSONSchemaPropsOrArrayHolder
result string
}{
{JSONSchemaPropsOrArrayHolder{}, `{"val1":null}`},
{JSONSchemaPropsOrArrayHolder{JSPoA: JSONSchemaPropsOrArray{Schema: &JSONSchemaProps{}}}, `{"val1":{}}`},
{JSONSchemaPropsOrArrayHolder{JSPoA: JSONSchemaPropsOrArray{Schema: &JSONSchemaProps{Type: "string"}}}, `{"val1":{"type":"string"}}`},
{JSONSchemaPropsOrArrayHolder{JSPoA: JSONSchemaPropsOrArray{JSONSchemas: []JSONSchemaProps{{}}}}, `{"val1":[{}]}`},
{JSONSchemaPropsOrArrayHolder{JSPoA: JSONSchemaPropsOrArray{JSONSchemas: []JSONSchemaProps{{}, {Type: "string"}}}}, `{"val1":[{},{"type":"string"}]}`},
{JSONSchemaPropsOrArrayHolder{JSPoAOmitEmpty: &JSONSchemaPropsOrArray{}}, `{"val1":null,"val2":null}`},
{JSONSchemaPropsOrArrayHolder{JSPoAOmitEmpty: &JSONSchemaPropsOrArray{Schema: &JSONSchemaProps{}}}, `{"val1":null,"val2":{}}`},
{JSONSchemaPropsOrArrayHolder{JSPoAOmitEmpty: &JSONSchemaPropsOrArray{Schema: &JSONSchemaProps{Type: "string"}}}, `{"val1":null,"val2":{"type":"string"}}`},
{JSONSchemaPropsOrArrayHolder{JSPoAOmitEmpty: &JSONSchemaPropsOrArray{JSONSchemas: []JSONSchemaProps{{}}}}, `{"val1":null,"val2":[{}]}`},
{JSONSchemaPropsOrArrayHolder{JSPoAOmitEmpty: &JSONSchemaPropsOrArray{JSONSchemas: []JSONSchemaProps{{}, {Type: "string"}}}}, `{"val1":null,"val2":[{},{"type":"string"}]}`},
}
for i, c := range cases {
result, err := json.Marshal(&c.input)
if err != nil {
t.Errorf("%d: Unexpected error marshaling input '%v': %v", i, c.input, err)
}
if string(result) != c.result {
t.Errorf("%d: Failed to marshal input '%v': expected: %q, got %q", i, c.input, c.result, string(result))
}
}
}

View File

@ -38,7 +38,7 @@ func Resource(resource string) schema.GroupResource {
} }
var ( var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes, addDefaultingFuncs) SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes, addDefaultingFuncs, addConversionFuncs)
localSchemeBuilder = &SchemeBuilder localSchemeBuilder = &SchemeBuilder
AddToScheme = localSchemeBuilder.AddToScheme AddToScheme = localSchemeBuilder.AddToScheme
) )
@ -52,3 +52,10 @@ func addKnownTypes(scheme *runtime.Scheme) error {
metav1.AddToGroupVersion(scheme, SchemeGroupVersion) metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil return nil
} }
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addDefaultingFuncs, addConversionFuncs)
}

View File

@ -26,9 +26,12 @@ type CustomResourceDefinitionSpec struct {
Version string `json:"version" protobuf:"bytes,2,opt,name=version"` Version string `json:"version" protobuf:"bytes,2,opt,name=version"`
// Names are the names used to describe this custom resource // Names are the names used to describe this custom resource
Names CustomResourceDefinitionNames `json:"names" protobuf:"bytes,3,opt,name=names"` Names CustomResourceDefinitionNames `json:"names" protobuf:"bytes,3,opt,name=names"`
// Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced // Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced
Scope ResourceScope `json:"scope" protobuf:"bytes,4,opt,name=scope,casttype=ResourceScope"` Scope ResourceScope `json:"scope" protobuf:"bytes,4,opt,name=scope,casttype=ResourceScope"`
// Validation describes the validation methods for CustomResources
// This field is alpha-level and should only be sent to servers that enable the CustomResourceValidation feature.
// +optional
Validation *CustomResourceValidation `json:"validation,omitempty" protobuf:"bytes,5,opt,name=validation"`
} }
// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition // CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition
@ -139,3 +142,9 @@ type CustomResourceDefinitionList struct {
// Items individual CustomResourceDefinitions // Items individual CustomResourceDefinitions
Items []CustomResourceDefinition `json:"items" protobuf:"bytes,2,rep,name=items"` Items []CustomResourceDefinition `json:"items" protobuf:"bytes,2,rep,name=items"`
} }
// CustomResourceValidation is a list of validation methods for CustomResources.
type CustomResourceValidation struct {
// OpenAPIV3Schema is the OpenAPI v3 schema to be validated against.
OpenAPIV3Schema *JSONSchemaProps `json:"openAPIV3Schema,omitempty" protobuf:"bytes,1,opt,name=openAPIV3Schema"`
}

View File

@ -0,0 +1,98 @@
/*
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 v1beta1
// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).
type JSONSchemaProps struct {
ID string `json:"id,omitempty" protobuf:"bytes,1,opt,name=id"`
Schema JSONSchemaURL `json:"$schema,omitempty" protobuf:"bytes,2,opt,name=schema"`
Ref *string `json:"$ref,omitempty" protobuf:"bytes,3,opt,name=ref"`
Description string `json:"description,omitempty" protobuf:"bytes,4,opt,name=description"`
Type string `json:"type,omitempty" protobuf:"bytes,5,opt,name=type"`
Format string `json:"format,omitempty" protobuf:"bytes,6,opt,name=format"`
Title string `json:"title,omitempty" protobuf:"bytes,7,opt,name=title"`
Default *JSON `json:"default,omitempty" protobuf:"bytes,8,opt,name=default"`
Maximum *float64 `json:"maximum,omitempty" protobuf:"bytes,9,opt,name=maximum"`
ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty" protobuf:"bytes,10,opt,name=exclusiveMaximum"`
Minimum *float64 `json:"minimum,omitempty" protobuf:"bytes,11,opt,name=minimum"`
ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty" protobuf:"bytes,12,opt,name=exclusiveMinimum"`
MaxLength *int64 `json:"maxLength,omitempty" protobuf:"bytes,13,opt,name=maxLength"`
MinLength *int64 `json:"minLength,omitempty" protobuf:"bytes,14,opt,name=minLength"`
Pattern string `json:"pattern,omitempty" protobuf:"bytes,15,opt,name=pattern"`
MaxItems *int64 `json:"maxItems,omitempty" protobuf:"bytes,16,opt,name=maxItems"`
MinItems *int64 `json:"minItems,omitempty" protobuf:"bytes,17,opt,name=minItems"`
UniqueItems bool `json:"uniqueItems,omitempty" protobuf:"bytes,18,opt,name=uniqueItems"`
MultipleOf *float64 `json:"multipleOf,omitempty" protobuf:"bytes,19,opt,name=multipleOf"`
Enum []JSON `json:"enum,omitempty" protobuf:"bytes,20,rep,name=enum"`
MaxProperties *int64 `json:"maxProperties,omitempty" protobuf:"bytes,21,opt,name=maxProperties"`
MinProperties *int64 `json:"minProperties,omitempty" protobuf:"bytes,22,opt,name=minProperties"`
Required []string `json:"required,omitempty" protobuf:"bytes,23,rep,name=required"`
Items *JSONSchemaPropsOrArray `json:"items,omitempty" protobuf:"bytes,24,opt,name=items"`
AllOf []JSONSchemaProps `json:"allOf,omitempty" protobuf:"bytes,25,rep,name=allOf"`
OneOf []JSONSchemaProps `json:"oneOf,omitempty" protobuf:"bytes,26,rep,name=oneOf"`
AnyOf []JSONSchemaProps `json:"anyOf,omitempty" protobuf:"bytes,27,rep,name=anyOf"`
Not *JSONSchemaProps `json:"not,omitempty" protobuf:"bytes,28,opt,name=not"`
Properties map[string]JSONSchemaProps `json:"properties,omitempty" protobuf:"bytes,29,rep,name=properties"`
AdditionalProperties *JSONSchemaPropsOrBool `json:"additionalProperties,omitempty" protobuf:"bytes,30,opt,name=additionalProperties"`
PatternProperties map[string]JSONSchemaProps `json:"patternProperties,omitempty" protobuf:"bytes,31,rep,name=patternProperties"`
Dependencies JSONSchemaDependencies `json:"dependencies,omitempty" protobuf:"bytes,32,opt,name=dependencies"`
AdditionalItems *JSONSchemaPropsOrBool `json:"additionalItems,omitempty" protobuf:"bytes,33,opt,name=additionalItems"`
Definitions JSONSchemaDefinitions `json:"definitions,omitempty" protobuf:"bytes,34,opt,name=definitions"`
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty" protobuf:"bytes,35,opt,name=externalDocs"`
Example *JSON `json:"example,omitempty" protobuf:"bytes,36,opt,name=example"`
}
// JSON represents any valid JSON value.
// These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil.
type JSON struct {
Raw []byte `protobuf:"bytes,1,opt,name=raw"`
}
// JSONSchemaURL represents a schema url.
type JSONSchemaURL string
// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps
// or an array of JSONSchemaProps. Mainly here for serialization purposes.
type JSONSchemaPropsOrArray struct {
Schema *JSONSchemaProps `protobuf:"bytes,1,opt,name=schema"`
JSONSchemas []JSONSchemaProps `protobuf:"bytes,2,rep,name=jSONSchemas"`
}
// JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value.
// Defaults to true for the boolean property.
type JSONSchemaPropsOrBool struct {
Allows bool `protobuf:"varint,1,opt,name=allows"`
Schema *JSONSchemaProps `protobuf:"bytes,2,opt,name=schema"`
}
// JSONSchemaDependencies represent a dependencies property.
type JSONSchemaDependencies map[string]JSONSchemaPropsOrStringArray
// JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array.
type JSONSchemaPropsOrStringArray struct {
Schema *JSONSchemaProps `protobuf:"bytes,1,opt,name=schema"`
Property []string `protobuf:"bytes,2,rep,name=property"`
}
// JSONSchemaDefinitions contains the models explicitly defined in this spec.
type JSONSchemaDefinitions map[string]JSONSchemaProps
// ExternalDocumentation allows referencing an external resource for extended documentation.
type ExternalDocumentation struct {
Description string `json:"description,omitempty" protobuf:"bytes,1,opt,name=description"`
URL string `json:"url,omitempty" protobuf:"bytes,2,opt,name=url"`
}

View File

@ -47,6 +47,20 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_apiextensions_CustomResourceDefinitionSpec_To_v1beta1_CustomResourceDefinitionSpec, Convert_apiextensions_CustomResourceDefinitionSpec_To_v1beta1_CustomResourceDefinitionSpec,
Convert_v1beta1_CustomResourceDefinitionStatus_To_apiextensions_CustomResourceDefinitionStatus, Convert_v1beta1_CustomResourceDefinitionStatus_To_apiextensions_CustomResourceDefinitionStatus,
Convert_apiextensions_CustomResourceDefinitionStatus_To_v1beta1_CustomResourceDefinitionStatus, Convert_apiextensions_CustomResourceDefinitionStatus_To_v1beta1_CustomResourceDefinitionStatus,
Convert_v1beta1_CustomResourceValidation_To_apiextensions_CustomResourceValidation,
Convert_apiextensions_CustomResourceValidation_To_v1beta1_CustomResourceValidation,
Convert_v1beta1_ExternalDocumentation_To_apiextensions_ExternalDocumentation,
Convert_apiextensions_ExternalDocumentation_To_v1beta1_ExternalDocumentation,
Convert_v1beta1_JSON_To_apiextensions_JSON,
Convert_apiextensions_JSON_To_v1beta1_JSON,
Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps,
Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps,
Convert_v1beta1_JSONSchemaPropsOrArray_To_apiextensions_JSONSchemaPropsOrArray,
Convert_apiextensions_JSONSchemaPropsOrArray_To_v1beta1_JSONSchemaPropsOrArray,
Convert_v1beta1_JSONSchemaPropsOrBool_To_apiextensions_JSONSchemaPropsOrBool,
Convert_apiextensions_JSONSchemaPropsOrBool_To_v1beta1_JSONSchemaPropsOrBool,
Convert_v1beta1_JSONSchemaPropsOrStringArray_To_apiextensions_JSONSchemaPropsOrStringArray,
Convert_apiextensions_JSONSchemaPropsOrStringArray_To_v1beta1_JSONSchemaPropsOrStringArray,
) )
} }
@ -112,7 +126,17 @@ func Convert_apiextensions_CustomResourceDefinitionCondition_To_v1beta1_CustomRe
func autoConvert_v1beta1_CustomResourceDefinitionList_To_apiextensions_CustomResourceDefinitionList(in *CustomResourceDefinitionList, out *apiextensions.CustomResourceDefinitionList, s conversion.Scope) error { func autoConvert_v1beta1_CustomResourceDefinitionList_To_apiextensions_CustomResourceDefinitionList(in *CustomResourceDefinitionList, out *apiextensions.CustomResourceDefinitionList, s conversion.Scope) error {
out.ListMeta = in.ListMeta out.ListMeta = in.ListMeta
out.Items = *(*[]apiextensions.CustomResourceDefinition)(unsafe.Pointer(&in.Items)) if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]apiextensions.CustomResourceDefinition, len(*in))
for i := range *in {
if err := Convert_v1beta1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil return nil
} }
@ -123,7 +147,17 @@ func Convert_v1beta1_CustomResourceDefinitionList_To_apiextensions_CustomResourc
func autoConvert_apiextensions_CustomResourceDefinitionList_To_v1beta1_CustomResourceDefinitionList(in *apiextensions.CustomResourceDefinitionList, out *CustomResourceDefinitionList, s conversion.Scope) error { func autoConvert_apiextensions_CustomResourceDefinitionList_To_v1beta1_CustomResourceDefinitionList(in *apiextensions.CustomResourceDefinitionList, out *CustomResourceDefinitionList, s conversion.Scope) error {
out.ListMeta = in.ListMeta out.ListMeta = in.ListMeta
out.Items = *(*[]CustomResourceDefinition)(unsafe.Pointer(&in.Items)) if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]CustomResourceDefinition, len(*in))
for i := range *in {
if err := Convert_apiextensions_CustomResourceDefinition_To_v1beta1_CustomResourceDefinition(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil return nil
} }
@ -167,6 +201,15 @@ func autoConvert_v1beta1_CustomResourceDefinitionSpec_To_apiextensions_CustomRes
return err return err
} }
out.Scope = apiextensions.ResourceScope(in.Scope) out.Scope = apiextensions.ResourceScope(in.Scope)
if in.Validation != nil {
in, out := &in.Validation, &out.Validation
*out = new(apiextensions.CustomResourceValidation)
if err := Convert_v1beta1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(*in, *out, s); err != nil {
return err
}
} else {
out.Validation = nil
}
return nil return nil
} }
@ -182,6 +225,15 @@ func autoConvert_apiextensions_CustomResourceDefinitionSpec_To_v1beta1_CustomRes
return err return err
} }
out.Scope = ResourceScope(in.Scope) out.Scope = ResourceScope(in.Scope)
if in.Validation != nil {
in, out := &in.Validation, &out.Validation
*out = new(CustomResourceValidation)
if err := Convert_apiextensions_CustomResourceValidation_To_v1beta1_CustomResourceValidation(*in, *out, s); err != nil {
return err
}
} else {
out.Validation = nil
}
return nil return nil
} }
@ -215,3 +267,562 @@ func autoConvert_apiextensions_CustomResourceDefinitionStatus_To_v1beta1_CustomR
func Convert_apiextensions_CustomResourceDefinitionStatus_To_v1beta1_CustomResourceDefinitionStatus(in *apiextensions.CustomResourceDefinitionStatus, out *CustomResourceDefinitionStatus, s conversion.Scope) error { func Convert_apiextensions_CustomResourceDefinitionStatus_To_v1beta1_CustomResourceDefinitionStatus(in *apiextensions.CustomResourceDefinitionStatus, out *CustomResourceDefinitionStatus, s conversion.Scope) error {
return autoConvert_apiextensions_CustomResourceDefinitionStatus_To_v1beta1_CustomResourceDefinitionStatus(in, out, s) return autoConvert_apiextensions_CustomResourceDefinitionStatus_To_v1beta1_CustomResourceDefinitionStatus(in, out, s)
} }
func autoConvert_v1beta1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(in *CustomResourceValidation, out *apiextensions.CustomResourceValidation, s conversion.Scope) error {
if in.OpenAPIV3Schema != nil {
in, out := &in.OpenAPIV3Schema, &out.OpenAPIV3Schema
*out = new(apiextensions.JSONSchemaProps)
if err := Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(*in, *out, s); err != nil {
return err
}
} else {
out.OpenAPIV3Schema = nil
}
return nil
}
// Convert_v1beta1_CustomResourceValidation_To_apiextensions_CustomResourceValidation is an autogenerated conversion function.
func Convert_v1beta1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(in *CustomResourceValidation, out *apiextensions.CustomResourceValidation, s conversion.Scope) error {
return autoConvert_v1beta1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(in, out, s)
}
func autoConvert_apiextensions_CustomResourceValidation_To_v1beta1_CustomResourceValidation(in *apiextensions.CustomResourceValidation, out *CustomResourceValidation, s conversion.Scope) error {
if in.OpenAPIV3Schema != nil {
in, out := &in.OpenAPIV3Schema, &out.OpenAPIV3Schema
*out = new(JSONSchemaProps)
if err := Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(*in, *out, s); err != nil {
return err
}
} else {
out.OpenAPIV3Schema = nil
}
return nil
}
// Convert_apiextensions_CustomResourceValidation_To_v1beta1_CustomResourceValidation is an autogenerated conversion function.
func Convert_apiextensions_CustomResourceValidation_To_v1beta1_CustomResourceValidation(in *apiextensions.CustomResourceValidation, out *CustomResourceValidation, s conversion.Scope) error {
return autoConvert_apiextensions_CustomResourceValidation_To_v1beta1_CustomResourceValidation(in, out, s)
}
func autoConvert_v1beta1_ExternalDocumentation_To_apiextensions_ExternalDocumentation(in *ExternalDocumentation, out *apiextensions.ExternalDocumentation, s conversion.Scope) error {
out.Description = in.Description
out.URL = in.URL
return nil
}
// Convert_v1beta1_ExternalDocumentation_To_apiextensions_ExternalDocumentation is an autogenerated conversion function.
func Convert_v1beta1_ExternalDocumentation_To_apiextensions_ExternalDocumentation(in *ExternalDocumentation, out *apiextensions.ExternalDocumentation, s conversion.Scope) error {
return autoConvert_v1beta1_ExternalDocumentation_To_apiextensions_ExternalDocumentation(in, out, s)
}
func autoConvert_apiextensions_ExternalDocumentation_To_v1beta1_ExternalDocumentation(in *apiextensions.ExternalDocumentation, out *ExternalDocumentation, s conversion.Scope) error {
out.Description = in.Description
out.URL = in.URL
return nil
}
// Convert_apiextensions_ExternalDocumentation_To_v1beta1_ExternalDocumentation is an autogenerated conversion function.
func Convert_apiextensions_ExternalDocumentation_To_v1beta1_ExternalDocumentation(in *apiextensions.ExternalDocumentation, out *ExternalDocumentation, s conversion.Scope) error {
return autoConvert_apiextensions_ExternalDocumentation_To_v1beta1_ExternalDocumentation(in, out, s)
}
func autoConvert_v1beta1_JSON_To_apiextensions_JSON(in *JSON, out *apiextensions.JSON, s conversion.Scope) error {
// WARNING: in.Raw requires manual conversion: does not exist in peer-type
return nil
}
func autoConvert_apiextensions_JSON_To_v1beta1_JSON(in *apiextensions.JSON, out *JSON, s conversion.Scope) error {
// FIXME: Type apiextensions.JSON is unsupported.
return nil
}
func autoConvert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(in *JSONSchemaProps, out *apiextensions.JSONSchemaProps, s conversion.Scope) error {
out.ID = in.ID
out.Schema = apiextensions.JSONSchemaURL(in.Schema)
out.Ref = (*string)(unsafe.Pointer(in.Ref))
out.Description = in.Description
out.Type = in.Type
out.Format = in.Format
out.Title = in.Title
if in.Default != nil {
in, out := &in.Default, &out.Default
*out = new(apiextensions.JSON)
if err := Convert_v1beta1_JSON_To_apiextensions_JSON(*in, *out, s); err != nil {
return err
}
} else {
out.Default = nil
}
out.Maximum = (*float64)(unsafe.Pointer(in.Maximum))
out.ExclusiveMaximum = in.ExclusiveMaximum
out.Minimum = (*float64)(unsafe.Pointer(in.Minimum))
out.ExclusiveMinimum = in.ExclusiveMinimum
out.MaxLength = (*int64)(unsafe.Pointer(in.MaxLength))
out.MinLength = (*int64)(unsafe.Pointer(in.MinLength))
out.Pattern = in.Pattern
out.MaxItems = (*int64)(unsafe.Pointer(in.MaxItems))
out.MinItems = (*int64)(unsafe.Pointer(in.MinItems))
out.UniqueItems = in.UniqueItems
out.MultipleOf = (*float64)(unsafe.Pointer(in.MultipleOf))
if in.Enum != nil {
in, out := &in.Enum, &out.Enum
*out = make([]apiextensions.JSON, len(*in))
for i := range *in {
if err := Convert_v1beta1_JSON_To_apiextensions_JSON(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.Enum = nil
}
out.MaxProperties = (*int64)(unsafe.Pointer(in.MaxProperties))
out.MinProperties = (*int64)(unsafe.Pointer(in.MinProperties))
out.Required = *(*[]string)(unsafe.Pointer(&in.Required))
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = new(apiextensions.JSONSchemaPropsOrArray)
if err := Convert_v1beta1_JSONSchemaPropsOrArray_To_apiextensions_JSONSchemaPropsOrArray(*in, *out, s); err != nil {
return err
}
} else {
out.Items = nil
}
if in.AllOf != nil {
in, out := &in.AllOf, &out.AllOf
*out = make([]apiextensions.JSONSchemaProps, len(*in))
for i := range *in {
if err := Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.AllOf = nil
}
if in.OneOf != nil {
in, out := &in.OneOf, &out.OneOf
*out = make([]apiextensions.JSONSchemaProps, len(*in))
for i := range *in {
if err := Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.OneOf = nil
}
if in.AnyOf != nil {
in, out := &in.AnyOf, &out.AnyOf
*out = make([]apiextensions.JSONSchemaProps, len(*in))
for i := range *in {
if err := Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.AnyOf = nil
}
if in.Not != nil {
in, out := &in.Not, &out.Not
*out = new(apiextensions.JSONSchemaProps)
if err := Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(*in, *out, s); err != nil {
return err
}
} else {
out.Not = nil
}
if in.Properties != nil {
in, out := &in.Properties, &out.Properties
*out = make(map[string]apiextensions.JSONSchemaProps, len(*in))
for key, val := range *in {
newVal := new(apiextensions.JSONSchemaProps)
if err := Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(&val, newVal, s); err != nil {
return err
}
(*out)[key] = *newVal
}
} else {
out.Properties = nil
}
if in.AdditionalProperties != nil {
in, out := &in.AdditionalProperties, &out.AdditionalProperties
*out = new(apiextensions.JSONSchemaPropsOrBool)
if err := Convert_v1beta1_JSONSchemaPropsOrBool_To_apiextensions_JSONSchemaPropsOrBool(*in, *out, s); err != nil {
return err
}
} else {
out.AdditionalProperties = nil
}
if in.PatternProperties != nil {
in, out := &in.PatternProperties, &out.PatternProperties
*out = make(map[string]apiextensions.JSONSchemaProps, len(*in))
for key, val := range *in {
newVal := new(apiextensions.JSONSchemaProps)
if err := Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(&val, newVal, s); err != nil {
return err
}
(*out)[key] = *newVal
}
} else {
out.PatternProperties = nil
}
if in.Dependencies != nil {
in, out := &in.Dependencies, &out.Dependencies
*out = make(apiextensions.JSONSchemaDependencies, len(*in))
for key, val := range *in {
newVal := new(apiextensions.JSONSchemaPropsOrStringArray)
if err := Convert_v1beta1_JSONSchemaPropsOrStringArray_To_apiextensions_JSONSchemaPropsOrStringArray(&val, newVal, s); err != nil {
return err
}
(*out)[key] = *newVal
}
} else {
out.Dependencies = nil
}
if in.AdditionalItems != nil {
in, out := &in.AdditionalItems, &out.AdditionalItems
*out = new(apiextensions.JSONSchemaPropsOrBool)
if err := Convert_v1beta1_JSONSchemaPropsOrBool_To_apiextensions_JSONSchemaPropsOrBool(*in, *out, s); err != nil {
return err
}
} else {
out.AdditionalItems = nil
}
if in.Definitions != nil {
in, out := &in.Definitions, &out.Definitions
*out = make(apiextensions.JSONSchemaDefinitions, len(*in))
for key, val := range *in {
newVal := new(apiextensions.JSONSchemaProps)
if err := Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(&val, newVal, s); err != nil {
return err
}
(*out)[key] = *newVal
}
} else {
out.Definitions = nil
}
out.ExternalDocs = (*apiextensions.ExternalDocumentation)(unsafe.Pointer(in.ExternalDocs))
if in.Example != nil {
in, out := &in.Example, &out.Example
*out = new(apiextensions.JSON)
if err := Convert_v1beta1_JSON_To_apiextensions_JSON(*in, *out, s); err != nil {
return err
}
} else {
out.Example = nil
}
return nil
}
// Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps is an autogenerated conversion function.
func Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(in *JSONSchemaProps, out *apiextensions.JSONSchemaProps, s conversion.Scope) error {
return autoConvert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(in, out, s)
}
func autoConvert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(in *apiextensions.JSONSchemaProps, out *JSONSchemaProps, s conversion.Scope) error {
out.ID = in.ID
out.Schema = JSONSchemaURL(in.Schema)
out.Ref = (*string)(unsafe.Pointer(in.Ref))
out.Description = in.Description
out.Type = in.Type
out.Format = in.Format
out.Title = in.Title
if in.Default != nil {
in, out := &in.Default, &out.Default
*out = new(JSON)
if err := Convert_apiextensions_JSON_To_v1beta1_JSON(*in, *out, s); err != nil {
return err
}
} else {
out.Default = nil
}
out.Maximum = (*float64)(unsafe.Pointer(in.Maximum))
out.ExclusiveMaximum = in.ExclusiveMaximum
out.Minimum = (*float64)(unsafe.Pointer(in.Minimum))
out.ExclusiveMinimum = in.ExclusiveMinimum
out.MaxLength = (*int64)(unsafe.Pointer(in.MaxLength))
out.MinLength = (*int64)(unsafe.Pointer(in.MinLength))
out.Pattern = in.Pattern
out.MaxItems = (*int64)(unsafe.Pointer(in.MaxItems))
out.MinItems = (*int64)(unsafe.Pointer(in.MinItems))
out.UniqueItems = in.UniqueItems
out.MultipleOf = (*float64)(unsafe.Pointer(in.MultipleOf))
if in.Enum != nil {
in, out := &in.Enum, &out.Enum
*out = make([]JSON, len(*in))
for i := range *in {
if err := Convert_apiextensions_JSON_To_v1beta1_JSON(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.Enum = nil
}
out.MaxProperties = (*int64)(unsafe.Pointer(in.MaxProperties))
out.MinProperties = (*int64)(unsafe.Pointer(in.MinProperties))
out.Required = *(*[]string)(unsafe.Pointer(&in.Required))
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = new(JSONSchemaPropsOrArray)
if err := Convert_apiextensions_JSONSchemaPropsOrArray_To_v1beta1_JSONSchemaPropsOrArray(*in, *out, s); err != nil {
return err
}
} else {
out.Items = nil
}
if in.AllOf != nil {
in, out := &in.AllOf, &out.AllOf
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
if err := Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.AllOf = nil
}
if in.OneOf != nil {
in, out := &in.OneOf, &out.OneOf
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
if err := Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.OneOf = nil
}
if in.AnyOf != nil {
in, out := &in.AnyOf, &out.AnyOf
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
if err := Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.AnyOf = nil
}
if in.Not != nil {
in, out := &in.Not, &out.Not
*out = new(JSONSchemaProps)
if err := Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(*in, *out, s); err != nil {
return err
}
} else {
out.Not = nil
}
if in.Properties != nil {
in, out := &in.Properties, &out.Properties
*out = make(map[string]JSONSchemaProps, len(*in))
for key, val := range *in {
newVal := new(JSONSchemaProps)
if err := Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(&val, newVal, s); err != nil {
return err
}
(*out)[key] = *newVal
}
} else {
out.Properties = nil
}
if in.AdditionalProperties != nil {
in, out := &in.AdditionalProperties, &out.AdditionalProperties
*out = new(JSONSchemaPropsOrBool)
if err := Convert_apiextensions_JSONSchemaPropsOrBool_To_v1beta1_JSONSchemaPropsOrBool(*in, *out, s); err != nil {
return err
}
} else {
out.AdditionalProperties = nil
}
if in.PatternProperties != nil {
in, out := &in.PatternProperties, &out.PatternProperties
*out = make(map[string]JSONSchemaProps, len(*in))
for key, val := range *in {
newVal := new(JSONSchemaProps)
if err := Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(&val, newVal, s); err != nil {
return err
}
(*out)[key] = *newVal
}
} else {
out.PatternProperties = nil
}
if in.Dependencies != nil {
in, out := &in.Dependencies, &out.Dependencies
*out = make(JSONSchemaDependencies, len(*in))
for key, val := range *in {
newVal := new(JSONSchemaPropsOrStringArray)
if err := Convert_apiextensions_JSONSchemaPropsOrStringArray_To_v1beta1_JSONSchemaPropsOrStringArray(&val, newVal, s); err != nil {
return err
}
(*out)[key] = *newVal
}
} else {
out.Dependencies = nil
}
if in.AdditionalItems != nil {
in, out := &in.AdditionalItems, &out.AdditionalItems
*out = new(JSONSchemaPropsOrBool)
if err := Convert_apiextensions_JSONSchemaPropsOrBool_To_v1beta1_JSONSchemaPropsOrBool(*in, *out, s); err != nil {
return err
}
} else {
out.AdditionalItems = nil
}
if in.Definitions != nil {
in, out := &in.Definitions, &out.Definitions
*out = make(JSONSchemaDefinitions, len(*in))
for key, val := range *in {
newVal := new(JSONSchemaProps)
if err := Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(&val, newVal, s); err != nil {
return err
}
(*out)[key] = *newVal
}
} else {
out.Definitions = nil
}
out.ExternalDocs = (*ExternalDocumentation)(unsafe.Pointer(in.ExternalDocs))
if in.Example != nil {
in, out := &in.Example, &out.Example
*out = new(JSON)
if err := Convert_apiextensions_JSON_To_v1beta1_JSON(*in, *out, s); err != nil {
return err
}
} else {
out.Example = nil
}
return nil
}
func autoConvert_v1beta1_JSONSchemaPropsOrArray_To_apiextensions_JSONSchemaPropsOrArray(in *JSONSchemaPropsOrArray, out *apiextensions.JSONSchemaPropsOrArray, s conversion.Scope) error {
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = new(apiextensions.JSONSchemaProps)
if err := Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(*in, *out, s); err != nil {
return err
}
} else {
out.Schema = nil
}
if in.JSONSchemas != nil {
in, out := &in.JSONSchemas, &out.JSONSchemas
*out = make([]apiextensions.JSONSchemaProps, len(*in))
for i := range *in {
if err := Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.JSONSchemas = nil
}
return nil
}
// Convert_v1beta1_JSONSchemaPropsOrArray_To_apiextensions_JSONSchemaPropsOrArray is an autogenerated conversion function.
func Convert_v1beta1_JSONSchemaPropsOrArray_To_apiextensions_JSONSchemaPropsOrArray(in *JSONSchemaPropsOrArray, out *apiextensions.JSONSchemaPropsOrArray, s conversion.Scope) error {
return autoConvert_v1beta1_JSONSchemaPropsOrArray_To_apiextensions_JSONSchemaPropsOrArray(in, out, s)
}
func autoConvert_apiextensions_JSONSchemaPropsOrArray_To_v1beta1_JSONSchemaPropsOrArray(in *apiextensions.JSONSchemaPropsOrArray, out *JSONSchemaPropsOrArray, s conversion.Scope) error {
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = new(JSONSchemaProps)
if err := Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(*in, *out, s); err != nil {
return err
}
} else {
out.Schema = nil
}
if in.JSONSchemas != nil {
in, out := &in.JSONSchemas, &out.JSONSchemas
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
if err := Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.JSONSchemas = nil
}
return nil
}
// Convert_apiextensions_JSONSchemaPropsOrArray_To_v1beta1_JSONSchemaPropsOrArray is an autogenerated conversion function.
func Convert_apiextensions_JSONSchemaPropsOrArray_To_v1beta1_JSONSchemaPropsOrArray(in *apiextensions.JSONSchemaPropsOrArray, out *JSONSchemaPropsOrArray, s conversion.Scope) error {
return autoConvert_apiextensions_JSONSchemaPropsOrArray_To_v1beta1_JSONSchemaPropsOrArray(in, out, s)
}
func autoConvert_v1beta1_JSONSchemaPropsOrBool_To_apiextensions_JSONSchemaPropsOrBool(in *JSONSchemaPropsOrBool, out *apiextensions.JSONSchemaPropsOrBool, s conversion.Scope) error {
out.Allows = in.Allows
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = new(apiextensions.JSONSchemaProps)
if err := Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(*in, *out, s); err != nil {
return err
}
} else {
out.Schema = nil
}
return nil
}
// Convert_v1beta1_JSONSchemaPropsOrBool_To_apiextensions_JSONSchemaPropsOrBool is an autogenerated conversion function.
func Convert_v1beta1_JSONSchemaPropsOrBool_To_apiextensions_JSONSchemaPropsOrBool(in *JSONSchemaPropsOrBool, out *apiextensions.JSONSchemaPropsOrBool, s conversion.Scope) error {
return autoConvert_v1beta1_JSONSchemaPropsOrBool_To_apiextensions_JSONSchemaPropsOrBool(in, out, s)
}
func autoConvert_apiextensions_JSONSchemaPropsOrBool_To_v1beta1_JSONSchemaPropsOrBool(in *apiextensions.JSONSchemaPropsOrBool, out *JSONSchemaPropsOrBool, s conversion.Scope) error {
out.Allows = in.Allows
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = new(JSONSchemaProps)
if err := Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(*in, *out, s); err != nil {
return err
}
} else {
out.Schema = nil
}
return nil
}
// Convert_apiextensions_JSONSchemaPropsOrBool_To_v1beta1_JSONSchemaPropsOrBool is an autogenerated conversion function.
func Convert_apiextensions_JSONSchemaPropsOrBool_To_v1beta1_JSONSchemaPropsOrBool(in *apiextensions.JSONSchemaPropsOrBool, out *JSONSchemaPropsOrBool, s conversion.Scope) error {
return autoConvert_apiextensions_JSONSchemaPropsOrBool_To_v1beta1_JSONSchemaPropsOrBool(in, out, s)
}
func autoConvert_v1beta1_JSONSchemaPropsOrStringArray_To_apiextensions_JSONSchemaPropsOrStringArray(in *JSONSchemaPropsOrStringArray, out *apiextensions.JSONSchemaPropsOrStringArray, s conversion.Scope) error {
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = new(apiextensions.JSONSchemaProps)
if err := Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(*in, *out, s); err != nil {
return err
}
} else {
out.Schema = nil
}
out.Property = *(*[]string)(unsafe.Pointer(&in.Property))
return nil
}
// Convert_v1beta1_JSONSchemaPropsOrStringArray_To_apiextensions_JSONSchemaPropsOrStringArray is an autogenerated conversion function.
func Convert_v1beta1_JSONSchemaPropsOrStringArray_To_apiextensions_JSONSchemaPropsOrStringArray(in *JSONSchemaPropsOrStringArray, out *apiextensions.JSONSchemaPropsOrStringArray, s conversion.Scope) error {
return autoConvert_v1beta1_JSONSchemaPropsOrStringArray_To_apiextensions_JSONSchemaPropsOrStringArray(in, out, s)
}
func autoConvert_apiextensions_JSONSchemaPropsOrStringArray_To_v1beta1_JSONSchemaPropsOrStringArray(in *apiextensions.JSONSchemaPropsOrStringArray, out *JSONSchemaPropsOrStringArray, s conversion.Scope) error {
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = new(JSONSchemaProps)
if err := Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(*in, *out, s); err != nil {
return err
}
} else {
out.Schema = nil
}
out.Property = *(*[]string)(unsafe.Pointer(&in.Property))
return nil
}
// Convert_apiextensions_JSONSchemaPropsOrStringArray_To_v1beta1_JSONSchemaPropsOrStringArray is an autogenerated conversion function.
func Convert_apiextensions_JSONSchemaPropsOrStringArray_To_v1beta1_JSONSchemaPropsOrStringArray(in *apiextensions.JSONSchemaPropsOrStringArray, out *JSONSchemaPropsOrStringArray, s conversion.Scope) error {
return autoConvert_apiextensions_JSONSchemaPropsOrStringArray_To_v1beta1_JSONSchemaPropsOrStringArray(in, out, s)
}

View File

@ -60,6 +60,34 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
in.(*CustomResourceDefinitionStatus).DeepCopyInto(out.(*CustomResourceDefinitionStatus)) in.(*CustomResourceDefinitionStatus).DeepCopyInto(out.(*CustomResourceDefinitionStatus))
return nil return nil
}, InType: reflect.TypeOf(&CustomResourceDefinitionStatus{})}, }, InType: reflect.TypeOf(&CustomResourceDefinitionStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*CustomResourceValidation).DeepCopyInto(out.(*CustomResourceValidation))
return nil
}, InType: reflect.TypeOf(&CustomResourceValidation{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*ExternalDocumentation).DeepCopyInto(out.(*ExternalDocumentation))
return nil
}, InType: reflect.TypeOf(&ExternalDocumentation{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*JSON).DeepCopyInto(out.(*JSON))
return nil
}, InType: reflect.TypeOf(&JSON{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*JSONSchemaProps).DeepCopyInto(out.(*JSONSchemaProps))
return nil
}, InType: reflect.TypeOf(&JSONSchemaProps{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*JSONSchemaPropsOrArray).DeepCopyInto(out.(*JSONSchemaPropsOrArray))
return nil
}, InType: reflect.TypeOf(&JSONSchemaPropsOrArray{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*JSONSchemaPropsOrBool).DeepCopyInto(out.(*JSONSchemaPropsOrBool))
return nil
}, InType: reflect.TypeOf(&JSONSchemaPropsOrBool{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*JSONSchemaPropsOrStringArray).DeepCopyInto(out.(*JSONSchemaPropsOrStringArray))
return nil
}, InType: reflect.TypeOf(&JSONSchemaPropsOrStringArray{})},
) )
} }
@ -168,6 +196,15 @@ func (in *CustomResourceDefinitionNames) DeepCopy() *CustomResourceDefinitionNam
func (in *CustomResourceDefinitionSpec) DeepCopyInto(out *CustomResourceDefinitionSpec) { func (in *CustomResourceDefinitionSpec) DeepCopyInto(out *CustomResourceDefinitionSpec) {
*out = *in *out = *in
in.Names.DeepCopyInto(&out.Names) in.Names.DeepCopyInto(&out.Names)
if in.Validation != nil {
in, out := &in.Validation, &out.Validation
if *in == nil {
*out = nil
} else {
*out = new(CustomResourceValidation)
(*in).DeepCopyInto(*out)
}
}
return return
} }
@ -204,3 +241,159 @@ func (in *CustomResourceDefinitionStatus) DeepCopy() *CustomResourceDefinitionSt
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceValidation) DeepCopyInto(out *CustomResourceValidation) {
*out = *in
if in.OpenAPIV3Schema != nil {
in, out := &in.OpenAPIV3Schema, &out.OpenAPIV3Schema
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaProps)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceValidation.
func (in *CustomResourceValidation) DeepCopy() *CustomResourceValidation {
if in == nil {
return nil
}
out := new(CustomResourceValidation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalDocumentation) DeepCopyInto(out *ExternalDocumentation) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalDocumentation.
func (in *ExternalDocumentation) DeepCopy() *ExternalDocumentation {
if in == nil {
return nil
}
out := new(ExternalDocumentation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSON) DeepCopyInto(out *JSON) {
*out = *in
if in.Raw != nil {
in, out := &in.Raw, &out.Raw
*out = make([]byte, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSON.
func (in *JSON) DeepCopy() *JSON {
if in == nil {
return nil
}
out := new(JSON)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONSchemaProps) DeepCopyInto(out *JSONSchemaProps) {
clone := in.DeepCopy()
*out = *clone
return
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONSchemaPropsOrArray) DeepCopyInto(out *JSONSchemaPropsOrArray) {
*out = *in
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaProps)
(*in).DeepCopyInto(*out)
}
}
if in.JSONSchemas != nil {
in, out := &in.JSONSchemas, &out.JSONSchemas
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrArray.
func (in *JSONSchemaPropsOrArray) DeepCopy() *JSONSchemaPropsOrArray {
if in == nil {
return nil
}
out := new(JSONSchemaPropsOrArray)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONSchemaPropsOrBool) DeepCopyInto(out *JSONSchemaPropsOrBool) {
*out = *in
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaProps)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrBool.
func (in *JSONSchemaPropsOrBool) DeepCopy() *JSONSchemaPropsOrBool {
if in == nil {
return nil
}
out := new(JSONSchemaPropsOrBool)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONSchemaPropsOrStringArray) DeepCopyInto(out *JSONSchemaPropsOrStringArray) {
*out = *in
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaProps)
(*in).DeepCopyInto(*out)
}
}
if in.Property != nil {
in, out := &in.Property, &out.Property
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrStringArray.
func (in *JSONSchemaPropsOrStringArray) DeepCopy() *JSONSchemaPropsOrStringArray {
if in == nil {
return nil
}
out := new(JSONSchemaPropsOrStringArray)
in.DeepCopyInto(out)
return out
}

View File

@ -10,10 +10,13 @@ go_library(
name = "go_default_library", name = "go_default_library",
srcs = ["validation.go"], srcs = ["validation.go"],
deps = [ 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/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/api/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/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/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" "fmt"
"strings" "strings"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "github.com/go-openapi/spec"
genericvalidation "k8s.io/apimachinery/pkg/api/validation" genericvalidation "k8s.io/apimachinery/pkg/api/validation"
validationutil "k8s.io/apimachinery/pkg/util/validation" validationutil "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field" "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 // ValidateCustomResourceDefinition statically validates
@ -100,6 +105,12 @@ func ValidateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefi
allErrs = append(allErrs, ValidateCustomResourceDefinitionNames(&spec.Names, fldPath.Child("names"))...) 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 return allErrs
} }
@ -158,3 +169,162 @@ func ValidateCustomResourceDefinitionNames(names *apiextensions.CustomResourceDe
return allErrs 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

@ -60,6 +60,30 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
in.(*CustomResourceDefinitionStatus).DeepCopyInto(out.(*CustomResourceDefinitionStatus)) in.(*CustomResourceDefinitionStatus).DeepCopyInto(out.(*CustomResourceDefinitionStatus))
return nil return nil
}, InType: reflect.TypeOf(&CustomResourceDefinitionStatus{})}, }, InType: reflect.TypeOf(&CustomResourceDefinitionStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*CustomResourceValidation).DeepCopyInto(out.(*CustomResourceValidation))
return nil
}, InType: reflect.TypeOf(&CustomResourceValidation{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*ExternalDocumentation).DeepCopyInto(out.(*ExternalDocumentation))
return nil
}, InType: reflect.TypeOf(&ExternalDocumentation{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*JSONSchemaProps).DeepCopyInto(out.(*JSONSchemaProps))
return nil
}, InType: reflect.TypeOf(&JSONSchemaProps{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*JSONSchemaPropsOrArray).DeepCopyInto(out.(*JSONSchemaPropsOrArray))
return nil
}, InType: reflect.TypeOf(&JSONSchemaPropsOrArray{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*JSONSchemaPropsOrBool).DeepCopyInto(out.(*JSONSchemaPropsOrBool))
return nil
}, InType: reflect.TypeOf(&JSONSchemaPropsOrBool{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*JSONSchemaPropsOrStringArray).DeepCopyInto(out.(*JSONSchemaPropsOrStringArray))
return nil
}, InType: reflect.TypeOf(&JSONSchemaPropsOrStringArray{})},
) )
} }
@ -168,6 +192,15 @@ func (in *CustomResourceDefinitionNames) DeepCopy() *CustomResourceDefinitionNam
func (in *CustomResourceDefinitionSpec) DeepCopyInto(out *CustomResourceDefinitionSpec) { func (in *CustomResourceDefinitionSpec) DeepCopyInto(out *CustomResourceDefinitionSpec) {
*out = *in *out = *in
in.Names.DeepCopyInto(&out.Names) in.Names.DeepCopyInto(&out.Names)
if in.Validation != nil {
in, out := &in.Validation, &out.Validation
if *in == nil {
*out = nil
} else {
*out = new(CustomResourceValidation)
(*in).DeepCopyInto(*out)
}
}
return return
} }
@ -204,3 +237,138 @@ func (in *CustomResourceDefinitionStatus) DeepCopy() *CustomResourceDefinitionSt
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceValidation) DeepCopyInto(out *CustomResourceValidation) {
*out = *in
if in.OpenAPIV3Schema != nil {
in, out := &in.OpenAPIV3Schema, &out.OpenAPIV3Schema
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaProps)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceValidation.
func (in *CustomResourceValidation) DeepCopy() *CustomResourceValidation {
if in == nil {
return nil
}
out := new(CustomResourceValidation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalDocumentation) DeepCopyInto(out *ExternalDocumentation) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalDocumentation.
func (in *ExternalDocumentation) DeepCopy() *ExternalDocumentation {
if in == nil {
return nil
}
out := new(ExternalDocumentation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONSchemaProps) DeepCopyInto(out *JSONSchemaProps) {
clone := in.DeepCopy()
*out = *clone
return
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONSchemaPropsOrArray) DeepCopyInto(out *JSONSchemaPropsOrArray) {
*out = *in
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaProps)
(*in).DeepCopyInto(*out)
}
}
if in.JSONSchemas != nil {
in, out := &in.JSONSchemas, &out.JSONSchemas
*out = make([]JSONSchemaProps, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrArray.
func (in *JSONSchemaPropsOrArray) DeepCopy() *JSONSchemaPropsOrArray {
if in == nil {
return nil
}
out := new(JSONSchemaPropsOrArray)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONSchemaPropsOrBool) DeepCopyInto(out *JSONSchemaPropsOrBool) {
*out = *in
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaProps)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrBool.
func (in *JSONSchemaPropsOrBool) DeepCopy() *JSONSchemaPropsOrBool {
if in == nil {
return nil
}
out := new(JSONSchemaPropsOrBool)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONSchemaPropsOrStringArray) DeepCopyInto(out *JSONSchemaPropsOrStringArray) {
*out = *in
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
if *in == nil {
*out = nil
} else {
*out = new(JSONSchemaProps)
(*in).DeepCopyInto(*out)
}
}
if in.Property != nil {
in, out := &in.Property, &out.Property
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrStringArray.
func (in *JSONSchemaPropsOrStringArray) DeepCopy() *JSONSchemaPropsOrStringArray {
if in == nil {
return nil
}
out := new(JSONSchemaPropsOrStringArray)
in.DeepCopyInto(out)
return out
}

View File

@ -14,10 +14,14 @@ go_library(
"customresource_handler.go", "customresource_handler.go",
], ],
deps = [ 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/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:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install: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/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/clientset:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset: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", "//vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions:go_default_library",
@ -68,6 +72,9 @@ filegroup(
filegroup( filegroup(
name = "all-srcs", name = "all-srcs",
srcs = [":package-srcs"], srcs = [
":package-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:all-srcs",
],
tags = ["automanaged"], tags = ["automanaged"],
) )

View File

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

View File

@ -26,6 +26,11 @@ import (
"sync/atomic" "sync/atomic"
"time" "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" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -44,8 +49,11 @@ import (
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/storage/storagebackend" "k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/client-go/discovery" "k8s.io/client-go/discovery"
cache "k8s.io/client-go/tools/cache"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "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" listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
"k8s.io/apiextensions-apiserver/pkg/controller/finalizer" "k8s.io/apiextensions-apiserver/pkg/controller/finalizer"
"k8s.io/apiextensions-apiserver/pkg/registry/customresource" "k8s.io/apiextensions-apiserver/pkg/registry/customresource"
@ -84,6 +92,7 @@ func NewCustomResourceDefinitionHandler(
groupDiscoveryHandler *groupDiscoveryHandler, groupDiscoveryHandler *groupDiscoveryHandler,
requestContextMapper apirequest.RequestContextMapper, requestContextMapper apirequest.RequestContextMapper,
crdLister listers.CustomResourceDefinitionLister, crdLister listers.CustomResourceDefinitionLister,
crdInformer informers.CustomResourceDefinitionInformer,
delegate http.Handler, delegate http.Handler,
restOptionsGetter generic.RESTOptionsGetter, restOptionsGetter generic.RESTOptionsGetter,
admission admission.Interface) *crdHandler { admission admission.Interface) *crdHandler {
@ -98,6 +107,10 @@ func NewCustomResourceDefinitionHandler(
admission: admission, admission: admission,
} }
crdInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
UpdateFunc: ret.updateCustomResourceDefinition,
})
ret.customStorage.Store(crdStorageMap{}) ret.customStorage.Store(crdStorageMap{})
return ret return ret
} }
@ -155,7 +168,12 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
terminating := apiextensions.IsCRDConditionTrue(crd, apiextensions.Terminating) 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 storage := crdInfo.storage
requestScope := crdInfo.requestScope requestScope := crdInfo.requestScope
minRequestTimeout := 1 * time.Minute minRequestTimeout := 1 * time.Minute
@ -250,15 +268,18 @@ func (r *crdHandler) removeDeadStorage() {
// GetCustomResourceListerCollectionDeleter returns the ListerCollectionDeleter for // GetCustomResourceListerCollectionDeleter returns the ListerCollectionDeleter for
// the given uid, or nil if one does not exist. // the given uid, or nil if one does not exist.
func (r *crdHandler) GetCustomResourceListerCollectionDeleter(crd *apiextensions.CustomResourceDefinition) finalizer.ListerCollectionDeleter { 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 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) storageMap := r.customStorage.Load().(crdStorageMap)
ret, ok := storageMap[crd.UID] ret, ok := storageMap[crd.UID]
if ok { if ok {
return ret return ret, nil
} }
r.customStorageLock.Lock() r.customStorageLock.Lock()
@ -266,7 +287,7 @@ func (r *crdHandler) getServingInfoFor(crd *apiextensions.CustomResourceDefiniti
ret, ok = storageMap[crd.UID] ret, ok = storageMap[crd.UID]
if ok { if ok {
return ret return ret, nil
} }
// In addition to Unstructured objects (Custom Resources), we also may sometimes need to // 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), unstructuredTyper: discovery.NewUnstructuredObjectTyper(nil),
} }
creator := unstructuredCreator{} 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( storage := customresource.NewREST(
schema.GroupResource{Group: crd.Spec.Group, Resource: crd.Spec.Names.Plural}, 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}, 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, typer,
crd.Spec.Scope == apiextensions.NamespaceScoped, crd.Spec.Scope == apiextensions.NamespaceScoped,
kind, kind,
validator,
), ),
r.restOptionsGetter, r.restOptionsGetter,
) )
@ -354,7 +387,27 @@ func (r *crdHandler) getServingInfoFor(crd *apiextensions.CustomResourceDefiniti
storageMap2[crd.UID] = ret storageMap2[crd.UID] = ret
r.customStorage.Store(storageMap2) 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 { 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

@ -0,0 +1,25 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["kube_features.go"],
deps = ["//vendor/k8s.io/apiserver/pkg/util/feature: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,46 @@
/*
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 features
import (
utilfeature "k8s.io/apiserver/pkg/util/feature"
)
const (
// Every feature gate should add method here following this template:
//
// // owner: @username
// // alpha: v1.4
// MyFeature() bool
// owner: @sttts, @nikhita
// alpha: v1.8
//
// CustomResourceValidation is a list of validation methods for CustomResources
CustomResourceValidation utilfeature.Feature = "CustomResourceValidation"
)
func init() {
utilfeature.DefaultFeatureGate.Add(defaultKubernetesFeatureGates)
}
// defaultKubernetesFeatureGates consists of all known Kubernetes-specific feature keys.
// To add a new feature, define a key for it above and add it here. The features will be
// available throughout Kubernetes binaries.
var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureSpec{
CustomResourceValidation: {Default: false, PreRelease: utilfeature.Alpha},
}

View File

@ -12,6 +12,8 @@ go_library(
"strategy.go", "strategy.go",
], ],
deps = [ 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/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",

View File

@ -19,9 +19,12 @@ package customresource
import ( import (
"fmt" "fmt"
"github.com/go-openapi/validate"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/validation" "k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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/fields"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -30,6 +33,8 @@ import (
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names" "k8s.io/apiserver/pkg/storage/names"
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
) )
type customResourceDefinitionStorageStrategy struct { type customResourceDefinitionStorageStrategy struct {
@ -40,7 +45,7 @@ type customResourceDefinitionStorageStrategy struct {
validator customResourceValidator 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{ return customResourceDefinitionStorageStrategy{
ObjectTyper: typer, ObjectTyper: typer,
NameGenerator: names.SimpleNameGenerator, NameGenerator: names.SimpleNameGenerator,
@ -48,6 +53,7 @@ func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.Gr
validator: customResourceValidator{ validator: customResourceValidator{
namespaceScoped: namespaceScoped, namespaceScoped: namespaceScoped,
kind: kind, kind: kind,
validator: validator,
}, },
} }
} }
@ -113,6 +119,7 @@ func (a customResourceDefinitionStorageStrategy) MatchCustomResourceDefinitionSt
type customResourceValidator struct { type customResourceValidator struct {
namespaceScoped bool namespaceScoped bool
kind schema.GroupVersionKind kind schema.GroupVersionKind
validator *validate.SchemaValidator
} }
func (a customResourceValidator) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { 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))} 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")) 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))} 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")) return validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))
} }

View File

@ -14,6 +14,7 @@ go_library(
deps = [ deps = [
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
@ -28,6 +29,7 @@ go_library(
"//vendor/k8s.io/apiserver/pkg/storage:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/errors:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/errors:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
], ],
) )

View File

@ -28,9 +28,11 @@ import (
"k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names" "k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
) )
type strategy struct { type strategy struct {
@ -50,6 +52,11 @@ func (strategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Obje
crd := obj.(*apiextensions.CustomResourceDefinition) crd := obj.(*apiextensions.CustomResourceDefinition)
crd.Status = apiextensions.CustomResourceDefinitionStatus{} crd.Status = apiextensions.CustomResourceDefinitionStatus{}
crd.Generation = 1 crd.Generation = 1
// if the feature gate is disabled, drop the feature.
if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceValidation) {
crd.Spec.Validation = nil
}
} }
func (strategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { func (strategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) {
@ -68,6 +75,11 @@ func (strategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime
if !apiequality.Semantic.DeepEqual(oldCRD.Spec, newCRD.Spec) { if !apiequality.Semantic.DeepEqual(oldCRD.Spec, newCRD.Spec) {
newCRD.Generation = oldCRD.Generation + 1 newCRD.Generation = oldCRD.Generation + 1
} }
if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceValidation) {
newCRD.Spec.Validation = nil
oldCRD.Spec.Validation = nil
}
} }
func (strategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { func (strategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {

View File

@ -32,6 +32,7 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library",
], ],
) )

View File

@ -213,7 +213,7 @@ func checkForWatchCachePrimed(crd *apiextensionsv1beta1.CustomResourceDefinition
return err return err
} }
instanceName := "foo" instanceName := "setup-instance"
instance := &unstructured.Unstructured{ instance := &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
"apiVersion": crd.Spec.Group + "/" + crd.Spec.Version, "apiVersion": crd.Spec.Group + "/" + crd.Spec.Version,
@ -222,6 +222,11 @@ func checkForWatchCachePrimed(crd *apiextensionsv1beta1.CustomResourceDefinition
"namespace": ns, "namespace": ns,
"name": instanceName, "name": instanceName,
}, },
"alpha": "foo_123",
"beta": 10,
"gamma": "bar",
"delta": "hello",
"epsilon": "foobar",
}, },
} }
if _, err := resourceClient.Create(instance); err != nil { if _, err := resourceClient.Create(instance); err != nil {

View File

@ -19,10 +19,15 @@ package integration
import ( import (
"strings" "strings"
"testing" "testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apiextensions-apiserver/test/integration/testserver" "k8s.io/apiextensions-apiserver/test/integration/testserver"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
) )
func TestForProperValidationErrors(t *testing.T) { func TestForProperValidationErrors(t *testing.T) {
@ -79,3 +84,368 @@ func TestForProperValidationErrors(t *testing.T) {
} }
} }
} }
func newNoxuValidationCRD(scope apiextensionsv1beta1.ResourceScope) *apiextensionsv1beta1.CustomResourceDefinition {
return &apiextensionsv1beta1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "noxus.mygroup.example.com"},
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
Group: "mygroup.example.com",
Version: "v1beta1",
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
Plural: "noxus",
Singular: "nonenglishnoxu",
Kind: "WishIHadChosenNoxu",
ShortNames: []string{"foo", "bar", "abc", "def"},
ListKind: "NoxuItemList",
},
Scope: apiextensionsv1beta1.NamespaceScoped,
Validation: &apiextensionsv1beta1.CustomResourceValidation{
OpenAPIV3Schema: &apiextensionsv1beta1.JSONSchemaProps{
Required: []string{"alpha", "beta"},
AdditionalProperties: &apiextensionsv1beta1.JSONSchemaPropsOrBool{
Allows: true,
},
Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
"alpha": {
Description: "Alpha is an alphanumeric string with underscores",
Type: "string",
Pattern: "^[a-zA-Z0-9_]*$",
},
"beta": {
Description: "Minimum value of beta is 10",
Type: "number",
Minimum: float64Ptr(10),
},
"gamma": {
Description: "Gamma is restricted to foo, bar and baz",
Type: "string",
Enum: []apiextensionsv1beta1.JSON{
{
Raw: []byte(`"foo"`),
},
{
Raw: []byte(`"bar"`),
},
{
Raw: []byte(`"baz"`),
},
},
},
"delta": {
Description: "Delta is a string with a maximum length of 5 or a number with a minimum value of 0",
AnyOf: []apiextensionsv1beta1.JSONSchemaProps{
{
Type: "string",
MaxLength: int64Ptr(5),
},
{
Type: "number",
Minimum: float64Ptr(0),
},
},
},
},
},
},
},
}
}
func newNoxuValidationInstance(namespace, name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "mygroup.example.com/v1beta1",
"kind": "WishIHadChosenNoxu",
"metadata": map[string]interface{}{
"namespace": namespace,
"name": name,
},
"alpha": "foo_123",
"beta": 10,
"gamma": "bar",
"delta": "hello",
},
}
}
func TestCustomResourceValidation(t *testing.T) {
stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer()
if err != nil {
t.Fatal(err)
}
defer close(stopCh)
// enable alpha feature CustomResourceValidation
err = utilfeature.DefaultFeatureGate.Set("CustomResourceValidation=true")
if err != nil {
t.Errorf("failed to enable feature gate for CustomResourceValidation: %v", err)
}
noxuDefinition := newNoxuValidationCRD(apiextensionsv1beta1.NamespaceScoped)
noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool)
if err != nil {
t.Fatal(err)
}
ns := "not-the-default"
noxuResourceClient := NewNamespacedCustomResourceClient(ns, noxuVersionClient, noxuDefinition)
_, err = instantiateCustomResource(t, newNoxuValidationInstance(ns, "foo"), noxuResourceClient, noxuDefinition)
if err != nil {
t.Fatalf("unable to create noxu instance: %v", err)
}
}
func TestCustomResourceUpdateValidation(t *testing.T) {
stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer()
if err != nil {
t.Fatal(err)
}
defer close(stopCh)
// enable alpha feature CustomResourceValidation
err = utilfeature.DefaultFeatureGate.Set("CustomResourceValidation=true")
if err != nil {
t.Errorf("failed to enable feature gate for CustomResourceValidation: %v", err)
}
noxuDefinition := newNoxuValidationCRD(apiextensionsv1beta1.NamespaceScoped)
noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool)
if err != nil {
t.Fatal(err)
}
ns := "not-the-default"
noxuResourceClient := NewNamespacedCustomResourceClient(ns, noxuVersionClient, noxuDefinition)
_, err = instantiateCustomResource(t, newNoxuValidationInstance(ns, "foo"), noxuResourceClient, noxuDefinition)
if err != nil {
t.Fatalf("unable to create noxu instance: %v", err)
}
gottenNoxuInstance, err := noxuResourceClient.Get("foo", metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
// invalidate the instance
gottenNoxuInstance.Object = map[string]interface{}{
"apiVersion": "mygroup.example.com/v1beta1",
"kind": "WishIHadChosenNoxu",
"metadata": map[string]interface{}{
"namespace": "not-the-default",
"name": "foo",
},
"gamma": "bar",
"delta": "hello",
}
_, err = noxuResourceClient.Update(gottenNoxuInstance)
if err == nil {
t.Fatalf("unexpected non-error: alpha and beta should be present while updating %v", gottenNoxuInstance)
}
}
func TestCustomResourceValidationErrors(t *testing.T) {
stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer()
if err != nil {
t.Fatal(err)
}
defer close(stopCh)
// enable alpha feature CustomResourceValidation
err = utilfeature.DefaultFeatureGate.Set("CustomResourceValidation=true")
if err != nil {
t.Errorf("failed to enable feature gate for CustomResourceValidation: %v", err)
}
noxuDefinition := newNoxuValidationCRD(apiextensionsv1beta1.NamespaceScoped)
noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool)
if err != nil {
t.Fatal(err)
}
ns := "not-the-default"
noxuResourceClient := NewNamespacedCustomResourceClient(ns, noxuVersionClient, noxuDefinition)
tests := []struct {
name string
instanceFn func() *unstructured.Unstructured
expectedError string
}{
{
name: "bad alpha",
instanceFn: func() *unstructured.Unstructured {
instance := newNoxuValidationInstance(ns, "foo")
instance.Object["alpha"] = "foo_123!"
return instance
},
expectedError: "alpha in body should match '^[a-zA-Z0-9_]*$'",
},
{
name: "bad beta",
instanceFn: func() *unstructured.Unstructured {
instance := newNoxuValidationInstance(ns, "foo")
instance.Object["beta"] = 5
return instance
},
expectedError: "beta in body should be greater than or equal to 10",
},
{
name: "bad gamma",
instanceFn: func() *unstructured.Unstructured {
instance := newNoxuValidationInstance(ns, "foo")
instance.Object["gamma"] = "qux"
return instance
},
expectedError: "gamma in body should be one of [foo bar baz]",
},
{
name: "bad delta",
instanceFn: func() *unstructured.Unstructured {
instance := newNoxuValidationInstance(ns, "foo")
instance.Object["delta"] = "foobarbaz"
return instance
},
expectedError: "must validate at least one schema (anyOf)\ndelta in body should be at most 5 chars long",
},
{
name: "absent alpha and beta",
instanceFn: func() *unstructured.Unstructured {
instance := newNoxuValidationInstance(ns, "foo")
instance.Object = map[string]interface{}{
"apiVersion": "mygroup.example.com/v1beta1",
"kind": "WishIHadChosenNoxu",
"metadata": map[string]interface{}{
"namespace": "not-the-default",
"name": "foo",
},
"gamma": "bar",
"delta": "hello",
}
return instance
},
expectedError: ".alpha in body is required\n.beta in body is required",
},
}
for _, tc := range tests {
_, err := noxuResourceClient.Create(tc.instanceFn())
if err == nil {
t.Errorf("%v: expected %v", tc.name, tc.expectedError)
continue
}
// this only works when status errors contain the expect kind and version, so this effectively tests serializations too
if !strings.Contains(err.Error(), tc.expectedError) {
t.Errorf("%v: expected %v, got %v", tc.name, tc.expectedError, err)
continue
}
}
}
func TestCRValidationOnCRDUpdate(t *testing.T) {
stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer()
if err != nil {
t.Fatal(err)
}
defer close(stopCh)
// enable alpha feature CustomResourceValidation
err = utilfeature.DefaultFeatureGate.Set("CustomResourceValidation=true")
if err != nil {
t.Errorf("failed to enable feature gate for CustomResourceValidation: %v", err)
}
noxuDefinition := newNoxuValidationCRD(apiextensionsv1beta1.NamespaceScoped)
// set stricter schema
noxuDefinition.Spec.Validation.OpenAPIV3Schema.Required = []string{"alpha", "beta", "epsilon"}
noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool)
if err != nil {
t.Fatal(err)
}
ns := "not-the-default"
noxuResourceClient := NewNamespacedCustomResourceClient(ns, noxuVersionClient, noxuDefinition)
// CR is rejected
_, err = instantiateCustomResource(t, newNoxuValidationInstance(ns, "foo"), noxuResourceClient, noxuDefinition)
if err == nil {
t.Fatalf("unexpected non-error: CR should be rejected")
}
gottenCRD, err := apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get("noxus.mygroup.example.com", metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
// update the CRD to a less stricter schema
gottenCRD.Spec.Validation.OpenAPIV3Schema.Required = []string{"alpha", "beta"}
updatedCRD, err := apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Update(gottenCRD)
if err != nil {
t.Fatal(err)
}
// CR is now accepted
err = wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
_, err = instantiateCustomResource(t, newNoxuValidationInstance(ns, "foo"), noxuResourceClient, updatedCRD)
if err != nil {
return false, err
}
return true, nil
})
if err != nil {
t.Fatal(err)
}
}
func TestForbiddenFieldsInSchema(t *testing.T) {
stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer()
if err != nil {
t.Fatal(err)
}
defer close(stopCh)
// enable alpha feature CustomResourceValidation
err = utilfeature.DefaultFeatureGate.Set("CustomResourceValidation=true")
if err != nil {
t.Errorf("failed to enable feature gate for CustomResourceValidation: %v", err)
}
noxuDefinition := newNoxuValidationCRD(apiextensionsv1beta1.NamespaceScoped)
noxuDefinition.Spec.Validation.OpenAPIV3Schema.AdditionalProperties.Allows = false
_, err = testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool)
if err == nil {
t.Fatalf("unexpected non-error: additionalProperties cannot be set to false")
}
noxuDefinition.Spec.Validation.OpenAPIV3Schema.Properties["zeta"] = apiextensionsv1beta1.JSONSchemaProps{
Type: "array",
UniqueItems: true,
}
noxuDefinition.Spec.Validation.OpenAPIV3Schema.AdditionalProperties.Allows = true
_, err = testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool)
if err == nil {
t.Fatalf("unexpected non-error: uniqueItems cannot be set to true")
}
noxuDefinition.Spec.Validation.OpenAPIV3Schema.Properties["zeta"] = apiextensionsv1beta1.JSONSchemaProps{
Type: "array",
UniqueItems: false,
}
_, err = testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool)
if err != nil {
t.Fatal(err)
}
}
func float64Ptr(f float64) *float64 {
return &f
}
func int64Ptr(f int64) *int64 {
return &f
}

View File

@ -27,14 +27,14 @@ const (
// // alpha: v1.4 // // alpha: v1.4
// MyFeature() bool // MyFeature() bool
// owner: tallclair // owner: @tallclair
// alpha: v1.5 // alpha: v1.5
// //
// StreamingProxyRedirects controls whether the apiserver should intercept (and follow) // StreamingProxyRedirects controls whether the apiserver should intercept (and follow)
// redirects from the backend (Kubelet) for streaming requests (exec/attach/port-forward). // redirects from the backend (Kubelet) for streaming requests (exec/attach/port-forward).
StreamingProxyRedirects utilfeature.Feature = "StreamingProxyRedirects" StreamingProxyRedirects utilfeature.Feature = "StreamingProxyRedirects"
// owner: tallclair // owner: @tallclair
// alpha: v1.7 // alpha: v1.7
// //
// AdvancedAuditing enables a much more general API auditing pipeline, which includes support for // AdvancedAuditing enables a much more general API auditing pipeline, which includes support for