mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-09 20:17:41 +00:00
apiextensions: fix validation error for status.storedVersions
This commit is contained in:
parent
d4fde1e92a
commit
cfcbce31a3
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user