Move CRD approval validation into validation package
This commit is contained in:
		@@ -12,6 +12,7 @@ go_library(
 | 
				
			|||||||
    importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation",
 | 
					    importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation",
 | 
				
			||||||
    importpath = "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation",
 | 
					    importpath = "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation",
 | 
				
			||||||
    deps = [
 | 
					    deps = [
 | 
				
			||||||
 | 
					        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apihelpers:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
 | 
					        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
 | 
					        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library",
 | 
					        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library",
 | 
				
			||||||
@@ -22,6 +23,7 @@ go_library(
 | 
				
			|||||||
        "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/api/validation:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/api/validation:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
 | 
				
			||||||
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
 | 
				
			||||||
@@ -44,6 +46,7 @@ go_test(
 | 
				
			|||||||
        "//staging/src/k8s.io/apimachinery/pkg/api/apitesting/fuzzer:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/api/apitesting/fuzzer:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
 | 
				
			||||||
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,9 +25,11 @@ import (
 | 
				
			|||||||
	govalidate "github.com/go-openapi/validate"
 | 
						govalidate "github.com/go-openapi/validate"
 | 
				
			||||||
	schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
 | 
						schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/apiextensions-apiserver/pkg/apihelpers"
 | 
				
			||||||
	apiequality "k8s.io/apimachinery/pkg/api/equality"
 | 
						apiequality "k8s.io/apimachinery/pkg/api/equality"
 | 
				
			||||||
	genericvalidation "k8s.io/apimachinery/pkg/api/validation"
 | 
						genericvalidation "k8s.io/apimachinery/pkg/api/validation"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime/schema"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
						"k8s.io/apimachinery/pkg/util/sets"
 | 
				
			||||||
	utilvalidation "k8s.io/apimachinery/pkg/util/validation"
 | 
						utilvalidation "k8s.io/apimachinery/pkg/util/validation"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
						"k8s.io/apimachinery/pkg/util/validation/field"
 | 
				
			||||||
@@ -48,7 +50,7 @@ var (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ValidateCustomResourceDefinition statically validates
 | 
					// ValidateCustomResourceDefinition statically validates
 | 
				
			||||||
func ValidateCustomResourceDefinition(obj *apiextensions.CustomResourceDefinition) field.ErrorList {
 | 
					func ValidateCustomResourceDefinition(obj *apiextensions.CustomResourceDefinition, requestGV schema.GroupVersion) field.ErrorList {
 | 
				
			||||||
	nameValidationFn := func(name string, prefix bool) []string {
 | 
						nameValidationFn := func(name string, prefix bool) []string {
 | 
				
			||||||
		ret := genericvalidation.NameIsDNSSubdomain(name, prefix)
 | 
							ret := genericvalidation.NameIsDNSSubdomain(name, prefix)
 | 
				
			||||||
		requiredName := obj.Spec.Names.Plural + "." + obj.Spec.Group
 | 
							requiredName := obj.Spec.Names.Plural + "." + obj.Spec.Group
 | 
				
			||||||
@@ -62,15 +64,17 @@ func ValidateCustomResourceDefinition(obj *apiextensions.CustomResourceDefinitio
 | 
				
			|||||||
	allErrs = append(allErrs, ValidateCustomResourceDefinitionSpec(&obj.Spec, field.NewPath("spec"))...)
 | 
						allErrs = append(allErrs, ValidateCustomResourceDefinitionSpec(&obj.Spec, field.NewPath("spec"))...)
 | 
				
			||||||
	allErrs = append(allErrs, ValidateCustomResourceDefinitionStatus(&obj.Status, field.NewPath("status"))...)
 | 
						allErrs = append(allErrs, ValidateCustomResourceDefinitionStatus(&obj.Status, field.NewPath("status"))...)
 | 
				
			||||||
	allErrs = append(allErrs, ValidateCustomResourceDefinitionStoredVersions(obj.Status.StoredVersions, obj.Spec.Versions, field.NewPath("status").Child("storedVersions"))...)
 | 
						allErrs = append(allErrs, ValidateCustomResourceDefinitionStoredVersions(obj.Status.StoredVersions, obj.Spec.Versions, field.NewPath("status").Child("storedVersions"))...)
 | 
				
			||||||
 | 
						allErrs = append(allErrs, validateAPIApproval(obj, nil, requestGV)...)
 | 
				
			||||||
	return allErrs
 | 
						return allErrs
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ValidateCustomResourceDefinitionUpdate statically validates
 | 
					// ValidateCustomResourceDefinitionUpdate statically validates
 | 
				
			||||||
func ValidateCustomResourceDefinitionUpdate(obj, oldObj *apiextensions.CustomResourceDefinition) field.ErrorList {
 | 
					func ValidateCustomResourceDefinitionUpdate(obj, oldObj *apiextensions.CustomResourceDefinition, requestGV schema.GroupVersion) field.ErrorList {
 | 
				
			||||||
	allErrs := genericvalidation.ValidateObjectMetaUpdate(&obj.ObjectMeta, &oldObj.ObjectMeta, field.NewPath("metadata"))
 | 
						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, 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, ValidateCustomResourceDefinitionStatus(&obj.Status, field.NewPath("status"))...)
 | 
				
			||||||
	allErrs = append(allErrs, ValidateCustomResourceDefinitionStoredVersions(obj.Status.StoredVersions, obj.Spec.Versions, field.NewPath("status").Child("storedVersions"))...)
 | 
						allErrs = append(allErrs, ValidateCustomResourceDefinitionStoredVersions(obj.Status.StoredVersions, obj.Spec.Versions, field.NewPath("status").Child("storedVersions"))...)
 | 
				
			||||||
 | 
						allErrs = append(allErrs, validateAPIApproval(obj, oldObj, requestGV)...)
 | 
				
			||||||
	return allErrs
 | 
						return allErrs
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1094,3 +1098,44 @@ func schemaHasKubernetesExtensions(s *apiextensions.JSONSchemaProps) bool {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return false
 | 
						return false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// validateAPIApproval returns a list of errors if the API approval annotation isn't valid
 | 
				
			||||||
 | 
					func validateAPIApproval(newCRD, oldCRD *apiextensions.CustomResourceDefinition, requestGV schema.GroupVersion) field.ErrorList {
 | 
				
			||||||
 | 
						// check to see if we need confirm API approval for kube group.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if requestGV == v1beta1.SchemeGroupVersion {
 | 
				
			||||||
 | 
							// no-op for compatibility with v1beta1
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !apihelpers.IsProtectedCommunityGroup(newCRD.Spec.Group) {
 | 
				
			||||||
 | 
							// no-op for non-protected groups
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// default to a state that allows missing values to continue to be missing
 | 
				
			||||||
 | 
						var oldApprovalState *apihelpers.APIApprovalState
 | 
				
			||||||
 | 
						if oldCRD != nil {
 | 
				
			||||||
 | 
							t, _ := apihelpers.GetAPIApprovalState(oldCRD.Annotations)
 | 
				
			||||||
 | 
							oldApprovalState = &t
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						newApprovalState, reason := apihelpers.GetAPIApprovalState(newCRD.Annotations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// if the approval state hasn't changed, never fail on approval validation
 | 
				
			||||||
 | 
						// this is allowed so that a v1 client that is simply updating spec and not mutating this value doesn't get rejected.  Imagine a controller controlling a CRD spec.
 | 
				
			||||||
 | 
						if oldApprovalState != nil && *oldApprovalState == newApprovalState {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// in v1, we require valid approval strings
 | 
				
			||||||
 | 
						switch newApprovalState {
 | 
				
			||||||
 | 
						case apihelpers.APIApprovalInvalid:
 | 
				
			||||||
 | 
							return field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations").Key(v1beta1.KubeAPIApprovedAnnotation), newCRD.Annotations[v1beta1.KubeAPIApprovedAnnotation], reason)}
 | 
				
			||||||
 | 
						case apihelpers.APIApprovalMissing:
 | 
				
			||||||
 | 
							return field.ErrorList{field.Required(field.NewPath("metadata", "annotations").Key(v1beta1.KubeAPIApprovedAnnotation), reason)}
 | 
				
			||||||
 | 
						case apihelpers.APIApproved, apihelpers.APIApprovalBypassed:
 | 
				
			||||||
 | 
							// success
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations").Key(v1beta1.KubeAPIApprovedAnnotation), newCRD.Annotations[v1beta1.KubeAPIApprovedAnnotation], reason)}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,6 +28,7 @@ import (
 | 
				
			|||||||
	"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
 | 
						"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime/schema"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/runtime/serializer"
 | 
						"k8s.io/apimachinery/pkg/runtime/serializer"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/json"
 | 
						"k8s.io/apimachinery/pkg/util/json"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
						"k8s.io/apimachinery/pkg/util/validation/field"
 | 
				
			||||||
@@ -78,6 +79,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
	tests := []struct {
 | 
						tests := []struct {
 | 
				
			||||||
		name            string
 | 
							name            string
 | 
				
			||||||
		resource        *apiextensions.CustomResourceDefinition
 | 
							resource        *apiextensions.CustomResourceDefinition
 | 
				
			||||||
 | 
							requestGV       schema.GroupVersion
 | 
				
			||||||
		errors          []validationMatch
 | 
							errors          []validationMatch
 | 
				
			||||||
		enabledFeatures []featuregate.Feature
 | 
							enabledFeatures []featuregate.Feature
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
@@ -315,6 +317,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				forbidden("spec", "conversion", "webhookClientConfig"),
 | 
									forbidden("spec", "conversion", "webhookClientConfig"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -354,6 +357,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				forbidden("spec", "conversion", "conversionReviewVersions"),
 | 
									forbidden("spec", "conversion", "conversionReviewVersions"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -665,7 +669,8 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version1"},
 | 
										StoredVersions: []string{"version1"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			errors: []validationMatch{},
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
 | 
								errors:    []validationMatch{},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "webhook conversion without preserveUnknownFields=false",
 | 
								name: "webhook conversion without preserveUnknownFields=false",
 | 
				
			||||||
@@ -705,6 +710,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version1"},
 | 
										StoredVersions: []string{"version1"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				invalid("spec", "conversion", "strategy"),
 | 
									invalid("spec", "conversion", "strategy"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -788,6 +794,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				invalid("spec", "versions"),
 | 
									invalid("spec", "versions"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -826,6 +833,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				invalid("spec", "versions"),
 | 
									invalid("spec", "versions"),
 | 
				
			||||||
				invalid("status", "storedVersions"),
 | 
									invalid("status", "storedVersions"),
 | 
				
			||||||
@@ -865,6 +873,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				invalid("status", "storedVersions"),
 | 
									invalid("status", "storedVersions"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -898,6 +907,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{},
 | 
										StoredVersions: []string{},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				invalid("status", "storedVersions"),
 | 
									invalid("status", "storedVersions"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -914,6 +924,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					PreserveUnknownFields: pointer.BoolPtr(true),
 | 
										PreserveUnknownFields: pointer.BoolPtr(true),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				invalid("status", "storedVersions"),
 | 
									invalid("status", "storedVersions"),
 | 
				
			||||||
				invalid("metadata", "name"),
 | 
									invalid("metadata", "name"),
 | 
				
			||||||
@@ -966,6 +977,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					},
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				invalid("status", "storedVersions"),
 | 
									invalid("status", "storedVersions"),
 | 
				
			||||||
				invalid("metadata", "name"),
 | 
									invalid("metadata", "name"),
 | 
				
			||||||
@@ -1014,6 +1026,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				invalid("metadata", "name"),
 | 
									invalid("metadata", "name"),
 | 
				
			||||||
				invalid("spec", "group"),
 | 
									invalid("spec", "group"),
 | 
				
			||||||
@@ -1054,6 +1067,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				forbidden("spec", "validation", "openAPIV3Schema", "additionalProperties"),
 | 
									forbidden("spec", "validation", "openAPIV3Schema", "additionalProperties"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -1092,7 +1106,8 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			errors: []validationMatch{},
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
 | 
								errors:    []validationMatch{},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "per-version fields may not all be set to identical values (top-level field should be used instead)",
 | 
								name: "per-version fields may not all be set to identical values (top-level field should be used instead)",
 | 
				
			||||||
@@ -1136,6 +1151,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				// Per-version schema/subresources/columns may not all be set to identical values.
 | 
									// Per-version schema/subresources/columns may not all be set to identical values.
 | 
				
			||||||
				// Note that the test will fail if we de-duplicate the expected errors below.
 | 
									// Note that the test will fail if we de-duplicate the expected errors below.
 | 
				
			||||||
@@ -1421,6 +1437,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version0"},
 | 
										StoredVersions: []string{"version0"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				invalid("spec", "versions[3]", "subresources", "scale", "labelSelectorPath"),
 | 
									invalid("spec", "versions[3]", "subresources", "scale", "labelSelectorPath"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -1457,6 +1474,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "default"), // disabled feature-gate
 | 
									forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "default"), // disabled feature-gate
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -1491,6 +1509,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				required("spec", "validation", "openAPIV3Schema", "type"),
 | 
									required("spec", "validation", "openAPIV3Schema", "type"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -1525,6 +1544,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				required("spec", "validation", "openAPIV3Schema", "type"),
 | 
									required("spec", "validation", "openAPIV3Schema", "type"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -1563,6 +1583,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				required("spec", "validation", "openAPIV3Schema", "type"),
 | 
									required("spec", "validation", "openAPIV3Schema", "type"),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -1626,6 +1647,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
			errors: []validationMatch{
 | 
								errors: []validationMatch{
 | 
				
			||||||
				forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "x-kubernetes-embedded-resource"),
 | 
									forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "x-kubernetes-embedded-resource"),
 | 
				
			||||||
				forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[apiVersion]", "properties[foo]", "x-kubernetes-embedded-resource"),
 | 
									forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[apiVersion]", "properties[foo]", "x-kubernetes-embedded-resource"),
 | 
				
			||||||
@@ -2388,7 +2410,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
 | 
				
			|||||||
			if tc.resource.Spec.Conversion != nil && tc.resource.Spec.Conversion.Strategy == apiextensions.WebhookConverter && len(tc.resource.Spec.Conversion.ConversionReviewVersions) == 0 {
 | 
								if tc.resource.Spec.Conversion != nil && tc.resource.Spec.Conversion.Strategy == apiextensions.WebhookConverter && len(tc.resource.Spec.Conversion.ConversionReviewVersions) == 0 {
 | 
				
			||||||
				tc.resource.Spec.Conversion.ConversionReviewVersions = []string{"v1beta1"}
 | 
									tc.resource.Spec.Conversion.ConversionReviewVersions = []string{"v1beta1"}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			errs := ValidateCustomResourceDefinition(tc.resource)
 | 
								errs := ValidateCustomResourceDefinition(tc.resource, tc.requestGV)
 | 
				
			||||||
			seenErrs := make([]bool, len(errs))
 | 
								seenErrs := make([]bool, len(errs))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for _, expectedError := range tc.errors {
 | 
								for _, expectedError := range tc.errors {
 | 
				
			||||||
@@ -2420,6 +2442,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
 | 
				
			|||||||
		name            string
 | 
							name            string
 | 
				
			||||||
		old             *apiextensions.CustomResourceDefinition
 | 
							old             *apiextensions.CustomResourceDefinition
 | 
				
			||||||
		resource        *apiextensions.CustomResourceDefinition
 | 
							resource        *apiextensions.CustomResourceDefinition
 | 
				
			||||||
 | 
							requestGV       schema.GroupVersion
 | 
				
			||||||
		errors          []validationMatch
 | 
							errors          []validationMatch
 | 
				
			||||||
		enabledFeatures []featuregate.Feature
 | 
							enabledFeatures []featuregate.Feature
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
@@ -3350,7 +3373,8 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
 | 
				
			|||||||
					StoredVersions: []string{"version"},
 | 
										StoredVersions: []string{"version"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			errors: []validationMatch{},
 | 
								requestGV: apiextensionsv1beta1.SchemeGroupVersion,
 | 
				
			||||||
 | 
								errors:    []validationMatch{},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "setting defaults with enabled feature gate",
 | 
								name: "setting defaults with enabled feature gate",
 | 
				
			||||||
@@ -3528,7 +3552,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
 | 
				
			|||||||
				defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, gate, true)()
 | 
									defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, gate, true)()
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			errs := ValidateCustomResourceDefinitionUpdate(tc.resource, tc.old)
 | 
								errs := ValidateCustomResourceDefinitionUpdate(tc.resource, tc.old, tc.requestGV)
 | 
				
			||||||
			seenErrs := make([]bool, len(errs))
 | 
								seenErrs := make([]bool, len(errs))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for _, expectedError := range tc.errors {
 | 
								for _, expectedError := range tc.errors {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,9 +11,7 @@ go_library(
 | 
				
			|||||||
    importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition",
 | 
					    importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition",
 | 
				
			||||||
    importpath = "k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition",
 | 
					    importpath = "k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition",
 | 
				
			||||||
    deps = [
 | 
					    deps = [
 | 
				
			||||||
        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apihelpers:go_default_library",
 | 
					 | 
				
			||||||
        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
 | 
					        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
 | 
					 | 
				
			||||||
        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation:go_default_library",
 | 
					        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
 | 
					        "//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
 | 
				
			||||||
@@ -22,6 +20,7 @@ go_library(
 | 
				
			|||||||
        "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
 | 
				
			||||||
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
 | 
				
			||||||
@@ -55,12 +54,14 @@ go_test(
 | 
				
			|||||||
    deps = [
 | 
					    deps = [
 | 
				
			||||||
        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
 | 
					        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
 | 
					        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
 | 
				
			||||||
 | 
					        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
 | 
					        "//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
				
			||||||
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
 | 
					 | 
				
			||||||
        "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
 | 
					        "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
 | 
					        "//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/utils/pointer:go_default_library",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,17 +20,16 @@ import (
 | 
				
			|||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"k8s.io/apiextensions-apiserver/pkg/apihelpers"
 | 
					 | 
				
			||||||
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
 | 
						"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
 | 
				
			||||||
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 | 
					 | 
				
			||||||
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
 | 
						"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
 | 
				
			||||||
	apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
 | 
						apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
 | 
				
			||||||
	apiequality "k8s.io/apimachinery/pkg/api/equality"
 | 
						apiequality "k8s.io/apimachinery/pkg/api/equality"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/fields"
 | 
						"k8s.io/apimachinery/pkg/fields"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/labels"
 | 
						"k8s.io/apimachinery/pkg/labels"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime/schema"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
						"k8s.io/apimachinery/pkg/util/validation/field"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/endpoints/request"
 | 
						genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/registry/generic"
 | 
						"k8s.io/apiserver/pkg/registry/generic"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/storage"
 | 
						"k8s.io/apiserver/pkg/storage"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/storage/names"
 | 
						"k8s.io/apiserver/pkg/storage/names"
 | 
				
			||||||
@@ -101,8 +100,12 @@ func (strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Validate validates a new CustomResourceDefinition.
 | 
					// Validate validates a new CustomResourceDefinition.
 | 
				
			||||||
func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
 | 
					func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
 | 
				
			||||||
	fieldErrors := validation.ValidateCustomResourceDefinition(obj.(*apiextensions.CustomResourceDefinition))
 | 
						var groupVersion schema.GroupVersion
 | 
				
			||||||
	return append(fieldErrors, validateAPIApproval(ctx, obj.(*apiextensions.CustomResourceDefinition), nil)...)
 | 
						if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
 | 
				
			||||||
 | 
							groupVersion = schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return validation.ValidateCustomResourceDefinition(obj.(*apiextensions.CustomResourceDefinition), groupVersion)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AllowCreateOnUpdate is false for CustomResourceDefinition; this means a POST is
 | 
					// AllowCreateOnUpdate is false for CustomResourceDefinition; this means a POST is
 | 
				
			||||||
@@ -122,47 +125,12 @@ func (strategy) Canonicalize(obj runtime.Object) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ValidateUpdate is the default update validation for an end user updating status.
 | 
					// ValidateUpdate is the default update validation for an end user updating status.
 | 
				
			||||||
func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
 | 
					func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
 | 
				
			||||||
	fieldErrors := validation.ValidateCustomResourceDefinitionUpdate(obj.(*apiextensions.CustomResourceDefinition), old.(*apiextensions.CustomResourceDefinition))
 | 
						var groupVersion schema.GroupVersion
 | 
				
			||||||
 | 
						if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
 | 
				
			||||||
	return append(fieldErrors, validateAPIApproval(ctx, obj.(*apiextensions.CustomResourceDefinition), old.(*apiextensions.CustomResourceDefinition))...)
 | 
							groupVersion = schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// validateAPIApproval returns a list of errors if the API approval annotation isn't valid
 | 
					 | 
				
			||||||
func validateAPIApproval(ctx context.Context, newCRD, oldCRD *apiextensions.CustomResourceDefinition) field.ErrorList {
 | 
					 | 
				
			||||||
	// check to see if we need confirm API approval for kube group.  Do nothing for non-protected groups and do nothing in v1beta1.
 | 
					 | 
				
			||||||
	if requestInfo, ok := request.RequestInfoFrom(ctx); !ok || requestInfo.APIVersion == "v1beta1" {
 | 
					 | 
				
			||||||
		return field.ErrorList{}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if !apihelpers.IsProtectedCommunityGroup(newCRD.Spec.Group) {
 | 
					 | 
				
			||||||
		return field.ErrorList{}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// default to a state that allows missing values to continue to be missing
 | 
						return validation.ValidateCustomResourceDefinitionUpdate(obj.(*apiextensions.CustomResourceDefinition), old.(*apiextensions.CustomResourceDefinition), groupVersion)
 | 
				
			||||||
	var oldApprovalState *apihelpers.APIApprovalState
 | 
					 | 
				
			||||||
	if oldCRD != nil {
 | 
					 | 
				
			||||||
		t, _ := apihelpers.GetAPIApprovalState(oldCRD.Annotations)
 | 
					 | 
				
			||||||
		oldApprovalState = &t
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	newApprovalState, reason := apihelpers.GetAPIApprovalState(newCRD.Annotations)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// if the approval state hasn't changed, never fail on approval validation
 | 
					 | 
				
			||||||
	// this is allowed so that a v1 client that is simply updating spec and not mutating this value doesn't get rejected.  Imagine a controller controlling a CRD spec.
 | 
					 | 
				
			||||||
	if oldApprovalState != nil && *oldApprovalState == newApprovalState {
 | 
					 | 
				
			||||||
		return field.ErrorList{}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// in v1, we require valid approval strings
 | 
					 | 
				
			||||||
	switch newApprovalState {
 | 
					 | 
				
			||||||
	case apihelpers.APIApprovalInvalid:
 | 
					 | 
				
			||||||
		return field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations").Key(v1beta1.KubeAPIApprovedAnnotation), newCRD.Annotations[v1beta1.KubeAPIApprovedAnnotation], reason)}
 | 
					 | 
				
			||||||
	case apihelpers.APIApprovalMissing:
 | 
					 | 
				
			||||||
		return field.ErrorList{field.Required(field.NewPath("metadata", "annotations").Key(v1beta1.KubeAPIApprovedAnnotation), reason)}
 | 
					 | 
				
			||||||
	case apihelpers.APIApproved, apihelpers.APIApprovalBypassed:
 | 
					 | 
				
			||||||
		// success
 | 
					 | 
				
			||||||
		return field.ErrorList{}
 | 
					 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
		return field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations").Key(v1beta1.KubeAPIApprovedAnnotation), newCRD.Annotations[v1beta1.KubeAPIApprovedAnnotation], reason)}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type statusStrategy struct {
 | 
					type statusStrategy struct {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,20 +17,21 @@ limitations under the License.
 | 
				
			|||||||
package customresourcedefinition
 | 
					package customresourcedefinition
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
					 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
 | 
						"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
 | 
				
			||||||
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 | 
						"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 | 
				
			||||||
 | 
						"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
 | 
				
			||||||
	apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
 | 
						apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime/schema"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/diff"
 | 
						"k8s.io/apimachinery/pkg/util/diff"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
						"k8s.io/apimachinery/pkg/util/validation/field"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/endpoints/request"
 | 
					 | 
				
			||||||
	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
	featuregatetesting "k8s.io/component-base/featuregate/testing"
 | 
						featuregatetesting "k8s.io/component-base/featuregate/testing"
 | 
				
			||||||
 | 
						"k8s.io/utils/pointer"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
 | 
					func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
 | 
				
			||||||
@@ -517,6 +518,9 @@ func TestValidateAPIApproval(t *testing.T) {
 | 
				
			|||||||
			annotationValue: "invalid",
 | 
								annotationValue: "invalid",
 | 
				
			||||||
			validateError: func(t *testing.T, errors field.ErrorList) {
 | 
								validateError: func(t *testing.T, errors field.ErrorList) {
 | 
				
			||||||
				t.Helper()
 | 
									t.Helper()
 | 
				
			||||||
 | 
									if len(errors) == 0 {
 | 
				
			||||||
 | 
										t.Fatal("expected errors, got none")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				if e, a := `metadata.annotations[api-approved.kubernetes.io]: Invalid value: "invalid": protected groups must have approval annotation "api-approved.kubernetes.io" with either a URL or a reason starting with "unapproved", see https://github.com/kubernetes/enhancements/pull/1111`, errors.ToAggregate().Error(); e != a {
 | 
									if e, a := `metadata.annotations[api-approved.kubernetes.io]: Invalid value: "invalid": protected groups must have approval annotation "api-approved.kubernetes.io" with either a URL or a reason starting with "unapproved", see https://github.com/kubernetes/enhancements/pull/1111`, errors.ToAggregate().Error(); e != a {
 | 
				
			||||||
					t.Fatal(errors)
 | 
										t.Fatal(errors)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -538,6 +542,9 @@ func TestValidateAPIApproval(t *testing.T) {
 | 
				
			|||||||
			oldAnnotationValue: strPtr("invalid"),
 | 
								oldAnnotationValue: strPtr("invalid"),
 | 
				
			||||||
			validateError: func(t *testing.T, errors field.ErrorList) {
 | 
								validateError: func(t *testing.T, errors field.ErrorList) {
 | 
				
			||||||
				t.Helper()
 | 
									t.Helper()
 | 
				
			||||||
 | 
									if len(errors) == 0 {
 | 
				
			||||||
 | 
										t.Fatal("expected errors, got none")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				if e, a := `metadata.annotations[api-approved.kubernetes.io]: Required value: protected groups must have approval annotation "api-approved.kubernetes.io", see https://github.com/kubernetes/enhancements/pull/1111`, errors.ToAggregate().Error(); e != a {
 | 
									if e, a := `metadata.annotations[api-approved.kubernetes.io]: Required value: protected groups must have approval annotation "api-approved.kubernetes.io", see https://github.com/kubernetes/enhancements/pull/1111`, errors.ToAggregate().Error(); e != a {
 | 
				
			||||||
					t.Fatal(errors)
 | 
										t.Fatal(errors)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -551,6 +558,9 @@ func TestValidateAPIApproval(t *testing.T) {
 | 
				
			|||||||
			oldAnnotationValue: strPtr(""),
 | 
								oldAnnotationValue: strPtr(""),
 | 
				
			||||||
			validateError: func(t *testing.T, errors field.ErrorList) {
 | 
								validateError: func(t *testing.T, errors field.ErrorList) {
 | 
				
			||||||
				t.Helper()
 | 
									t.Helper()
 | 
				
			||||||
 | 
									if len(errors) == 0 {
 | 
				
			||||||
 | 
										t.Fatal("expected errors, got none")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				if e, a := `metadata.annotations[api-approved.kubernetes.io]: Invalid value: "invalid": protected groups must have approval annotation "api-approved.kubernetes.io" with either a URL or a reason starting with "unapproved", see https://github.com/kubernetes/enhancements/pull/1111`, errors.ToAggregate().Error(); e != a {
 | 
									if e, a := `metadata.annotations[api-approved.kubernetes.io]: Invalid value: "invalid": protected groups must have approval annotation "api-approved.kubernetes.io" with either a URL or a reason starting with "unapproved", see https://github.com/kubernetes/enhancements/pull/1111`, errors.ToAggregate().Error(); e != a {
 | 
				
			||||||
					t.Fatal(errors)
 | 
										t.Fatal(errors)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -563,6 +573,9 @@ func TestValidateAPIApproval(t *testing.T) {
 | 
				
			|||||||
			annotationValue: "",
 | 
								annotationValue: "",
 | 
				
			||||||
			validateError: func(t *testing.T, errors field.ErrorList) {
 | 
								validateError: func(t *testing.T, errors field.ErrorList) {
 | 
				
			||||||
				t.Helper()
 | 
									t.Helper()
 | 
				
			||||||
 | 
									if len(errors) == 0 {
 | 
				
			||||||
 | 
										t.Fatal("expected errors, got none")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				if e, a := `metadata.annotations[api-approved.kubernetes.io]: Required value: protected groups must have approval annotation "api-approved.kubernetes.io", see https://github.com/kubernetes/enhancements/pull/1111`, errors.ToAggregate().Error(); e != a {
 | 
									if e, a := `metadata.annotations[api-approved.kubernetes.io]: Required value: protected groups must have approval annotation "api-approved.kubernetes.io", see https://github.com/kubernetes/enhancements/pull/1111`, errors.ToAggregate().Error(); e != a {
 | 
				
			||||||
					t.Fatal(errors)
 | 
										t.Fatal(errors)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -597,6 +610,9 @@ func TestValidateAPIApproval(t *testing.T) {
 | 
				
			|||||||
			annotationValue: "invalid",
 | 
								annotationValue: "invalid",
 | 
				
			||||||
			validateError: func(t *testing.T, errors field.ErrorList) {
 | 
								validateError: func(t *testing.T, errors field.ErrorList) {
 | 
				
			||||||
				t.Helper()
 | 
									t.Helper()
 | 
				
			||||||
 | 
									if len(errors) == 0 {
 | 
				
			||||||
 | 
										t.Fatal("expected errors, got none")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				if e, a := `metadata.annotations[api-approved.kubernetes.io]: Invalid value: "invalid": protected groups must have approval annotation "api-approved.kubernetes.io" with either a URL or a reason starting with "unapproved", see https://github.com/kubernetes/enhancements/pull/1111`, errors.ToAggregate().Error(); e != a {
 | 
									if e, a := `metadata.annotations[api-approved.kubernetes.io]: Invalid value: "invalid": protected groups must have approval annotation "api-approved.kubernetes.io" with either a URL or a reason starting with "unapproved", see https://github.com/kubernetes/enhancements/pull/1111`, errors.ToAggregate().Error(); e != a {
 | 
				
			||||||
					t.Fatal(errors)
 | 
										t.Fatal(errors)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -606,24 +622,48 @@ func TestValidateAPIApproval(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for _, test := range tests {
 | 
						for _, test := range tests {
 | 
				
			||||||
		t.Run(test.name, func(t *testing.T) {
 | 
							t.Run(test.name, func(t *testing.T) {
 | 
				
			||||||
			ctx := request.WithRequestInfo(context.TODO(), &request.RequestInfo{APIVersion: test.version})
 | 
					 | 
				
			||||||
			crd := &apiextensions.CustomResourceDefinition{
 | 
								crd := &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
				ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: test.annotationValue}},
 | 
									ObjectMeta: metav1.ObjectMeta{Name: "foos." + test.group, Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: test.annotationValue}, ResourceVersion: "1"},
 | 
				
			||||||
				Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
									Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
				
			||||||
					Group: test.group,
 | 
										Group:    test.group,
 | 
				
			||||||
 | 
										Scope:    apiextensions.NamespaceScoped,
 | 
				
			||||||
 | 
										Version:  "v1",
 | 
				
			||||||
 | 
										Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "v1", Storage: true, Served: true}},
 | 
				
			||||||
 | 
										Names:    apiextensions.CustomResourceDefinitionNames{Plural: "foos", Singular: "foo", Kind: "Foo", ListKind: "FooList"},
 | 
				
			||||||
 | 
										Validation: &apiextensions.CustomResourceValidation{
 | 
				
			||||||
 | 
											OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object", XPreserveUnknownFields: pointer.BoolPtr(true)},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Status: apiextensions.CustomResourceDefinitionStatus{
 | 
				
			||||||
 | 
										StoredVersions: []string{"v1"},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			var oldCRD *apiextensions.CustomResourceDefinition
 | 
								var oldCRD *apiextensions.CustomResourceDefinition
 | 
				
			||||||
			if test.oldAnnotationValue != nil {
 | 
								if test.oldAnnotationValue != nil {
 | 
				
			||||||
				oldCRD = &apiextensions.CustomResourceDefinition{
 | 
									oldCRD = &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
					ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: *test.oldAnnotationValue}},
 | 
										ObjectMeta: metav1.ObjectMeta{Name: "foos." + test.group, Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: *test.oldAnnotationValue}, ResourceVersion: "1"},
 | 
				
			||||||
					Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
										Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
				
			||||||
						Group: test.group,
 | 
											Group:    test.group,
 | 
				
			||||||
 | 
											Scope:    apiextensions.NamespaceScoped,
 | 
				
			||||||
 | 
											Version:  "v1",
 | 
				
			||||||
 | 
											Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "v1", Storage: true, Served: true}},
 | 
				
			||||||
 | 
											Names:    apiextensions.CustomResourceDefinitionNames{Plural: "foos", Singular: "foo", Kind: "Foo", ListKind: "FooList"},
 | 
				
			||||||
 | 
											Validation: &apiextensions.CustomResourceValidation{
 | 
				
			||||||
 | 
												OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object", XPreserveUnknownFields: pointer.BoolPtr(true)},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Status: apiextensions.CustomResourceDefinitionStatus{
 | 
				
			||||||
 | 
											StoredVersions: []string{"v1"},
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			actual := validateAPIApproval(ctx, crd, oldCRD)
 | 
								var actual field.ErrorList
 | 
				
			||||||
 | 
								if oldCRD == nil {
 | 
				
			||||||
 | 
									actual = validation.ValidateCustomResourceDefinition(crd, schema.GroupVersion{Group: "apiextensions.k8s.io", Version: test.version})
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									actual = validation.ValidateCustomResourceDefinitionUpdate(crd, oldCRD, schema.GroupVersion{Group: "apiextensions.k8s.io", Version: test.version})
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			test.validateError(t, actual)
 | 
								test.validateError(t, actual)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user