apiextensions: fix validation error for status.storedVersions
This commit is contained in:
		@@ -30,8 +30,13 @@ import (
 | 
				
			|||||||
	celgo "github.com/google/cel-go/cel"
 | 
						celgo "github.com/google/cel-go/cel"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"k8s.io/apiextensions-apiserver/pkg/apihelpers"
 | 
						"k8s.io/apiextensions-apiserver/pkg/apihelpers"
 | 
				
			||||||
 | 
						"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
 | 
				
			||||||
 | 
						apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 | 
				
			||||||
 | 
						apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 | 
				
			||||||
 | 
						structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
 | 
				
			||||||
	"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
 | 
						"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
 | 
				
			||||||
	structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
 | 
						structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
 | 
				
			||||||
 | 
						apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
 | 
				
			||||||
	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/util/sets"
 | 
						"k8s.io/apimachinery/pkg/util/sets"
 | 
				
			||||||
@@ -41,12 +46,6 @@ import (
 | 
				
			|||||||
	apiservercel "k8s.io/apiserver/pkg/cel"
 | 
						apiservercel "k8s.io/apiserver/pkg/cel"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/cel/environment"
 | 
						"k8s.io/apiserver/pkg/cel/environment"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/util/webhook"
 | 
						"k8s.io/apiserver/pkg/util/webhook"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
 | 
					 | 
				
			||||||
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 | 
					 | 
				
			||||||
	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 | 
					 | 
				
			||||||
	structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
 | 
					 | 
				
			||||||
	apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
@@ -219,7 +218,7 @@ func ValidateCustomResourceDefinitionStoredVersions(storedVersions []string, ver
 | 
				
			|||||||
	for _, v := range versions {
 | 
						for _, v := range versions {
 | 
				
			||||||
		_, ok := storedVersionsMap[v.Name]
 | 
							_, ok := storedVersionsMap[v.Name]
 | 
				
			||||||
		if v.Storage && !ok {
 | 
							if v.Storage && !ok {
 | 
				
			||||||
			allErrs = append(allErrs, field.Invalid(fldPath, v, "must have the storage version "+v.Name))
 | 
								allErrs = append(allErrs, field.Invalid(fldPath, storedVersions, "must have the storage version "+v.Name))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if ok {
 | 
							if ok {
 | 
				
			||||||
			delete(storedVersionsMap, v.Name)
 | 
								delete(storedVersionsMap, v.Name)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,8 +26,6 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/google/cel-go/cel"
 | 
						"github.com/google/cel-go/cel"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"k8s.io/utils/pointer"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
 | 
						"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
 | 
				
			||||||
	apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
 | 
						apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
 | 
				
			||||||
	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 | 
						apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 | 
				
			||||||
@@ -42,12 +40,13 @@ import (
 | 
				
			|||||||
	"k8s.io/apimachinery/pkg/util/version"
 | 
						"k8s.io/apimachinery/pkg/util/version"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/cel/environment"
 | 
						"k8s.io/apiserver/pkg/cel/environment"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/cel/library"
 | 
						"k8s.io/apiserver/pkg/cel/library"
 | 
				
			||||||
 | 
						"k8s.io/utils/pointer"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type validationMatch struct {
 | 
					type validationMatch struct {
 | 
				
			||||||
	path      *field.Path
 | 
						path           *field.Path
 | 
				
			||||||
	errorType field.ErrorType
 | 
						errorType      field.ErrorType
 | 
				
			||||||
	contains  string
 | 
						containsString string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func required(path ...string) validationMatch {
 | 
					func required(path ...string) validationMatch {
 | 
				
			||||||
@@ -73,7 +72,12 @@ func forbidden(path ...string) validationMatch {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (v validationMatch) matches(err *field.Error) bool {
 | 
					func (v validationMatch) matches(err *field.Error) bool {
 | 
				
			||||||
	return err.Type == v.errorType && err.Field == v.path.String() && strings.Contains(err.Error(), v.contains)
 | 
						return err.Type == v.errorType && err.Field == v.path.String() && strings.Contains(err.Error(), v.containsString)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (v validationMatch) contains(s string) validationMatch {
 | 
				
			||||||
 | 
						v.containsString = s
 | 
				
			||||||
 | 
						return v
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func strPtr(s string) *string { return &s }
 | 
					func strPtr(s string) *string { return &s }
 | 
				
			||||||
@@ -9436,6 +9440,100 @@ func TestSchemaHasDefaults(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidateCustomResourceDefinitionStoredVersions(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name           string
 | 
				
			||||||
 | 
							versions       []string
 | 
				
			||||||
 | 
							storageVersion string
 | 
				
			||||||
 | 
							storedVersions []string
 | 
				
			||||||
 | 
							errors         []validationMatch
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "one version",
 | 
				
			||||||
 | 
								versions:       []string{"v1"},
 | 
				
			||||||
 | 
								storageVersion: "v1",
 | 
				
			||||||
 | 
								storedVersions: []string{"v1"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "no stored version",
 | 
				
			||||||
 | 
								versions:       []string{"v1"},
 | 
				
			||||||
 | 
								storageVersion: "v1",
 | 
				
			||||||
 | 
								storedVersions: []string{},
 | 
				
			||||||
 | 
								errors: []validationMatch{
 | 
				
			||||||
 | 
									invalid("status", "storedVersions").contains("Invalid value: []string{}: must have at least one stored version"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "many versions",
 | 
				
			||||||
 | 
								versions:       []string{"v1alpha", "v1beta1", "v1"},
 | 
				
			||||||
 | 
								storageVersion: "v1",
 | 
				
			||||||
 | 
								storedVersions: []string{"v1alpha", "v1"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "missing stored versions",
 | 
				
			||||||
 | 
								versions:       []string{"v1beta1", "v1"},
 | 
				
			||||||
 | 
								storageVersion: "v1",
 | 
				
			||||||
 | 
								storedVersions: []string{"v1alpha", "v1beta1", "v1"},
 | 
				
			||||||
 | 
								errors: []validationMatch{
 | 
				
			||||||
 | 
									invalidIndex(0, "status", "storedVersions").contains("Invalid value: \"v1alpha\": must appear in spec.versions"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "missing storage versions",
 | 
				
			||||||
 | 
								versions:       []string{"v1alpha", "v1beta1", "v1"},
 | 
				
			||||||
 | 
								storageVersion: "v1",
 | 
				
			||||||
 | 
								storedVersions: []string{"v1alpha", "v1beta1"},
 | 
				
			||||||
 | 
								errors: []validationMatch{
 | 
				
			||||||
 | 
									invalid("status", "storedVersions").contains("Invalid value: []string{\"v1alpha\", \"v1beta1\"}: must have the storage version v1"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range tests {
 | 
				
			||||||
 | 
							crd := &apiextensions.CustomResourceDefinition{
 | 
				
			||||||
 | 
								ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
 | 
				
			||||||
 | 
								Spec: apiextensions.CustomResourceDefinitionSpec{
 | 
				
			||||||
 | 
									Group: "group.com",
 | 
				
			||||||
 | 
									Scope: "Cluster",
 | 
				
			||||||
 | 
									Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: tc.storedVersions},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, version := range tc.versions {
 | 
				
			||||||
 | 
								v := apiextensions.CustomResourceDefinitionVersion{Name: version}
 | 
				
			||||||
 | 
								if tc.storageVersion == version {
 | 
				
			||||||
 | 
									v.Storage = true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								crd.Spec.Versions = append(crd.Spec.Versions, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								errs := ValidateCustomResourceDefinitionStoredVersions(crd.Status.StoredVersions, crd.Spec.Versions, field.NewPath("status", "storedVersions"))
 | 
				
			||||||
 | 
								seenErrs := make([]bool, len(errs))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for _, expectedError := range tc.errors {
 | 
				
			||||||
 | 
									found := false
 | 
				
			||||||
 | 
									for i, err := range errs {
 | 
				
			||||||
 | 
										if expectedError.matches(err) && !seenErrs[i] {
 | 
				
			||||||
 | 
											found = true
 | 
				
			||||||
 | 
											seenErrs[i] = true
 | 
				
			||||||
 | 
											break
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if !found {
 | 
				
			||||||
 | 
										t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								for i, seen := range seenErrs {
 | 
				
			||||||
 | 
									if !seen {
 | 
				
			||||||
 | 
										t.Errorf("unexpected error: %v", errs[i])
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BenchmarkSchemaHas(b *testing.B) {
 | 
					func BenchmarkSchemaHas(b *testing.B) {
 | 
				
			||||||
	scheme := runtime.NewScheme()
 | 
						scheme := runtime.NewScheme()
 | 
				
			||||||
	codecs := serializer.NewCodecFactory(scheme)
 | 
						codecs := serializer.NewCodecFactory(scheme)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user