add prune and remove unused packages
This commit is contained in:
9
vendor/k8s.io/apiextensions-apiserver/pkg/apis/OWNERS
generated
vendored
9
vendor/k8s.io/apiextensions-apiserver/pkg/apis/OWNERS
generated
vendored
@@ -1,9 +0,0 @@
|
||||
# Disable inheritance as this is an api owners file
|
||||
options:
|
||||
no_parent_owners: true
|
||||
approvers:
|
||||
- api-approvers
|
||||
reviewers:
|
||||
- api-reviewers
|
||||
labels:
|
||||
- kind/api-change
|
147
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer/fuzzer.go
generated
vendored
147
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer/fuzzer.go
generated
vendored
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
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 fuzzer
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/google/gofuzz"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
)
|
||||
|
||||
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
|
||||
|
||||
// Funcs returns the fuzzer functions for the apiextensions apis.
|
||||
func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
|
||||
return []interface{}{
|
||||
func(obj *apiextensions.CustomResourceDefinitionSpec, c fuzz.Continue) {
|
||||
c.FuzzNoCustom(obj)
|
||||
|
||||
// match our defaulter
|
||||
if len(obj.Scope) == 0 {
|
||||
obj.Scope = apiextensions.NamespaceScoped
|
||||
}
|
||||
if len(obj.Names.Singular) == 0 {
|
||||
obj.Names.Singular = strings.ToLower(obj.Names.Kind)
|
||||
}
|
||||
if len(obj.Names.ListKind) == 0 && len(obj.Names.Kind) > 0 {
|
||||
obj.Names.ListKind = obj.Names.Kind + "List"
|
||||
}
|
||||
if len(obj.Versions) == 0 && len(obj.Version) != 0 {
|
||||
obj.Versions = []apiextensions.CustomResourceDefinitionVersion{
|
||||
{
|
||||
Name: obj.Version,
|
||||
Served: true,
|
||||
Storage: true,
|
||||
},
|
||||
}
|
||||
} else if len(obj.Versions) != 0 {
|
||||
obj.Version = obj.Versions[0].Name
|
||||
}
|
||||
if len(obj.AdditionalPrinterColumns) == 0 {
|
||||
obj.AdditionalPrinterColumns = []apiextensions.CustomResourceColumnDefinition{
|
||||
{Name: "Age", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"], JSONPath: ".metadata.creationTimestamp"},
|
||||
}
|
||||
}
|
||||
if obj.Conversion == nil {
|
||||
obj.Conversion = &apiextensions.CustomResourceConversion{
|
||||
Strategy: apiextensions.NoneConverter,
|
||||
}
|
||||
}
|
||||
},
|
||||
func(obj *apiextensions.CustomResourceDefinition, c fuzz.Continue) {
|
||||
c.FuzzNoCustom(obj)
|
||||
|
||||
if len(obj.Status.StoredVersions) == 0 {
|
||||
for _, v := range obj.Spec.Versions {
|
||||
if v.Storage && !apiextensions.IsStoredVersion(obj, v.Name) {
|
||||
obj.Status.StoredVersions = append(obj.Status.StoredVersions, v.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
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", "Ref":
|
||||
continue
|
||||
default:
|
||||
isValue := true
|
||||
switch field.Type.Kind() {
|
||||
case reflect.Interface, reflect.Map, reflect.Slice, reflect.Ptr:
|
||||
isValue = false
|
||||
}
|
||||
if isValue || c.Intn(10) == 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
|
||||
}
|
||||
if c.RandBool() {
|
||||
validRef := "validRef"
|
||||
obj.Ref = &validRef
|
||||
}
|
||||
},
|
||||
func(obj *apiextensions.JSONSchemaPropsOrBool, c fuzz.Continue) {
|
||||
if c.RandBool() {
|
||||
obj.Allows = true
|
||||
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)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
421
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers_test.go
generated
vendored
421
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers_test.go
generated
vendored
@@ -1,421 +0,0 @@
|
||||
/*
|
||||
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
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestCRDHasFinalizer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
crd *CustomResourceDefinition
|
||||
finalizerToCheck string
|
||||
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "missing",
|
||||
crd: &CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it"}},
|
||||
},
|
||||
finalizerToCheck: "it",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "present",
|
||||
crd: &CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it", "it"}},
|
||||
},
|
||||
finalizerToCheck: "it",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
actual := CRDHasFinalizer(tc.crd, tc.finalizerToCheck)
|
||||
if tc.expected != actual {
|
||||
t.Errorf("%v expected %v, got %v", tc.name, tc.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCRDRemoveFinalizer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
crd *CustomResourceDefinition
|
||||
finalizerToCheck string
|
||||
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "missing",
|
||||
crd: &CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it"}},
|
||||
},
|
||||
finalizerToCheck: "it",
|
||||
expected: []string{"not-it"},
|
||||
},
|
||||
{
|
||||
name: "present",
|
||||
crd: &CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it", "it"}},
|
||||
},
|
||||
finalizerToCheck: "it",
|
||||
expected: []string{"not-it"},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
CRDRemoveFinalizer(tc.crd, tc.finalizerToCheck)
|
||||
if !reflect.DeepEqual(tc.expected, tc.crd.Finalizers) {
|
||||
t.Errorf("%v expected %v, got %v", tc.name, tc.expected, tc.crd.Finalizers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCRDCondition(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
crdCondition []CustomResourceDefinitionCondition
|
||||
newCondition CustomResourceDefinitionCondition
|
||||
expectedcrdCondition []CustomResourceDefinitionCondition
|
||||
}{
|
||||
{
|
||||
name: "test setCRDcondition when one condition",
|
||||
crdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionTrue,
|
||||
Reason: "Accepted",
|
||||
Message: "the initial names have been accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
newCondition: CustomResourceDefinitionCondition{
|
||||
Type: Established,
|
||||
Status: ConditionFalse,
|
||||
Reason: "NotAccepted",
|
||||
Message: "Not accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
expectedcrdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionFalse,
|
||||
Reason: "NotAccepted",
|
||||
Message: "Not accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test setCRDcondition when two condition",
|
||||
crdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionTrue,
|
||||
Reason: "Accepted",
|
||||
Message: "the initial names have been accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Type: NamesAccepted,
|
||||
Status: ConditionTrue,
|
||||
Reason: "NoConflicts",
|
||||
Message: "no conflicts found",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
newCondition: CustomResourceDefinitionCondition{
|
||||
Type: NamesAccepted,
|
||||
Status: ConditionFalse,
|
||||
Reason: "Conflicts",
|
||||
Message: "conflicts found",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
expectedcrdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionTrue,
|
||||
Reason: "Accepted",
|
||||
Message: "the initial names have been accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Type: NamesAccepted,
|
||||
Status: ConditionFalse,
|
||||
Reason: "Conflicts",
|
||||
Message: "conflicts found",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test setCRDcondition when condition needs to be appended",
|
||||
crdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionTrue,
|
||||
Reason: "Accepted",
|
||||
Message: "the initial names have been accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
newCondition: CustomResourceDefinitionCondition{
|
||||
Type: Terminating,
|
||||
Status: ConditionFalse,
|
||||
Reason: "NeverEstablished",
|
||||
Message: "resource was never established",
|
||||
LastTransitionTime: metav1.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
expectedcrdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionTrue,
|
||||
Reason: "Accepted",
|
||||
Message: "the initial names have been accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Type: Terminating,
|
||||
Status: ConditionFalse,
|
||||
Reason: "NeverEstablished",
|
||||
Message: "resource was never established",
|
||||
LastTransitionTime: metav1.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
crd := generateCRDwithCondition(tc.crdCondition)
|
||||
SetCRDCondition(crd, tc.newCondition)
|
||||
if len(tc.expectedcrdCondition) != len(crd.Status.Conditions) {
|
||||
t.Errorf("%v expected %v, got %v", tc.name, tc.expectedcrdCondition, crd.Status.Conditions)
|
||||
}
|
||||
for i := range tc.expectedcrdCondition {
|
||||
if !IsCRDConditionEquivalent(&tc.expectedcrdCondition[i], &crd.Status.Conditions[i]) {
|
||||
t.Errorf("%v expected %v, got %v", tc.name, tc.expectedcrdCondition, crd.Status.Conditions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveCRDCondition(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
crdCondition []CustomResourceDefinitionCondition
|
||||
conditionType CustomResourceDefinitionConditionType
|
||||
expectedcrdCondition []CustomResourceDefinitionCondition
|
||||
}{
|
||||
{
|
||||
name: "test remove CRDCondition when the conditionType meets",
|
||||
crdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionTrue,
|
||||
Reason: "Accepted",
|
||||
Message: "the initial names have been accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Type: NamesAccepted,
|
||||
Status: ConditionTrue,
|
||||
Reason: "NoConflicts",
|
||||
Message: "no conflicts found",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
conditionType: NamesAccepted,
|
||||
expectedcrdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionTrue,
|
||||
Reason: "Accepted",
|
||||
Message: "the initial names have been accepted",
|
||||
LastTransitionTime: metav1.Date(2011, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test remove CRDCondition when the conditionType not meets",
|
||||
crdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionTrue,
|
||||
Reason: "Accepted",
|
||||
Message: "the initial names have been accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Type: NamesAccepted,
|
||||
Status: ConditionTrue,
|
||||
Reason: "NoConflicts",
|
||||
Message: "no conflicts found",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
conditionType: Terminating,
|
||||
expectedcrdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionTrue,
|
||||
Reason: "Accepted",
|
||||
Message: "the initial names have been accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Type: NamesAccepted,
|
||||
Status: ConditionTrue,
|
||||
Reason: "NoConflicts",
|
||||
Message: "no conflicts found",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
crd := generateCRDwithCondition(tc.crdCondition)
|
||||
RemoveCRDCondition(crd, tc.conditionType)
|
||||
if len(tc.expectedcrdCondition) != len(crd.Status.Conditions) {
|
||||
t.Errorf("%v expected %v, got %v", tc.name, tc.expectedcrdCondition, crd.Status.Conditions)
|
||||
}
|
||||
for i := range tc.expectedcrdCondition {
|
||||
if !IsCRDConditionEquivalent(&tc.expectedcrdCondition[i], &crd.Status.Conditions[i]) {
|
||||
t.Errorf("%v expected %v, got %v", tc.name, tc.expectedcrdCondition, crd.Status.Conditions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCRDConditionPresentAndEqual(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
crdCondition []CustomResourceDefinitionCondition
|
||||
conditionType CustomResourceDefinitionConditionType
|
||||
status ConditionStatus
|
||||
expectresult bool
|
||||
}{
|
||||
{
|
||||
name: "test CRDCondition is not Present",
|
||||
crdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionTrue,
|
||||
Reason: "Accepted",
|
||||
Message: "the initial names have been accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Type: NamesAccepted,
|
||||
Status: ConditionTrue,
|
||||
Reason: "NoConflicts",
|
||||
Message: "no conflicts found",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
conditionType: Terminating,
|
||||
status: ConditionTrue,
|
||||
expectresult: false,
|
||||
},
|
||||
{
|
||||
name: "test CRDCondition is Present but not Equal",
|
||||
crdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionTrue,
|
||||
Reason: "Accepted",
|
||||
Message: "the initial names have been accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Type: NamesAccepted,
|
||||
Status: ConditionTrue,
|
||||
Reason: "NoConflicts",
|
||||
Message: "no conflicts found",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
conditionType: Established,
|
||||
status: ConditionFalse,
|
||||
expectresult: false,
|
||||
},
|
||||
{
|
||||
name: "test CRDCondition is Present and Equal",
|
||||
crdCondition: []CustomResourceDefinitionCondition{
|
||||
{
|
||||
Type: Established,
|
||||
Status: ConditionTrue,
|
||||
Reason: "Accepted",
|
||||
Message: "the initial names have been accepted",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Type: NamesAccepted,
|
||||
Status: ConditionTrue,
|
||||
Reason: "NoConflicts",
|
||||
Message: "no conflicts found",
|
||||
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
conditionType: NamesAccepted,
|
||||
status: ConditionTrue,
|
||||
expectresult: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
crd := generateCRDwithCondition(tc.crdCondition)
|
||||
res := IsCRDConditionPresentAndEqual(crd, tc.conditionType, tc.status)
|
||||
if res != tc.expectresult {
|
||||
t.Errorf("%v expected %t, got %t", tc.name, tc.expectresult, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generateCRDwithCondition(conditions []CustomResourceDefinitionCondition) *CustomResourceDefinition {
|
||||
testCRDObjectMeta := metav1.ObjectMeta{
|
||||
Name: "plural.group.com",
|
||||
ResourceVersion: "12",
|
||||
}
|
||||
testCRDSpec := CustomResourceDefinitionSpec{
|
||||
Group: "group.com",
|
||||
Version: "version",
|
||||
Scope: ResourceScope("Cluster"),
|
||||
Names: CustomResourceDefinitionNames{
|
||||
Plural: "plural",
|
||||
Singular: "singular",
|
||||
Kind: "kind",
|
||||
ListKind: "listkind",
|
||||
},
|
||||
}
|
||||
testCRDAcceptedNames := CustomResourceDefinitionNames{
|
||||
Plural: "plural",
|
||||
Singular: "singular",
|
||||
Kind: "kind",
|
||||
ListKind: "listkind",
|
||||
}
|
||||
return &CustomResourceDefinition{
|
||||
ObjectMeta: testCRDObjectMeta,
|
||||
Spec: testCRDSpec,
|
||||
Status: CustomResourceDefinitionStatus{
|
||||
AcceptedNames: testCRDAcceptedNames,
|
||||
Conditions: conditions,
|
||||
},
|
||||
}
|
||||
}
|
31
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install/install.go
generated
vendored
31
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install/install.go
generated
vendored
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
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 install
|
||||
|
||||
import (
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// Install registers the API group and adds types to a scheme
|
||||
func Install(scheme *runtime.Scheme) {
|
||||
utilruntime.Must(apiextensions.AddToScheme(scheme))
|
||||
utilruntime.Must(v1beta1.AddToScheme(scheme))
|
||||
utilruntime.Must(scheme.SetVersionPriority(v1beta1.SchemeGroupVersion))
|
||||
}
|
28
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install/roundtrip_test.go
generated
vendored
28
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install/roundtrip_test.go
generated
vendored
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
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 install
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
|
||||
"k8s.io/apimachinery/pkg/api/apitesting/roundtrip"
|
||||
)
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
roundtrip.RoundTripTestForAPIGroup(t, Install, apiextensionsfuzzer.Funcs)
|
||||
}
|
113
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/conversion_test.go
generated
vendored
113
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/conversion_test.go
generated
vendored
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
522
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto
generated
vendored
522
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto
generated
vendored
@@ -1,522 +0,0 @@
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
|
||||
// This file was autogenerated by go-to-protobuf. Do not edit it manually!
|
||||
|
||||
syntax = 'proto2';
|
||||
|
||||
package k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1;
|
||||
|
||||
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
|
||||
import "k8s.io/apimachinery/pkg/runtime/generated.proto";
|
||||
import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
|
||||
|
||||
// Package-wide variables from generator "generated".
|
||||
option go_package = "v1beta1";
|
||||
|
||||
// ConversionRequest describes the conversion request parameters.
|
||||
message ConversionRequest {
|
||||
// `uid` is an identifier for the individual request/response. It allows us to distinguish instances of requests which are
|
||||
// otherwise identical (parallel requests, requests when earlier requests did not modify etc)
|
||||
// The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request.
|
||||
// It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.
|
||||
optional string uid = 1;
|
||||
|
||||
// `desiredAPIVersion` is the version to convert given objects to. e.g. "myapi.example.com/v1"
|
||||
optional string desiredAPIVersion = 2;
|
||||
|
||||
// `objects` is the list of CR objects to be converted.
|
||||
repeated k8s.io.apimachinery.pkg.runtime.RawExtension objects = 3;
|
||||
}
|
||||
|
||||
// ConversionResponse describes a conversion response.
|
||||
message ConversionResponse {
|
||||
// `uid` is an identifier for the individual request/response.
|
||||
// This should be copied over from the corresponding AdmissionRequest.
|
||||
optional string uid = 1;
|
||||
|
||||
// `convertedObjects` is the list of converted version of `request.objects` if the `result` is successful otherwise empty.
|
||||
// The webhook is expected to set apiVersion of these objects to the ConversionRequest.desiredAPIVersion. The list
|
||||
// must also has the same size as input list with the same objects in the same order(i.e. equal UIDs and object meta)
|
||||
repeated k8s.io.apimachinery.pkg.runtime.RawExtension convertedObjects = 2;
|
||||
|
||||
// `result` contains the result of conversion with extra details if the conversion failed. `result.status` determines if
|
||||
// the conversion failed or succeeded. The `result.status` field is required and represent the success or failure of the
|
||||
// conversion. A successful conversion must set `result.status` to `Success`. A failed conversion must set
|
||||
// `result.status` to `Failure` and provide more details in `result.message` and return http status 200. The `result.message`
|
||||
// will be used to construct an error message for the end user.
|
||||
optional k8s.io.apimachinery.pkg.apis.meta.v1.Status result = 3;
|
||||
}
|
||||
|
||||
// ConversionReview describes a conversion request/response.
|
||||
message ConversionReview {
|
||||
// `request` describes the attributes for the conversion request.
|
||||
// +optional
|
||||
optional ConversionRequest request = 1;
|
||||
|
||||
// `response` describes the attributes for the conversion response.
|
||||
// +optional
|
||||
optional ConversionResponse response = 2;
|
||||
}
|
||||
|
||||
// CustomResourceColumnDefinition specifies a column for server side printing.
|
||||
message CustomResourceColumnDefinition {
|
||||
// name is a human readable name for the column.
|
||||
optional string name = 1;
|
||||
|
||||
// type is an OpenAPI type definition for this column.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more.
|
||||
optional string type = 2;
|
||||
|
||||
// format is an optional OpenAPI type definition for this column. The 'name' format is applied
|
||||
// to the primary identifier column to assist in clients identifying column is the resource name.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more.
|
||||
// +optional
|
||||
optional string format = 3;
|
||||
|
||||
// description is a human readable description of this column.
|
||||
// +optional
|
||||
optional string description = 4;
|
||||
|
||||
// priority is an integer defining the relative importance of this column compared to others. Lower
|
||||
// numbers are considered higher priority. Columns that may be omitted in limited space scenarios
|
||||
// should be given a higher priority.
|
||||
// +optional
|
||||
optional int32 priority = 5;
|
||||
|
||||
// JSONPath is a simple JSON path, i.e. with array notation.
|
||||
optional string JSONPath = 6;
|
||||
}
|
||||
|
||||
// CustomResourceConversion describes how to convert different versions of a CR.
|
||||
message CustomResourceConversion {
|
||||
// `strategy` specifies the conversion strategy. Allowed values are:
|
||||
// - `None`: The converter only change the apiVersion and would not touch any other field in the CR.
|
||||
// - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information is needed for this option.
|
||||
optional string strategy = 1;
|
||||
|
||||
// `webhookClientConfig` is the instructions for how to call the webhook if strategy is `Webhook`. This field is
|
||||
// alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature.
|
||||
// +optional
|
||||
optional WebhookClientConfig webhookClientConfig = 2;
|
||||
}
|
||||
|
||||
// CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format
|
||||
// <.spec.name>.<.spec.group>.
|
||||
message CustomResourceDefinition {
|
||||
optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
|
||||
|
||||
// Spec describes how the user wants the resources to appear
|
||||
optional CustomResourceDefinitionSpec spec = 2;
|
||||
|
||||
// Status indicates the actual state of the CustomResourceDefinition
|
||||
// +optional
|
||||
optional CustomResourceDefinitionStatus status = 3;
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionCondition contains details for the current condition of this pod.
|
||||
message CustomResourceDefinitionCondition {
|
||||
// Type is the type of the condition.
|
||||
optional string type = 1;
|
||||
|
||||
// Status is the status of the condition.
|
||||
// Can be True, False, Unknown.
|
||||
optional string status = 2;
|
||||
|
||||
// Last time the condition transitioned from one status to another.
|
||||
// +optional
|
||||
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 3;
|
||||
|
||||
// Unique, one-word, CamelCase reason for the condition's last transition.
|
||||
// +optional
|
||||
optional string reason = 4;
|
||||
|
||||
// Human-readable message indicating details about last transition.
|
||||
// +optional
|
||||
optional string message = 5;
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionList is a list of CustomResourceDefinition objects.
|
||||
message CustomResourceDefinitionList {
|
||||
optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
|
||||
|
||||
// Items individual CustomResourceDefinitions
|
||||
repeated CustomResourceDefinition items = 2;
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition
|
||||
message CustomResourceDefinitionNames {
|
||||
// Plural is the plural name of the resource to serve. It must match the name of the CustomResourceDefinition-registration
|
||||
// too: plural.group and it must be all lowercase.
|
||||
optional string plural = 1;
|
||||
|
||||
// Singular is the singular name of the resource. It must be all lowercase Defaults to lowercased <kind>
|
||||
// +optional
|
||||
optional string singular = 2;
|
||||
|
||||
// ShortNames are short names for the resource. It must be all lowercase.
|
||||
// +optional
|
||||
repeated string shortNames = 3;
|
||||
|
||||
// Kind is the serialized kind of the resource. It is normally CamelCase and singular.
|
||||
optional string kind = 4;
|
||||
|
||||
// ListKind is the serialized kind of the list for this resource. Defaults to <kind>List.
|
||||
// +optional
|
||||
optional string listKind = 5;
|
||||
|
||||
// Categories is a list of grouped resources custom resources belong to (e.g. 'all')
|
||||
// +optional
|
||||
repeated string categories = 6;
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionSpec describes how a user wants their resource to appear
|
||||
message CustomResourceDefinitionSpec {
|
||||
// Group is the group this resource belongs in
|
||||
optional string group = 1;
|
||||
|
||||
// Version is the version this resource belongs in
|
||||
// Should be always first item in Versions field if provided.
|
||||
// Optional, but at least one of Version or Versions must be set.
|
||||
// Deprecated: Please use `Versions`.
|
||||
// +optional
|
||||
optional string version = 2;
|
||||
|
||||
// Names are the names used to describe this custom resource
|
||||
optional CustomResourceDefinitionNames names = 3;
|
||||
|
||||
// Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced
|
||||
optional string scope = 4;
|
||||
|
||||
// Validation describes the validation methods for CustomResources
|
||||
// Optional, the global validation schema for all versions.
|
||||
// Top-level and per-version schemas are mutually exclusive.
|
||||
// +optional
|
||||
optional CustomResourceValidation validation = 5;
|
||||
|
||||
// Subresources describes the subresources for CustomResource
|
||||
// Optional, the global subresources for all versions.
|
||||
// Top-level and per-version subresources are mutually exclusive.
|
||||
// +optional
|
||||
optional CustomResourceSubresources subresources = 6;
|
||||
|
||||
// Versions is the list of all supported versions for this resource.
|
||||
// If Version field is provided, this field is optional.
|
||||
// Validation: All versions must use the same validation schema for now. i.e., top
|
||||
// level Validation field is applied to all of these versions.
|
||||
// Order: The version name will be used to compute the order.
|
||||
// If the version string is "kube-like", it will sort above non "kube-like" version strings, which are ordered
|
||||
// lexicographically. "Kube-like" versions start with a "v", then are followed by a number (the major version),
|
||||
// then optionally the string "alpha" or "beta" and another number (the minor version). These are sorted first
|
||||
// by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing
|
||||
// major version, then minor version. An example sorted list of versions:
|
||||
// v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10.
|
||||
// +optional
|
||||
repeated CustomResourceDefinitionVersion versions = 7;
|
||||
|
||||
// AdditionalPrinterColumns are additional columns shown e.g. in kubectl next to the name. Defaults to a created-at column.
|
||||
// Optional, the global columns for all versions.
|
||||
// Top-level and per-version columns are mutually exclusive.
|
||||
// +optional
|
||||
repeated CustomResourceColumnDefinition additionalPrinterColumns = 8;
|
||||
|
||||
// `conversion` defines conversion settings for the CRD.
|
||||
// +optional
|
||||
optional CustomResourceConversion conversion = 9;
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition
|
||||
message CustomResourceDefinitionStatus {
|
||||
// Conditions indicate state for particular aspects of a CustomResourceDefinition
|
||||
repeated CustomResourceDefinitionCondition conditions = 1;
|
||||
|
||||
// AcceptedNames are the names that are actually being used to serve discovery
|
||||
// They may be different than the names in spec.
|
||||
optional CustomResourceDefinitionNames acceptedNames = 2;
|
||||
|
||||
// StoredVersions are all versions of CustomResources that were ever persisted. Tracking these
|
||||
// versions allows a migration path for stored versions in etcd. The field is mutable
|
||||
// so the migration controller can first finish a migration to another version (i.e.
|
||||
// that no old objects are left in the storage), and then remove the rest of the
|
||||
// versions from this list.
|
||||
// None of the versions in this list can be removed from the spec.Versions field.
|
||||
repeated string storedVersions = 3;
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionVersion describes a version for CRD.
|
||||
message CustomResourceDefinitionVersion {
|
||||
// Name is the version name, e.g. “v1”, “v2beta1”, etc.
|
||||
optional string name = 1;
|
||||
|
||||
// Served is a flag enabling/disabling this version from being served via REST APIs
|
||||
optional bool served = 2;
|
||||
|
||||
// Storage flags the version as storage version. There must be exactly one
|
||||
// flagged as storage version.
|
||||
optional bool storage = 3;
|
||||
|
||||
// Schema describes the schema for CustomResource used in validation, pruning, and defaulting.
|
||||
// Top-level and per-version schemas are mutually exclusive.
|
||||
// Per-version schemas must not all be set to identical values (top-level validation schema should be used instead)
|
||||
// This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature.
|
||||
// +optional
|
||||
optional CustomResourceValidation schema = 4;
|
||||
|
||||
// Subresources describes the subresources for CustomResource
|
||||
// Top-level and per-version subresources are mutually exclusive.
|
||||
// Per-version subresources must not all be set to identical values (top-level subresources should be used instead)
|
||||
// This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature.
|
||||
// +optional
|
||||
optional CustomResourceSubresources subresources = 5;
|
||||
|
||||
// AdditionalPrinterColumns are additional columns shown e.g. in kubectl next to the name. Defaults to a created-at column.
|
||||
// Top-level and per-version columns are mutually exclusive.
|
||||
// Per-version columns must not all be set to identical values (top-level columns should be used instead)
|
||||
// This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature.
|
||||
// NOTE: CRDs created prior to 1.13 populated the top-level additionalPrinterColumns field by default. To apply an
|
||||
// update that changes to per-version additionalPrinterColumns, the top-level additionalPrinterColumns field must
|
||||
// be explicitly set to null
|
||||
// +optional
|
||||
repeated CustomResourceColumnDefinition additionalPrinterColumns = 6;
|
||||
}
|
||||
|
||||
// CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources.
|
||||
message CustomResourceSubresourceScale {
|
||||
// SpecReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Spec.Replicas.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must be a JSON Path under .spec.
|
||||
// If there is no value under the given path in the CustomResource, the /scale subresource will return an error on GET.
|
||||
optional string specReplicasPath = 1;
|
||||
|
||||
// StatusReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Replicas.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must be a JSON Path under .status.
|
||||
// If there is no value under the given path in the CustomResource, the status replica value in the /scale subresource
|
||||
// will default to 0.
|
||||
optional string statusReplicasPath = 2;
|
||||
|
||||
// LabelSelectorPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Selector.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must be a JSON Path under .status.
|
||||
// Must be set to work with HPA.
|
||||
// If there is no value under the given path in the CustomResource, the status label selector value in the /scale
|
||||
// subresource will default to the empty string.
|
||||
// +optional
|
||||
optional string labelSelectorPath = 3;
|
||||
}
|
||||
|
||||
// CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources.
|
||||
// Status is represented by the `.status` JSON path inside of a CustomResource. When set,
|
||||
// * exposes a /status subresource for the custom resource
|
||||
// * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza
|
||||
// * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza
|
||||
message CustomResourceSubresourceStatus {
|
||||
}
|
||||
|
||||
// CustomResourceSubresources defines the status and scale subresources for CustomResources.
|
||||
message CustomResourceSubresources {
|
||||
// Status denotes the status subresource for CustomResources
|
||||
// +optional
|
||||
optional CustomResourceSubresourceStatus status = 1;
|
||||
|
||||
// Scale denotes the scale subresource for CustomResources
|
||||
// +optional
|
||||
optional CustomResourceSubresourceScale scale = 2;
|
||||
}
|
||||
|
||||
// CustomResourceValidation is a list of validation methods for CustomResources.
|
||||
message CustomResourceValidation {
|
||||
// OpenAPIV3Schema is the OpenAPI v3 schema to be validated against.
|
||||
// +optional
|
||||
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;
|
||||
}
|
||||
|
||||
// ServiceReference holds a reference to Service.legacy.k8s.io
|
||||
message ServiceReference {
|
||||
// `namespace` is the namespace of the service.
|
||||
// Required
|
||||
optional string namespace = 1;
|
||||
|
||||
// `name` is the name of the service.
|
||||
// Required
|
||||
optional string name = 2;
|
||||
|
||||
// `path` is an optional URL path which will be sent in any request to
|
||||
// this service.
|
||||
// +optional
|
||||
optional string path = 3;
|
||||
}
|
||||
|
||||
// WebhookClientConfig contains the information to make a TLS
|
||||
// connection with the webhook. It has the same field as admissionregistration.v1beta1.WebhookClientConfig.
|
||||
message WebhookClientConfig {
|
||||
// `url` gives the location of the webhook, in standard URL form
|
||||
// (`scheme://host:port/path`). Exactly one of `url` or `service`
|
||||
// must be specified.
|
||||
//
|
||||
// The `host` should not refer to a service running in the cluster; use
|
||||
// the `service` field instead. The host might be resolved via external
|
||||
// DNS in some apiservers (e.g., `kube-apiserver` cannot resolve
|
||||
// in-cluster DNS as that would be a layering violation). `host` may
|
||||
// also be an IP address.
|
||||
//
|
||||
// Please note that using `localhost` or `127.0.0.1` as a `host` is
|
||||
// risky unless you take great care to run this webhook on all hosts
|
||||
// which run an apiserver which might need to make calls to this
|
||||
// webhook. Such installs are likely to be non-portable, i.e., not easy
|
||||
// to turn up in a new cluster.
|
||||
//
|
||||
// The scheme must be "https"; the URL must begin with "https://".
|
||||
//
|
||||
// A path is optional, and if present may be any string permissible in
|
||||
// a URL. You may use the path to pass an arbitrary string to the
|
||||
// webhook, for example, a cluster identifier.
|
||||
//
|
||||
// Attempting to use a user or basic auth e.g. "user:password@" is not
|
||||
// allowed. Fragments ("#...") and query parameters ("?...") are not
|
||||
// allowed, either.
|
||||
//
|
||||
// +optional
|
||||
optional string url = 3;
|
||||
|
||||
// `service` is a reference to the service for this webhook. Either
|
||||
// `service` or `url` must be specified.
|
||||
//
|
||||
// If the webhook is running within the cluster, then you should use `service`.
|
||||
//
|
||||
// Port 443 will be used if it is open, otherwise it is an error.
|
||||
//
|
||||
// +optional
|
||||
optional ServiceReference service = 1;
|
||||
|
||||
// `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate.
|
||||
// If unspecified, system trust roots on the apiserver are used.
|
||||
// +optional
|
||||
optional bytes caBundle = 2;
|
||||
}
|
||||
|
150
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/marshal_test.go
generated
vendored
150
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/marshal_test.go
generated
vendored
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
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{Allows: true, Schema: &JSONSchemaProps{}}}},
|
||||
{`{"val1": {"type":"string"}}`, JSONSchemaPropsOrBoolHolder{JSPoB: JSONSchemaPropsOrBool{Allows: true, Schema: &JSONSchemaProps{Type: "string"}}}},
|
||||
{`{"val1": false}`, JSONSchemaPropsOrBoolHolder{JSPoB: JSONSchemaPropsOrBool{}}},
|
||||
{`{"val1": true}`, JSONSchemaPropsOrBoolHolder{JSPoB: JSONSchemaPropsOrBool{Allows: true}}},
|
||||
|
||||
{`{"val2": {}}`, JSONSchemaPropsOrBoolHolder{JSPoBOmitEmpty: &JSONSchemaPropsOrBool{Allows: true, Schema: &JSONSchemaProps{}}}},
|
||||
{`{"val2": {"type":"string"}}`, JSONSchemaPropsOrBoolHolder{JSPoBOmitEmpty: &JSONSchemaPropsOrBool{Allows: true, 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))
|
||||
}
|
||||
}
|
||||
}
|
734
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go
generated
vendored
734
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go
generated
vendored
@@ -1,734 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
genericvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
validationutil "k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
|
||||
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
||||
)
|
||||
|
||||
var (
|
||||
printerColumnDatatypes = sets.NewString("integer", "number", "string", "boolean", "date")
|
||||
customResourceColumnDefinitionFormats = sets.NewString("int32", "int64", "float", "double", "byte", "date", "date-time", "password")
|
||||
)
|
||||
|
||||
// ValidateCustomResourceDefinition statically validates
|
||||
func ValidateCustomResourceDefinition(obj *apiextensions.CustomResourceDefinition) field.ErrorList {
|
||||
nameValidationFn := func(name string, prefix bool) []string {
|
||||
ret := genericvalidation.NameIsDNSSubdomain(name, prefix)
|
||||
requiredName := obj.Spec.Names.Plural + "." + obj.Spec.Group
|
||||
if name != requiredName {
|
||||
ret = append(ret, fmt.Sprintf(`must be spec.names.plural+"."+spec.group`))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
allErrs := genericvalidation.ValidateObjectMeta(&obj.ObjectMeta, false, nameValidationFn, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionSpec(&obj.Spec, field.NewPath("spec"))...)
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionStatus(&obj.Status, field.NewPath("status"))...)
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionStoredVersions(obj.Status.StoredVersions, obj.Spec.Versions, field.NewPath("status").Child("storedVersions"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateCustomResourceDefinitionUpdate statically validates
|
||||
func ValidateCustomResourceDefinitionUpdate(obj, oldObj *apiextensions.CustomResourceDefinition) field.ErrorList {
|
||||
allErrs := genericvalidation.ValidateObjectMetaUpdate(&obj.ObjectMeta, &oldObj.ObjectMeta, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionSpecUpdate(&obj.Spec, &oldObj.Spec, apiextensions.IsCRDConditionTrue(oldObj, apiextensions.Established), field.NewPath("spec"))...)
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionStatus(&obj.Status, field.NewPath("status"))...)
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionStoredVersions(obj.Status.StoredVersions, obj.Spec.Versions, field.NewPath("status").Child("storedVersions"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateCustomResourceDefinitionStoredVersions statically validates
|
||||
func ValidateCustomResourceDefinitionStoredVersions(storedVersions []string, versions []apiextensions.CustomResourceDefinitionVersion, fldPath *field.Path) field.ErrorList {
|
||||
if len(storedVersions) == 0 {
|
||||
return field.ErrorList{field.Invalid(fldPath, storedVersions, "must have at least one stored version")}
|
||||
}
|
||||
allErrs := field.ErrorList{}
|
||||
storedVersionsMap := map[string]int{}
|
||||
for i, v := range storedVersions {
|
||||
storedVersionsMap[v] = i
|
||||
}
|
||||
for _, v := range versions {
|
||||
_, ok := storedVersionsMap[v.Name]
|
||||
if v.Storage && !ok {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, v, "must have the storage version "+v.Name))
|
||||
}
|
||||
if ok {
|
||||
delete(storedVersionsMap, v.Name)
|
||||
}
|
||||
}
|
||||
|
||||
for v, i := range storedVersionsMap {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), v, "must appear in spec.versions"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateUpdateCustomResourceDefinitionStatus statically validates
|
||||
func ValidateUpdateCustomResourceDefinitionStatus(obj, oldObj *apiextensions.CustomResourceDefinition) field.ErrorList {
|
||||
allErrs := genericvalidation.ValidateObjectMetaUpdate(&obj.ObjectMeta, &oldObj.ObjectMeta, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionStatus(&obj.Status, field.NewPath("status"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateCustomResourceDefinitionVersion statically validates.
|
||||
func ValidateCustomResourceDefinitionVersion(version *apiextensions.CustomResourceDefinitionVersion, fldPath *field.Path, statusEnabled bool) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionValidation(version.Schema, statusEnabled, fldPath.Child("schema"))...)
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionSubresources(version.Subresources, fldPath.Child("subresources"))...)
|
||||
for i := range version.AdditionalPrinterColumns {
|
||||
allErrs = append(allErrs, ValidateCustomResourceColumnDefinition(&version.AdditionalPrinterColumns[i], fldPath.Child("additionalPrinterColumns").Index(i))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateCustomResourceDefinitionSpec statically validates
|
||||
func ValidateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefinitionSpec, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if len(spec.Group) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("group"), ""))
|
||||
} else if errs := validationutil.IsDNS1123Subdomain(spec.Group); len(errs) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("group"), spec.Group, strings.Join(errs, ",")))
|
||||
} else if len(strings.Split(spec.Group, ".")) < 2 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("group"), spec.Group, "should be a domain with at least one dot"))
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, validateEnumStrings(fldPath.Child("scope"), string(spec.Scope), []string{string(apiextensions.ClusterScoped), string(apiextensions.NamespaceScoped)}, true)...)
|
||||
|
||||
storageFlagCount := 0
|
||||
versionsMap := map[string]bool{}
|
||||
uniqueNames := true
|
||||
for i, version := range spec.Versions {
|
||||
if version.Storage {
|
||||
storageFlagCount++
|
||||
}
|
||||
if versionsMap[version.Name] {
|
||||
uniqueNames = false
|
||||
} else {
|
||||
versionsMap[version.Name] = true
|
||||
}
|
||||
if errs := validationutil.IsDNS1035Label(version.Name); len(errs) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("versions").Index(i).Child("name"), spec.Versions[i].Name, strings.Join(errs, ",")))
|
||||
}
|
||||
subresources := getSubresourcesForVersion(spec, version.Name)
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionVersion(&version, fldPath.Child("versions").Index(i), hasStatusEnabled(subresources))...)
|
||||
}
|
||||
|
||||
// The top-level and per-version fields are mutual exclusive
|
||||
if spec.Validation != nil && hasPerVersionSchema(spec.Versions) {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("validation"), "top-level and per-version schemas are mutually exclusive"))
|
||||
}
|
||||
if spec.Subresources != nil && hasPerVersionSubresources(spec.Versions) {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("subresources"), "top-level and per-version subresources are mutually exclusive"))
|
||||
}
|
||||
if len(spec.AdditionalPrinterColumns) > 0 && hasPerVersionColumns(spec.Versions) {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalPrinterColumns"), "top-level and per-version additionalPrinterColumns are mutually exclusive"))
|
||||
}
|
||||
|
||||
// Per-version fields may not all be set to identical values (top-level field should be used instead)
|
||||
if hasIdenticalPerVersionSchema(spec.Versions) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("versions"), spec.Versions, "per-version schemas may not all be set to identical values (top-level validation should be used instead)"))
|
||||
}
|
||||
if hasIdenticalPerVersionSubresources(spec.Versions) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("versions"), spec.Versions, "per-version subresources may not all be set to identical values (top-level subresources should be used instead)"))
|
||||
}
|
||||
if hasIdenticalPerVersionColumns(spec.Versions) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("versions"), spec.Versions, "per-version additionalPrinterColumns may not all be set to identical values (top-level additionalPrinterColumns should be used instead)"))
|
||||
}
|
||||
|
||||
if !uniqueNames {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("versions"), spec.Versions, "must contain unique version names"))
|
||||
}
|
||||
if storageFlagCount != 1 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("versions"), spec.Versions, "must have exactly one version marked as storage version"))
|
||||
}
|
||||
if len(spec.Version) != 0 {
|
||||
if errs := validationutil.IsDNS1035Label(spec.Version); len(errs) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("version"), spec.Version, strings.Join(errs, ",")))
|
||||
}
|
||||
if len(spec.Versions) >= 1 && spec.Versions[0].Name != spec.Version {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("version"), spec.Version, "must match the first version in spec.versions"))
|
||||
}
|
||||
}
|
||||
|
||||
// in addition to the basic name restrictions, some names are required for spec, but not for status
|
||||
if len(spec.Names.Plural) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("names", "plural"), ""))
|
||||
}
|
||||
if len(spec.Names.Singular) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("names", "singular"), ""))
|
||||
}
|
||||
if len(spec.Names.Kind) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("names", "kind"), ""))
|
||||
}
|
||||
if len(spec.Names.ListKind) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("names", "listKind"), ""))
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionNames(&spec.Names, fldPath.Child("names"))...)
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceValidation) {
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionValidation(spec.Validation, hasAnyStatusEnabled(spec), fldPath.Child("validation"))...)
|
||||
} else if spec.Validation != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("validation"), "disabled by feature-gate CustomResourceValidation"))
|
||||
}
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) {
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionSubresources(spec.Subresources, fldPath.Child("subresources"))...)
|
||||
} else if spec.Subresources != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("subresources"), "disabled by feature-gate CustomResourceSubresources"))
|
||||
}
|
||||
|
||||
for i := range spec.AdditionalPrinterColumns {
|
||||
if errs := ValidateCustomResourceColumnDefinition(&spec.AdditionalPrinterColumns[i], fldPath.Child("additionalPrinterColumns").Index(i)); len(errs) > 0 {
|
||||
allErrs = append(allErrs, errs...)
|
||||
}
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, ValidateCustomResourceConversion(spec.Conversion, fldPath.Child("conversion"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateEnumStrings(fldPath *field.Path, value string, accepted []string, required bool) field.ErrorList {
|
||||
if value == "" {
|
||||
if required {
|
||||
return field.ErrorList{field.Required(fldPath, "")}
|
||||
}
|
||||
return field.ErrorList{}
|
||||
}
|
||||
for _, a := range accepted {
|
||||
if a == value {
|
||||
return field.ErrorList{}
|
||||
}
|
||||
}
|
||||
return field.ErrorList{field.NotSupported(fldPath, value, accepted)}
|
||||
}
|
||||
|
||||
// ValidateCustomResourceConversion statically validates
|
||||
func ValidateCustomResourceConversion(conversion *apiextensions.CustomResourceConversion, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if conversion == nil {
|
||||
return allErrs
|
||||
}
|
||||
allErrs = append(allErrs, validateEnumStrings(fldPath.Child("strategy"), string(conversion.Strategy), []string{string(apiextensions.NoneConverter), string(apiextensions.WebhookConverter)}, true)...)
|
||||
if conversion.Strategy == apiextensions.WebhookConverter {
|
||||
if conversion.WebhookClientConfig == nil {
|
||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceWebhookConversion) {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("webhookClientConfig"), "required when strategy is set to Webhook"))
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("webhookClientConfig"), "required when strategy is set to Webhook, but not allowed because the CustomResourceWebhookConversion feature is disabled"))
|
||||
}
|
||||
} else {
|
||||
cc := conversion.WebhookClientConfig
|
||||
switch {
|
||||
case (cc.URL == nil) == (cc.Service == nil):
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("webhookClientConfig"), "exactly one of url or service is required"))
|
||||
case cc.URL != nil:
|
||||
allErrs = append(allErrs, webhook.ValidateWebhookURL(fldPath.Child("webhookClientConfig").Child("url"), *cc.URL, true)...)
|
||||
case cc.Service != nil:
|
||||
allErrs = append(allErrs, webhook.ValidateWebhookService(fldPath.Child("webhookClientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path)...)
|
||||
}
|
||||
}
|
||||
} else if conversion.WebhookClientConfig != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("webhookClientConfig"), "should not be set when strategy is not set to Webhook"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateCustomResourceDefinitionSpecUpdate statically validates
|
||||
func ValidateCustomResourceDefinitionSpecUpdate(spec, oldSpec *apiextensions.CustomResourceDefinitionSpec, established bool, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := ValidateCustomResourceDefinitionSpec(spec, fldPath)
|
||||
|
||||
if established {
|
||||
// these effect the storage and cannot be changed therefore
|
||||
allErrs = append(allErrs, genericvalidation.ValidateImmutableField(spec.Scope, oldSpec.Scope, fldPath.Child("scope"))...)
|
||||
allErrs = append(allErrs, genericvalidation.ValidateImmutableField(spec.Names.Kind, oldSpec.Names.Kind, fldPath.Child("names", "kind"))...)
|
||||
}
|
||||
|
||||
// these affects the resource name, which is always immutable, so this can't be updated.
|
||||
allErrs = append(allErrs, genericvalidation.ValidateImmutableField(spec.Group, oldSpec.Group, fldPath.Child("group"))...)
|
||||
allErrs = append(allErrs, genericvalidation.ValidateImmutableField(spec.Names.Plural, oldSpec.Names.Plural, fldPath.Child("names", "plural"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// getSubresourcesForVersion returns the subresources for given version in given CRD spec.
|
||||
// NOTE That this function assumes version always exist since it's used by the validation process
|
||||
// that iterates through the existing versions.
|
||||
func getSubresourcesForVersion(crd *apiextensions.CustomResourceDefinitionSpec, version string) *apiextensions.CustomResourceSubresources {
|
||||
if !hasPerVersionSubresources(crd.Versions) {
|
||||
return crd.Subresources
|
||||
}
|
||||
for _, v := range crd.Versions {
|
||||
if version == v.Name {
|
||||
return v.Subresources
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasAnyStatusEnabled returns true if given CRD spec has at least one Status Subresource set
|
||||
// among the top-level and per-version Subresources.
|
||||
func hasAnyStatusEnabled(crd *apiextensions.CustomResourceDefinitionSpec) bool {
|
||||
if hasStatusEnabled(crd.Subresources) {
|
||||
return true
|
||||
}
|
||||
for _, v := range crd.Versions {
|
||||
if hasStatusEnabled(v.Subresources) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasStatusEnabled returns true if given CRD Subresources has non-nil Status set.
|
||||
func hasStatusEnabled(subresources *apiextensions.CustomResourceSubresources) bool {
|
||||
if subresources != nil && subresources.Status != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasPerVersionSchema returns true if a CRD uses per-version schema.
|
||||
func hasPerVersionSchema(versions []apiextensions.CustomResourceDefinitionVersion) bool {
|
||||
for _, v := range versions {
|
||||
if v.Schema != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasPerVersionSubresources returns true if a CRD uses per-version subresources.
|
||||
func hasPerVersionSubresources(versions []apiextensions.CustomResourceDefinitionVersion) bool {
|
||||
for _, v := range versions {
|
||||
if v.Subresources != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasPerVersionColumns returns true if a CRD uses per-version columns.
|
||||
func hasPerVersionColumns(versions []apiextensions.CustomResourceDefinitionVersion) bool {
|
||||
for _, v := range versions {
|
||||
if len(v.AdditionalPrinterColumns) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasIdenticalPerVersionSchema returns true if a CRD sets identical non-nil values
|
||||
// to all per-version schemas
|
||||
func hasIdenticalPerVersionSchema(versions []apiextensions.CustomResourceDefinitionVersion) bool {
|
||||
if len(versions) == 0 {
|
||||
return false
|
||||
}
|
||||
value := versions[0].Schema
|
||||
for _, v := range versions {
|
||||
if v.Schema == nil || !apiequality.Semantic.DeepEqual(v.Schema, value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// hasIdenticalPerVersionSubresources returns true if a CRD sets identical non-nil values
|
||||
// to all per-version subresources
|
||||
func hasIdenticalPerVersionSubresources(versions []apiextensions.CustomResourceDefinitionVersion) bool {
|
||||
if len(versions) == 0 {
|
||||
return false
|
||||
}
|
||||
value := versions[0].Subresources
|
||||
for _, v := range versions {
|
||||
if v.Subresources == nil || !apiequality.Semantic.DeepEqual(v.Subresources, value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// hasIdenticalPerVersionColumns returns true if a CRD sets identical non-nil values
|
||||
// to all per-version columns
|
||||
func hasIdenticalPerVersionColumns(versions []apiextensions.CustomResourceDefinitionVersion) bool {
|
||||
if len(versions) == 0 {
|
||||
return false
|
||||
}
|
||||
value := versions[0].AdditionalPrinterColumns
|
||||
for _, v := range versions {
|
||||
if len(v.AdditionalPrinterColumns) == 0 || !apiequality.Semantic.DeepEqual(v.AdditionalPrinterColumns, value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ValidateCustomResourceDefinitionStatus statically validates
|
||||
func ValidateCustomResourceDefinitionStatus(status *apiextensions.CustomResourceDefinitionStatus, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionNames(&status.AcceptedNames, fldPath.Child("acceptedNames"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateCustomResourceDefinitionNames statically validates
|
||||
func ValidateCustomResourceDefinitionNames(names *apiextensions.CustomResourceDefinitionNames, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if errs := validationutil.IsDNS1035Label(names.Plural); len(names.Plural) > 0 && len(errs) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("plural"), names.Plural, strings.Join(errs, ",")))
|
||||
}
|
||||
if errs := validationutil.IsDNS1035Label(names.Singular); len(names.Singular) > 0 && len(errs) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("singular"), names.Singular, strings.Join(errs, ",")))
|
||||
}
|
||||
if errs := validationutil.IsDNS1035Label(strings.ToLower(names.Kind)); len(names.Kind) > 0 && len(errs) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), names.Kind, "may have mixed case, but should otherwise match: "+strings.Join(errs, ",")))
|
||||
}
|
||||
if errs := validationutil.IsDNS1035Label(strings.ToLower(names.ListKind)); len(names.ListKind) > 0 && len(errs) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("listKind"), names.ListKind, "may have mixed case, but should otherwise match: "+strings.Join(errs, ",")))
|
||||
}
|
||||
|
||||
for i, shortName := range names.ShortNames {
|
||||
if errs := validationutil.IsDNS1035Label(shortName); len(errs) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("shortNames").Index(i), shortName, strings.Join(errs, ",")))
|
||||
}
|
||||
}
|
||||
|
||||
// kind and listKind may not be the same or parsing become ambiguous
|
||||
if len(names.Kind) > 0 && names.Kind == names.ListKind {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("listKind"), names.ListKind, "kind and listKind may not be the same"))
|
||||
}
|
||||
|
||||
for i, category := range names.Categories {
|
||||
if errs := validationutil.IsDNS1035Label(category); len(errs) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("categories").Index(i), category, strings.Join(errs, ",")))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateCustomResourceColumnDefinition statically validates a printer column.
|
||||
func ValidateCustomResourceColumnDefinition(col *apiextensions.CustomResourceColumnDefinition, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if len(col.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
|
||||
}
|
||||
|
||||
if len(col.Type) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("type"), fmt.Sprintf("must be one of %s", strings.Join(printerColumnDatatypes.List(), ","))))
|
||||
} else if !printerColumnDatatypes.Has(col.Type) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), col.Type, fmt.Sprintf("must be one of %s", strings.Join(printerColumnDatatypes.List(), ","))))
|
||||
}
|
||||
|
||||
if len(col.Format) > 0 && !customResourceColumnDefinitionFormats.Has(col.Format) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("format"), col.Format, fmt.Sprintf("must be one of %s", strings.Join(customResourceColumnDefinitionFormats.List(), ","))))
|
||||
}
|
||||
|
||||
if len(col.JSONPath) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("JSONPath"), ""))
|
||||
} else if errs := validateSimpleJSONPath(col.JSONPath, fldPath.Child("JSONPath")); len(errs) > 0 {
|
||||
allErrs = append(allErrs, errs...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// specStandardValidator applies validations for different OpenAPI specification versions.
|
||||
type specStandardValidator interface {
|
||||
validate(spec *apiextensions.JSONSchemaProps, fldPath *field.Path) field.ErrorList
|
||||
}
|
||||
|
||||
// ValidateCustomResourceDefinitionValidation statically validates
|
||||
func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiextensions.CustomResourceValidation, statusSubresourceEnabled bool, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if customResourceValidation == nil {
|
||||
return allErrs
|
||||
}
|
||||
|
||||
if schema := customResourceValidation.OpenAPIV3Schema; schema != nil {
|
||||
// if the status subresource is enabled, only certain fields are allowed inside the root schema.
|
||||
// these fields are chosen such that, if status is extracted as properties["status"], it's validation is not lost.
|
||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && statusSubresourceEnabled {
|
||||
v := reflect.ValueOf(schema).Elem()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
// skip zero values
|
||||
if value := v.Field(i).Interface(); reflect.DeepEqual(value, reflect.Zero(reflect.TypeOf(value)).Interface()) {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldName := v.Type().Field(i).Name
|
||||
|
||||
// only "object" type is valid at root of the schema since validation schema for status is extracted as properties["status"]
|
||||
if fieldName == "Type" {
|
||||
if schema.Type != "object" {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("openAPIV3Schema.type"), schema.Type, fmt.Sprintf(`only "object" is allowed as the type at the root of the schema if the status subresource is enabled`)))
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !allowedAtRootSchema(fieldName) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("openAPIV3Schema"), *schema, fmt.Sprintf(`only %v fields are allowed at the root of the schema if the status subresource is enabled`, allowedFieldsAtRootSchema)))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openAPIV3Schema := &specStandardValidatorV3{}
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema, fldPath.Child("openAPIV3Schema"), openAPIV3Schema)...)
|
||||
}
|
||||
|
||||
// if validation passed otherwise, make sure we can actually construct a schema validator from this custom resource validation.
|
||||
if len(allErrs) == 0 {
|
||||
if _, _, err := apiservervalidation.NewSchemaValidator(customResourceValidation); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, "", fmt.Sprintf("error building validator: %v", err)))
|
||||
}
|
||||
}
|
||||
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 and properties are mutual exclusive because otherwise they
|
||||
// contradict Kubernetes' API convention to ignore unknown fields.
|
||||
//
|
||||
// In other words:
|
||||
// - properties are for structs,
|
||||
// - additionalProperties are for map[string]interface{}
|
||||
//
|
||||
// Note: when patternProperties is added to OpenAPI some day, this will have to be
|
||||
// restricted like additionalProperties.
|
||||
if schema.AdditionalProperties != nil {
|
||||
if len(schema.Properties) != 0 {
|
||||
if schema.AdditionalProperties.Allows == false || schema.AdditionalProperties.Schema != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "additionalProperties and properties are mutual exclusive"))
|
||||
}
|
||||
}
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema.AdditionalProperties.Schema, fldPath.Child("additionalProperties"), 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 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 i, jsonSchema := range schema.AllOf {
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(&jsonSchema, fldPath.Child("allOf").Index(i), ssv)...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(schema.OneOf) != 0 {
|
||||
for i, jsonSchema := range schema.OneOf {
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(&jsonSchema, fldPath.Child("oneOf").Index(i), ssv)...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(schema.AnyOf) != 0 {
|
||||
for i, jsonSchema := range schema.AnyOf {
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(&jsonSchema, fldPath.Child("anyOf").Index(i), 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 i, jsonSchema := range schema.Items.JSONSchemas {
|
||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(&jsonSchema, fldPath.Child("items").Index(i), 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.Ref != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("$ref"), "$ref 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
|
||||
}
|
||||
|
||||
// ValidateCustomResourceDefinitionSubresources statically validates
|
||||
func ValidateCustomResourceDefinitionSubresources(subresources *apiextensions.CustomResourceSubresources, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if subresources == nil {
|
||||
return allErrs
|
||||
}
|
||||
|
||||
if subresources.Scale != nil {
|
||||
if len(subresources.Scale.SpecReplicasPath) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("scale.specReplicasPath"), ""))
|
||||
} else {
|
||||
// should be constrained json path under .spec
|
||||
if errs := validateSimpleJSONPath(subresources.Scale.SpecReplicasPath, fldPath.Child("scale.specReplicasPath")); len(errs) > 0 {
|
||||
allErrs = append(allErrs, errs...)
|
||||
} else if !strings.HasPrefix(subresources.Scale.SpecReplicasPath, ".spec.") {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("scale.specReplicasPath"), subresources.Scale.SpecReplicasPath, "should be a json path under .spec"))
|
||||
}
|
||||
}
|
||||
|
||||
if len(subresources.Scale.StatusReplicasPath) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("scale.statusReplicasPath"), ""))
|
||||
} else {
|
||||
// should be constrained json path under .status
|
||||
if errs := validateSimpleJSONPath(subresources.Scale.StatusReplicasPath, fldPath.Child("scale.statusReplicasPath")); len(errs) > 0 {
|
||||
allErrs = append(allErrs, errs...)
|
||||
} else if !strings.HasPrefix(subresources.Scale.StatusReplicasPath, ".status.") {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("scale.statusReplicasPath"), subresources.Scale.StatusReplicasPath, "should be a json path under .status"))
|
||||
}
|
||||
}
|
||||
|
||||
// if labelSelectorPath is present, it should be a constrained json path under .status
|
||||
if subresources.Scale.LabelSelectorPath != nil && len(*subresources.Scale.LabelSelectorPath) > 0 {
|
||||
if errs := validateSimpleJSONPath(*subresources.Scale.LabelSelectorPath, fldPath.Child("scale.labelSelectorPath")); len(errs) > 0 {
|
||||
allErrs = append(allErrs, errs...)
|
||||
} else if !strings.HasPrefix(*subresources.Scale.LabelSelectorPath, ".status.") {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("scale.labelSelectorPath"), subresources.Scale.LabelSelectorPath, "should be a json path under .status"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateSimpleJSONPath(s string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
switch {
|
||||
case len(s) == 0:
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, s, "must not be empty"))
|
||||
case s[0] != '.':
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, s, "must be a simple json path starting with ."))
|
||||
case s != ".":
|
||||
if cs := strings.Split(s[1:], "."); len(cs) < 1 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, s, "must be a json path in the dot notation"))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
var allowedFieldsAtRootSchema = []string{"Description", "Type", "Format", "Title", "Maximum", "ExclusiveMaximum", "Minimum", "ExclusiveMinimum", "MaxLength", "MinLength", "Pattern", "MaxItems", "MinItems", "UniqueItems", "MultipleOf", "Required", "Items", "Properties", "ExternalDocs", "Example"}
|
||||
|
||||
func allowedAtRootSchema(field string) bool {
|
||||
for _, v := range allowedFieldsAtRootSchema {
|
||||
if field == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
1280
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation_test.go
generated
vendored
1280
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user