Skip to content

Commit

Permalink
apiextensions: fix validation error for status.storedVersions
Browse files Browse the repository at this point in the history
Kubernetes-commit: cfcbce31a39d87e0ba30db995397e11eae334605
  • Loading branch information
sttts authored and k8s-publishing-bot committed Jul 28, 2023
1 parent b69767a commit 7709b76
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 13 deletions.
13 changes: 6 additions & 7 deletions pkg/apis/apiextensions/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,13 @@ import (
celgo "github.com/google/cel-go/cel"

"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"
structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
apiequality "k8s.io/apimachinery/pkg/api/equality"
genericvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/util/sets"
Expand All @@ -41,12 +46,6 @@ import (
apiservercel "k8s.io/apiserver/pkg/cel"
"k8s.io/apiserver/pkg/cel/environment"
"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 (
Expand Down Expand Up @@ -219,7 +218,7 @@ func ValidateCustomResourceDefinitionStoredVersions(storedVersions []string, ver
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))
allErrs = append(allErrs, field.Invalid(fldPath, storedVersions, "must have the storage version "+v.Name))
}
if ok {
delete(storedVersionsMap, v.Name)
Expand Down
110 changes: 104 additions & 6 deletions pkg/apis/apiextensions/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import (

"github.com/google/cel-go/cel"

"k8s.io/utils/pointer"

"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
Expand All @@ -42,12 +40,13 @@ import (
"k8s.io/apimachinery/pkg/util/version"
"k8s.io/apiserver/pkg/cel/environment"
"k8s.io/apiserver/pkg/cel/library"
"k8s.io/utils/pointer"
)

type validationMatch struct {
path *field.Path
errorType field.ErrorType
contains string
path *field.Path
errorType field.ErrorType
containsString string
}

func required(path ...string) validationMatch {
Expand All @@ -73,7 +72,12 @@ func forbidden(path ...string) validationMatch {
}

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 }
Expand Down Expand Up @@ -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) {
scheme := runtime.NewScheme()
codecs := serializer.NewCodecFactory(scheme)
Expand Down

0 comments on commit 7709b76

Please sign in to comment.