diff --git a/cmd/kube-apiserver/app/aggregator.go b/cmd/kube-apiserver/app/aggregator.go index 30f3b01915e..c21c6e81387 100644 --- a/cmd/kube-apiserver/app/aggregator.go +++ b/cmd/kube-apiserver/app/aggregator.go @@ -28,7 +28,7 @@ import ( "k8s.io/klog" - apiextensionsinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion" + apiextensionsinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -140,7 +140,7 @@ func createAggregatorServer(aggregatorConfig *aggregatorapiserver.Config, delega autoRegistrationController := autoregister.NewAutoRegisterController(aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(), apiRegistrationClient) apiServices := apiServicesToRegister(delegateAPIServer, autoRegistrationController) crdRegistrationController := crdregistration.NewCRDRegistrationController( - apiExtensionInformers.Apiextensions().InternalVersion().CustomResourceDefinitions(), + apiExtensionInformers.Apiextensions().V1().CustomResourceDefinitions(), autoRegistrationController) err = aggregatorServer.GenericAPIServer.AddPostStartHook("kube-apiserver-autoregistration", func(context genericapiserver.PostStartHookContext) error { diff --git a/pkg/master/controller/crdregistration/crdregistration_controller.go b/pkg/master/controller/crdregistration/crdregistration_controller.go index 0afbfd91e25..3f67871bb31 100644 --- a/pkg/master/controller/crdregistration/crdregistration_controller.go +++ b/pkg/master/controller/crdregistration/crdregistration_controller.go @@ -22,9 +22,9 @@ import ( "k8s.io/klog" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - crdinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" - crdlisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + crdinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1" + crdlisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/pkg/master/controller/crdregistration/crdregistration_controller_test.go b/pkg/master/controller/crdregistration/crdregistration_controller_test.go index 782d120ab14..3d98d68f1d8 100644 --- a/pkg/master/controller/crdregistration/crdregistration_controller_test.go +++ b/pkg/master/controller/crdregistration/crdregistration_controller_test.go @@ -20,8 +20,8 @@ import ( "reflect" "testing" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - crdlisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + crdlisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/tools/cache" diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apihelpers/helpers.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apihelpers/helpers.go index b874785a16d..523d968a500 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apihelpers/helpers.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apihelpers/helpers.go @@ -20,8 +20,10 @@ import ( "fmt" "net/url" "strings" + "time" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // IsProtectedCommunityGroup returns whether or not a group specified for a CRD is protected for the community and needs @@ -54,18 +56,205 @@ const ( // GetAPIApprovalState returns the state of the API approval and reason for that state func GetAPIApprovalState(annotations map[string]string) (state APIApprovalState, reason string) { - annotation := annotations[v1beta1.KubeAPIApprovedAnnotation] + annotation := annotations[apiextensions.KubeAPIApprovedAnnotation] // we use the result of this parsing in the switch/case below url, annotationURLParseErr := url.ParseRequestURI(annotation) switch { case len(annotation) == 0: - return APIApprovalMissing, fmt.Sprintf("protected groups must have approval annotation %q, see https://github.com/kubernetes/enhancements/pull/1111", v1beta1.KubeAPIApprovedAnnotation) + return APIApprovalMissing, fmt.Sprintf("protected groups must have approval annotation %q, see https://github.com/kubernetes/enhancements/pull/1111", apiextensions.KubeAPIApprovedAnnotation) case strings.HasPrefix(annotation, "unapproved"): return APIApprovalBypassed, fmt.Sprintf("not approved: %q", annotation) case annotationURLParseErr == nil && url != nil && len(url.Host) > 0 && len(url.Scheme) > 0: return APIApproved, fmt.Sprintf("approved in %v", annotation) default: - return APIApprovalInvalid, fmt.Sprintf("protected groups must have approval annotation %q with either a URL or a reason starting with \"unapproved\", see https://github.com/kubernetes/enhancements/pull/1111", v1beta1.KubeAPIApprovedAnnotation) + return APIApprovalInvalid, fmt.Sprintf("protected groups must have approval annotation %q with either a URL or a reason starting with \"unapproved\", see https://github.com/kubernetes/enhancements/pull/1111", apiextensions.KubeAPIApprovedAnnotation) } } + +// SetCRDCondition sets the status condition. It either overwrites the existing one or creates a new one. +func SetCRDCondition(crd *apiextensions.CustomResourceDefinition, newCondition apiextensions.CustomResourceDefinitionCondition) { + newCondition.LastTransitionTime = metav1.NewTime(time.Now()) + + existingCondition := FindCRDCondition(crd, newCondition.Type) + if existingCondition == nil { + crd.Status.Conditions = append(crd.Status.Conditions, newCondition) + return + } + + if existingCondition.Status != newCondition.Status || existingCondition.LastTransitionTime.IsZero() { + existingCondition.LastTransitionTime = newCondition.LastTransitionTime + } + + existingCondition.Status = newCondition.Status + existingCondition.Reason = newCondition.Reason + existingCondition.Message = newCondition.Message +} + +// RemoveCRDCondition removes the status condition. +func RemoveCRDCondition(crd *apiextensions.CustomResourceDefinition, conditionType apiextensions.CustomResourceDefinitionConditionType) { + newConditions := []apiextensions.CustomResourceDefinitionCondition{} + for _, condition := range crd.Status.Conditions { + if condition.Type != conditionType { + newConditions = append(newConditions, condition) + } + } + crd.Status.Conditions = newConditions +} + +// FindCRDCondition returns the condition you're looking for or nil. +func FindCRDCondition(crd *apiextensions.CustomResourceDefinition, conditionType apiextensions.CustomResourceDefinitionConditionType) *apiextensions.CustomResourceDefinitionCondition { + for i := range crd.Status.Conditions { + if crd.Status.Conditions[i].Type == conditionType { + return &crd.Status.Conditions[i] + } + } + + return nil +} + +// IsCRDConditionTrue indicates if the condition is present and strictly true. +func IsCRDConditionTrue(crd *apiextensions.CustomResourceDefinition, conditionType apiextensions.CustomResourceDefinitionConditionType) bool { + return IsCRDConditionPresentAndEqual(crd, conditionType, apiextensions.ConditionTrue) +} + +// IsCRDConditionFalse indicates if the condition is present and false. +func IsCRDConditionFalse(crd *apiextensions.CustomResourceDefinition, conditionType apiextensions.CustomResourceDefinitionConditionType) bool { + return IsCRDConditionPresentAndEqual(crd, conditionType, apiextensions.ConditionFalse) +} + +// IsCRDConditionPresentAndEqual indicates if the condition is present and equal to the given status. +func IsCRDConditionPresentAndEqual(crd *apiextensions.CustomResourceDefinition, conditionType apiextensions.CustomResourceDefinitionConditionType, status apiextensions.ConditionStatus) bool { + for _, condition := range crd.Status.Conditions { + if condition.Type == conditionType { + return condition.Status == status + } + } + return false +} + +// IsCRDConditionEquivalent returns true if the lhs and rhs are equivalent except for times. +func IsCRDConditionEquivalent(lhs, rhs *apiextensions.CustomResourceDefinitionCondition) bool { + if lhs == nil && rhs == nil { + return true + } + if lhs == nil || rhs == nil { + return false + } + + return lhs.Message == rhs.Message && lhs.Reason == rhs.Reason && lhs.Status == rhs.Status && lhs.Type == rhs.Type +} + +// CRDHasFinalizer returns true if the finalizer is in the list. +func CRDHasFinalizer(crd *apiextensions.CustomResourceDefinition, needle string) bool { + for _, finalizer := range crd.Finalizers { + if finalizer == needle { + return true + } + } + + return false +} + +// CRDRemoveFinalizer removes the finalizer if present. +func CRDRemoveFinalizer(crd *apiextensions.CustomResourceDefinition, needle string) { + newFinalizers := []string{} + for _, finalizer := range crd.Finalizers { + if finalizer != needle { + newFinalizers = append(newFinalizers, finalizer) + } + } + crd.Finalizers = newFinalizers +} + +// HasServedCRDVersion returns true if the given version is in the list of CRD's versions and the Served flag is set. +func HasServedCRDVersion(crd *apiextensions.CustomResourceDefinition, version string) bool { + for _, v := range crd.Spec.Versions { + if v.Name == version { + return v.Served + } + } + return false +} + +// GetCRDStorageVersion returns the storage version for given CRD. +func GetCRDStorageVersion(crd *apiextensions.CustomResourceDefinition) (string, error) { + for _, v := range crd.Spec.Versions { + if v.Storage { + return v.Name, nil + } + } + // This should not happened if crd is valid + return "", fmt.Errorf("invalid apiextensions.CustomResourceDefinition, no storage version") +} + +// IsStoredVersion returns whether the given version is the storage version of the CRD. +func IsStoredVersion(crd *apiextensions.CustomResourceDefinition, version string) bool { + for _, v := range crd.Status.StoredVersions { + if version == v { + return true + } + } + return false +} + +// GetSchemaForVersion returns the validation schema for the given version or nil. +func GetSchemaForVersion(crd *apiextensions.CustomResourceDefinition, version string) (*apiextensions.CustomResourceValidation, error) { + for _, v := range crd.Spec.Versions { + if version == v.Name { + return v.Schema, nil + } + } + return nil, fmt.Errorf("version %s not found in apiextensions.CustomResourceDefinition: %v", version, crd.Name) +} + +// GetSubresourcesForVersion returns the subresources for given version or nil. +func GetSubresourcesForVersion(crd *apiextensions.CustomResourceDefinition, version string) (*apiextensions.CustomResourceSubresources, error) { + for _, v := range crd.Spec.Versions { + if version == v.Name { + return v.Subresources, nil + } + } + return nil, fmt.Errorf("version %s not found in apiextensions.CustomResourceDefinition: %v", version, crd.Name) +} + +// 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 +} + +// HasVersionServed returns true if given CRD has given version served. +func HasVersionServed(crd *apiextensions.CustomResourceDefinition, version string) bool { + for _, v := range crd.Spec.Versions { + if !v.Served || v.Name != version { + continue + } + return true + } + return false +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apihelpers/helpers_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apihelpers/helpers_test.go index 7e12f7ec572..1b8fef9ae98 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apihelpers/helpers_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apihelpers/helpers_test.go @@ -17,9 +17,12 @@ limitations under the License. package apihelpers import ( + "reflect" "testing" + "time" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestIsProtectedCommunityGroup(t *testing.T) { @@ -76,22 +79,22 @@ func TestGetAPIApprovalState(t *testing.T) { }{ { name: "bare unapproved", - annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "unapproved"}, + annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "unapproved"}, expected: APIApprovalBypassed, }, { name: "unapproved with message", - annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "unapproved, experimental-only"}, + annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "unapproved, experimental-only"}, expected: APIApprovalBypassed, }, { name: "mismatched case", - annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "Unapproved"}, + annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "Unapproved"}, expected: APIApprovalInvalid, }, { name: "empty", - annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: ""}, + annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: ""}, expected: APIApprovalMissing, }, { @@ -101,27 +104,27 @@ func TestGetAPIApprovalState(t *testing.T) { }, { name: "url", - annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "https://github.com/kubernetes/kubernetes/pull/78458"}, + annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "https://github.com/kubernetes/kubernetes/pull/78458"}, expected: APIApproved, }, { name: "url - no scheme", - annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "github.com/kubernetes/kubernetes/pull/78458"}, + annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "github.com/kubernetes/kubernetes/pull/78458"}, expected: APIApprovalInvalid, }, { name: "url - no host", - annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "http:///kubernetes/kubernetes/pull/78458"}, + annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "http:///kubernetes/kubernetes/pull/78458"}, expected: APIApprovalInvalid, }, { name: "url - just path", - annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "/"}, + annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "/"}, expected: APIApprovalInvalid, }, { name: "missing scheme", - annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "github.com/kubernetes/kubernetes/pull/78458"}, + annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "github.com/kubernetes/kubernetes/pull/78458"}, expected: APIApprovalInvalid, }, } @@ -136,3 +139,461 @@ func TestGetAPIApprovalState(t *testing.T) { }) } } + +func TestCRDHasFinalizer(t *testing.T) { + tests := []struct { + name string + crd *apiextensions.CustomResourceDefinition + finalizerToCheck string + + expected bool + }{ + { + name: "missing", + crd: &apiextensions.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it"}}, + }, + finalizerToCheck: "it", + expected: false, + }, + { + name: "present", + crd: &apiextensions.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 *apiextensions.CustomResourceDefinition + finalizerToCheck string + + expected []string + }{ + { + name: "missing", + crd: &apiextensions.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it"}}, + }, + finalizerToCheck: "it", + expected: []string{"not-it"}, + }, + { + name: "present", + crd: &apiextensions.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 []apiextensions.CustomResourceDefinitionCondition + newCondition apiextensions.CustomResourceDefinitionCondition + expectedcrdCondition []apiextensions.CustomResourceDefinitionCondition + }{ + { + name: "test setCRDcondition when one condition", + crdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + newCondition: apiextensions.CustomResourceDefinitionCondition{ + Type: apiextensions.Established, + Status: apiextensions.ConditionFalse, + Reason: "NotAccepted", + Message: "Not accepted", + LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC), + }, + expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.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: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Type: apiextensions.NamesAccepted, + Status: apiextensions.ConditionTrue, + Reason: "NoConflicts", + Message: "no conflicts found", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + newCondition: apiextensions.CustomResourceDefinitionCondition{ + Type: apiextensions.NamesAccepted, + Status: apiextensions.ConditionFalse, + Reason: "Conflicts", + Message: "conflicts found", + LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC), + }, + expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Type: apiextensions.NamesAccepted, + Status: apiextensions.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: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + newCondition: apiextensions.CustomResourceDefinitionCondition{ + Type: apiextensions.Terminating, + Status: apiextensions.ConditionFalse, + Reason: "Neverapiextensions.Established", + Message: "resource was never apiextensions.Established", + LastTransitionTime: metav1.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC), + }, + expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Type: apiextensions.Terminating, + Status: apiextensions.ConditionFalse, + Reason: "Neverapiextensions.Established", + Message: "resource was never apiextensions.Established", + LastTransitionTime: metav1.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC), + }, + }, + }, + { + name: "set new condition which doesn't have lastTransitionTime set", + crdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + newCondition: apiextensions.CustomResourceDefinitionCondition{ + Type: apiextensions.Established, + Status: apiextensions.ConditionFalse, + Reason: "NotAccepted", + Message: "Not accepted", + }, + expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionFalse, + Reason: "NotAccepted", + Message: "Not accepted", + LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC), + }, + }, + }, + { + name: "append new condition which doesn't have lastTransitionTime set", + crdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + newCondition: apiextensions.CustomResourceDefinitionCondition{ + Type: apiextensions.Terminating, + Status: apiextensions.ConditionFalse, + Reason: "Neverapiextensions.Established", + Message: "resource was never apiextensions.Established", + }, + expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Type: apiextensions.Terminating, + Status: apiextensions.ConditionFalse, + Reason: "Neverapiextensions.Established", + Message: "resource was never apiextensions.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[i], crd.Status.Conditions[i]) + } + if crd.Status.Conditions[i].LastTransitionTime.IsZero() { + t.Errorf("%q/%d lastTransitionTime should not be null: %v", tc.name, i, crd.Status.Conditions[i]) + } + } + } +} + +func TestRemoveCRDCondition(t *testing.T) { + tests := []struct { + name string + crdCondition []apiextensions.CustomResourceDefinitionCondition + conditionType apiextensions.CustomResourceDefinitionConditionType + expectedcrdCondition []apiextensions.CustomResourceDefinitionCondition + }{ + { + name: "test remove CRDCondition when the conditionType meets", + crdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Type: apiextensions.NamesAccepted, + Status: apiextensions.ConditionTrue, + Reason: "NoConflicts", + Message: "no conflicts found", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + conditionType: apiextensions.NamesAccepted, + expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.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: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Type: apiextensions.NamesAccepted, + Status: apiextensions.ConditionTrue, + Reason: "NoConflicts", + Message: "no conflicts found", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + conditionType: apiextensions.Terminating, + expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Type: apiextensions.NamesAccepted, + Status: apiextensions.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 []apiextensions.CustomResourceDefinitionCondition + conditionType apiextensions.CustomResourceDefinitionConditionType + status apiextensions.ConditionStatus + expectresult bool + }{ + { + name: "test CRDCondition is not Present", + crdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Type: apiextensions.NamesAccepted, + Status: apiextensions.ConditionTrue, + Reason: "NoConflicts", + Message: "no conflicts found", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + conditionType: apiextensions.Terminating, + status: apiextensions.ConditionTrue, + expectresult: false, + }, + { + name: "test CRDCondition is Present but not Equal", + crdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Type: apiextensions.NamesAccepted, + Status: apiextensions.ConditionTrue, + Reason: "NoConflicts", + Message: "no conflicts found", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + conditionType: apiextensions.Established, + status: apiextensions.ConditionFalse, + expectresult: false, + }, + { + name: "test CRDCondition is Present and Equal", + crdCondition: []apiextensions.CustomResourceDefinitionCondition{ + { + Type: apiextensions.Established, + Status: apiextensions.ConditionTrue, + Reason: "Accepted", + Message: "the initial names have been accepted", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Type: apiextensions.NamesAccepted, + Status: apiextensions.ConditionTrue, + Reason: "NoConflicts", + Message: "no conflicts found", + LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + conditionType: apiextensions.NamesAccepted, + status: apiextensions.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 []apiextensions.CustomResourceDefinitionCondition) *apiextensions.CustomResourceDefinition { + testCRDObjectMeta := metav1.ObjectMeta{ + Name: "plural.group.com", + ResourceVersion: "12", + } + testCRDSpec := apiextensions.CustomResourceDefinitionSpec{ + Group: "group.com", + Names: apiextensions.CustomResourceDefinitionNames{ + Plural: "plural", + Singular: "singular", + Kind: "kind", + ListKind: "listkind", + }, + } + testCRDAcceptedNames := apiextensions.CustomResourceDefinitionNames{ + Plural: "plural", + Singular: "singular", + Kind: "kind", + ListKind: "listkind", + } + return &apiextensions.CustomResourceDefinition{ + ObjectMeta: testCRDObjectMeta, + Spec: testCRDSpec, + Status: apiextensions.CustomResourceDefinitionStatus{ + AcceptedNames: testCRDAcceptedNames, + Conditions: conditions, + }, + } +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go index 89b9060e1b7..84066786776 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go @@ -25,10 +25,10 @@ import ( "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" _ "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset" _ "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions" - internalinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion" + externalinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions" "k8s.io/apiextensions-apiserver/pkg/controller/apiapproval" "k8s.io/apiextensions-apiserver/pkg/controller/establish" "k8s.io/apiextensions-apiserver/pkg/controller/finalizer" @@ -109,7 +109,7 @@ type CustomResourceDefinitions struct { GenericAPIServer *genericapiserver.GenericAPIServer // provided for easier embedding - Informers internalinformers.SharedInformerFactory + Informers externalinformers.SharedInformerFactory } // Complete fills in any fields not set that are required to have valid data. It's mutating the receiver. @@ -164,13 +164,13 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) return nil, err } - crdClient, err := internalclientset.NewForConfig(s.GenericAPIServer.LoopbackClientConfig) + crdClient, err := clientset.NewForConfig(s.GenericAPIServer.LoopbackClientConfig) if err != nil { // it's really bad that this is leaking here, but until we can fix the test (which I'm pretty sure isn't even testing what it wants to test), // we need to be able to move forward return nil, fmt.Errorf("failed to create clientset: %v", err) } - s.Informers = internalinformers.NewSharedInformerFactory(crdClient, 5*time.Minute) + s.Informers = externalinformers.NewSharedInformerFactory(crdClient, 5*time.Minute) delegateHandler := delegationTarget.UnprotectedHandler() if delegateHandler == nil { @@ -185,11 +185,11 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) discovery: map[string]*discovery.APIGroupHandler{}, delegate: delegateHandler, } - establishingController := establish.NewEstablishingController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions()) + establishingController := establish.NewEstablishingController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1()) crdHandler, err := NewCustomResourceDefinitionHandler( versionDiscoveryHandler, groupDiscoveryHandler, - s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), + s.Informers.Apiextensions().V1().CustomResourceDefinitions(), delegateHandler, c.ExtraConfig.CRDRESTOptionsGetter, c.GenericConfig.AdmissionControl, @@ -209,18 +209,18 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/apis", crdHandler) s.GenericAPIServer.Handler.NonGoRestfulMux.HandlePrefix("/apis/", crdHandler) - crdController := NewDiscoveryController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), versionDiscoveryHandler, groupDiscoveryHandler) - namingController := status.NewNamingConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions()) - nonStructuralSchemaController := nonstructuralschema.NewConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions()) - apiApprovalController := apiapproval.NewKubernetesAPIApprovalPolicyConformantConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions()) + crdController := NewDiscoveryController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), versionDiscoveryHandler, groupDiscoveryHandler) + namingController := status.NewNamingConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1()) + nonStructuralSchemaController := nonstructuralschema.NewConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1()) + apiApprovalController := apiapproval.NewKubernetesAPIApprovalPolicyConformantConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1()) finalizingController := finalizer.NewCRDFinalizer( - s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), - crdClient.Apiextensions(), + s.Informers.Apiextensions().V1().CustomResourceDefinitions(), + crdClient.ApiextensionsV1(), crdHandler, ) var openapiController *openapicontroller.Controller if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourcePublishOpenAPI) { - openapiController = openapicontroller.NewController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions()) + openapiController = openapicontroller.NewController(s.Informers.Apiextensions().V1().CustomResourceDefinitions()) } s.GenericAPIServer.AddPostStartHookOrDie("start-apiextensions-informers", func(context genericapiserver.PostStartHookContext) error { @@ -249,7 +249,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) // but we won't go healthy until we can handle the ones already present. s.GenericAPIServer.AddPostStartHookOrDie("crd-informer-synced", func(context genericapiserver.PostStartHookContext) error { return wait.PollImmediateUntil(100*time.Millisecond, func() (bool, error) { - return s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions().Informer().HasSynced(), nil + return s.Informers.Apiextensions().V1().CustomResourceDefinitions().Informer().HasSynced(), nil }, context.StopCh) }) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/converter.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/converter.go index d9827ca4cb8..625c7ae20c4 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/converter.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/converter.go @@ -20,7 +20,7 @@ import ( "fmt" autoscalingv1 "k8s.io/api/autoscaling/v1" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -84,7 +84,6 @@ func (m *CRConverterFactory) NewConverter(crd *apiextensions.CustomResourceDefin // Determine whether we should expect to be asked to "convert" autoscaling/v1 Scale types convertScale := false if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) { - convertScale = crd.Spec.Subresources != nil && crd.Spec.Subresources.Scale != nil for _, version := range crd.Spec.Versions { if version.Subresources != nil && version.Subresources.Scale != nil { convertScale = true diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/converter_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/converter_test.go index 933b1c037cf..1b5ebd3e0e6 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/converter_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/converter_test.go @@ -21,7 +21,7 @@ import ( "strings" "testing" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/webhook_converter.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/webhook_converter.go index 8013e89767b..0cdf39a5ec4 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/webhook_converter.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/webhook_converter.go @@ -22,7 +22,7 @@ import ( "fmt" "time" - internal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apivalidation "k8s.io/apimachinery/pkg/api/validation" @@ -73,8 +73,8 @@ type webhookConverter struct { conversionReviewVersions []string } -func webhookClientConfigForCRD(crd *internal.CustomResourceDefinition) *webhook.ClientConfig { - apiConfig := crd.Spec.Conversion.WebhookClientConfig +func webhookClientConfigForCRD(crd *apiextensions.CustomResourceDefinition) *webhook.ClientConfig { + apiConfig := crd.Spec.Conversion.Webhook.ClientConfig ret := webhook.ClientConfig{ Name: fmt.Sprintf("conversion_webhook_for_%s", crd.Name), CABundle: apiConfig.CABundle, @@ -86,7 +86,7 @@ func webhookClientConfigForCRD(crd *internal.CustomResourceDefinition) *webhook. ret.Service = &webhook.ClientConfigService{ Name: apiConfig.Service.Name, Namespace: apiConfig.Service.Namespace, - Port: apiConfig.Service.Port, + Port: *apiConfig.Service.Port, } if apiConfig.Service.Path != nil { ret.Service.Path = *apiConfig.Service.Path @@ -97,7 +97,7 @@ func webhookClientConfigForCRD(crd *internal.CustomResourceDefinition) *webhook. var _ crConverterInterface = &webhookConverter{} -func (f *webhookConverterFactory) NewWebhookConverter(crd *internal.CustomResourceDefinition) (*webhookConverter, error) { +func (f *webhookConverterFactory) NewWebhookConverter(crd *apiextensions.CustomResourceDefinition) (*webhookConverter, error) { restClient, err := f.clientManager.HookClient(*webhookClientConfigForCRD(crd)) if err != nil { return nil, err @@ -108,7 +108,7 @@ func (f *webhookConverterFactory) NewWebhookConverter(crd *internal.CustomResour name: crd.Name, nopConverter: nopConverter{}, - conversionReviewVersions: crd.Spec.Conversion.ConversionReviewVersions, + conversionReviewVersions: crd.Spec.Conversion.Webhook.ConversionReviewVersions, }, nil } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go index f1592444a4c..8d6030eb4b8 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go @@ -34,9 +34,10 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" - listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1" + listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" ) type DiscoveryController struct { @@ -86,7 +87,7 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error { foundVersion := false foundGroup := false for _, crd := range crds { - if !apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) { + if !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Established) { continue } @@ -126,7 +127,7 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error { verbs := metav1.Verbs([]string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"}) // if we're terminating we don't allow some verbs - if apiextensions.IsCRDConditionTrue(crd, apiextensions.Terminating) { + if apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Terminating) { verbs = metav1.Verbs([]string{"delete", "deletecollection", "get", "list", "watch"}) } @@ -141,7 +142,7 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error { StorageVersionHash: storageVersionHash, }) - subresources, err := apiextensions.GetSubresourcesForVersion(crd, version.Version) + subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, version.Version) if err != nil { return err } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go index 4e15edd708d..ef701fece09 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go @@ -29,15 +29,17 @@ import ( "github.com/go-openapi/strfmt" "github.com/go-openapi/validate" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" + apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/apiserver/conversion" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting" schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta" structuralpruning "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning" apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation" - informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" - listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1" + listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/controller/establish" "k8s.io/apiextensions-apiserver/pkg/controller/finalizer" "k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder" @@ -287,7 +289,7 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - if !apiextensions.HasServedCRDVersion(crd, requestInfo.APIVersion) { + if !apiextensionshelpers.HasServedCRDVersion(crd, requestInfo.APIVersion) { r.delegate.ServeHTTP(w, req) return } @@ -296,13 +298,13 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // but it becomes "unserved" because another names update leads to a conflict // and EstablishingController wasn't fast enough to put the CRD into the Established condition. // We accept this as the problem is small and self-healing. - if !apiextensions.IsCRDConditionTrue(crd, apiextensions.NamesAccepted) && - !apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) { + if !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.NamesAccepted) && + !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Established) { r.delegate.ServeHTTP(w, req) return } - terminating := apiextensions.IsCRDConditionTrue(crd, apiextensions.Terminating) + terminating := apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Terminating) crdInfo, err := r.getOrCreateServingInfoFor(crd.UID, crd.Name) if apierrors.IsNotFound(err) { @@ -335,7 +337,7 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { } var handlerFunc http.HandlerFunc - subresources, err := apiextensions.GetSubresourcesForVersion(crd, requestInfo.APIVersion) + subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, requestInfo.APIVersion) if err != nil { utilruntime.HandleError(err) responsewriters.ErrorNegotiated( @@ -467,8 +469,8 @@ func (r *crdHandler) updateCustomResourceDefinition(oldObj, newObj interface{}) // For HA clusters, we want to prevent race conditions when changing status to Established, // so we want to be sure that CRD is Installing at least for 5 seconds before Establishing it. // TODO: find a real HA safe checkpointing mechanism instead of an arbitrary wait. - if !apiextensions.IsCRDConditionTrue(newCRD, apiextensions.Established) && - apiextensions.IsCRDConditionTrue(newCRD, apiextensions.NamesAccepted) { + if !apiextensionshelpers.IsCRDConditionTrue(newCRD, apiextensions.Established) && + apiextensionshelpers.IsCRDConditionTrue(newCRD, apiextensions.NamesAccepted) { if r.masterCount > 1 { r.establishingController.QueueCRD(newCRD.Name, 5*time.Second) } else { @@ -607,7 +609,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd return ret, nil } - storageVersion, err := apiextensions.GetCRDStorageVersion(crd) + storageVersion, err := apiextensionshelpers.GetCRDStorageVersion(crd) if err != nil { return nil, err } @@ -622,7 +624,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd structuralSchemas := map[string]*structuralschema.Structural{} for _, v := range crd.Spec.Versions { - val, err := apiextensions.GetSchemaForVersion(crd, v.Name) + val, err := apiextensionshelpers.GetSchemaForVersion(crd, v.Name) if err != nil { utilruntime.HandleError(err) return nil, fmt.Errorf("the server could not properly serve the CR schema") @@ -630,14 +632,18 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd if val == nil { continue } - s, err := structuralschema.NewStructural(val.OpenAPIV3Schema) - if *crd.Spec.PreserveUnknownFields == false && err != nil { + internalValidation := &apiextensionsinternal.CustomResourceValidation{} + if err := apiextensions.Convert_v1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(val, internalValidation, nil); err != nil { + return nil, fmt.Errorf("failed converting CRD validation to internal version: %v", err) + } + s, err := structuralschema.NewStructural(internalValidation.OpenAPIV3Schema) + if crd.Spec.PreserveUnknownFields == false && err != nil { // This should never happen. If it does, it is a programming error. utilruntime.HandleError(fmt.Errorf("failed to convert schema to structural: %v", err)) return nil, fmt.Errorf("the server could not properly serve the CR schema") // validation should avoid this } - if *crd.Spec.PreserveUnknownFields == false { + if crd.Spec.PreserveUnknownFields == false { // we don't own s completely, e.g. defaults are not deep-copied. So better make a copy here. s = s.DeepCopy() @@ -679,36 +685,39 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd typer := newUnstructuredObjectTyper(parameterScheme) creator := unstructuredCreator{} - validationSchema, err := apiextensions.GetSchemaForVersion(crd, v.Name) + validationSchema, err := apiextensionshelpers.GetSchemaForVersion(crd, v.Name) if err != nil { utilruntime.HandleError(err) return nil, fmt.Errorf("the server could not properly serve the CR schema") } - validator, _, err := apiservervalidation.NewSchemaValidator(validationSchema) + var internalValidationSchema *apiextensionsinternal.CustomResourceValidation + if validationSchema != nil { + internalValidationSchema = &apiextensionsinternal.CustomResourceValidation{} + if err := apiextensions.Convert_v1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(validationSchema, internalValidationSchema, nil); err != nil { + return nil, fmt.Errorf("failed to convert CRD validation to internal version: %v", err) + } + } + validator, _, err := apiservervalidation.NewSchemaValidator(internalValidationSchema) if err != nil { return nil, err } - // Check for nil because we dereference this throughout the handler code. - // Note: we always default this to non-nil. But we should guard these dereferences any way. - if crd.Spec.PreserveUnknownFields == nil { - return nil, fmt.Errorf("unexpected nil spec.preserveUnknownFields in the CustomResourceDefinition") - } - - var statusSpec *apiextensions.CustomResourceSubresourceStatus + var statusSpec *apiextensionsinternal.CustomResourceSubresourceStatus var statusValidator *validate.SchemaValidator - subresources, err := apiextensions.GetSubresourcesForVersion(crd, v.Name) + subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, v.Name) if err != nil { utilruntime.HandleError(err) return nil, fmt.Errorf("the server could not properly serve the CR subresources") } if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Status != nil { equivalentResourceRegistry.RegisterKindFor(resource, "status", kind) - - statusSpec = subresources.Status + statusSpec = &apiextensionsinternal.CustomResourceSubresourceStatus{} + if err := apiextensions.Convert_v1_CustomResourceSubresourceStatus_To_apiextensions_CustomResourceSubresourceStatus(subresources.Status, statusSpec, nil); err != nil { + return nil, fmt.Errorf("failed converting CRD status subresource to internal version: %v", err) + } // for the status subresource, validate only against the status schema - if validationSchema != nil && validationSchema.OpenAPIV3Schema != nil && validationSchema.OpenAPIV3Schema.Properties != nil { - if statusSchema, ok := validationSchema.OpenAPIV3Schema.Properties["status"]; ok { + if internalValidationSchema != nil && internalValidationSchema.OpenAPIV3Schema != nil && internalValidationSchema.OpenAPIV3Schema.Properties != nil { + if statusSchema, ok := internalValidationSchema.OpenAPIV3Schema.Properties["status"]; ok { openapiSchema := &spec.Schema{} if err := apiservervalidation.ConvertJSONSchemaPropsWithPostProcess(&statusSchema, openapiSchema, apiservervalidation.StripUnsupportedFormatsPostProcess); err != nil { return nil, err @@ -718,14 +727,16 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd } } - var scaleSpec *apiextensions.CustomResourceSubresourceScale + var scaleSpec *apiextensionsinternal.CustomResourceSubresourceScale if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Scale != nil { equivalentResourceRegistry.RegisterKindFor(resource, "scale", autoscalingv1.SchemeGroupVersion.WithKind("Scale")) - - scaleSpec = subresources.Scale + scaleSpec = &apiextensionsinternal.CustomResourceSubresourceScale{} + if err := apiextensions.Convert_v1_CustomResourceSubresourceScale_To_apiextensions_CustomResourceSubresourceScale(subresources.Scale, scaleSpec, nil); err != nil { + return nil, fmt.Errorf("failed converting CRD status subresource to internal version: %v", err) + } } - columns, err := apiextensions.GetColumnsForVersion(crd, v.Name) + columns, err := getColumnsForVersion(crd, v.Name) if err != nil { utilruntime.HandleError(err) return nil, fmt.Errorf("the server could not properly serve the CR columns") @@ -756,7 +767,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd encoderVersion: schema.GroupVersion{Group: crd.Spec.Group, Version: storageVersion}, structuralSchemas: structuralSchemas, structuralSchemaGK: kind.GroupKind(), - preserveUnknownFields: *crd.Spec.PreserveUnknownFields, + preserveUnknownFields: crd.Spec.PreserveUnknownFields, }, crd.Status.AcceptedNames.Categories, table, @@ -779,7 +790,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd converter: safeConverter, structuralSchemas: structuralSchemas, structuralSchemaGK: kind.GroupKind(), - preserveUnknownFields: *crd.Spec.PreserveUnknownFields, + preserveUnknownFields: crd.Spec.PreserveUnknownFields, } var standardSerializers []runtime.SerializerInfo for _, s := range negotiatedSerializer.SupportedMediaTypes() { @@ -830,7 +841,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd reqScope.Creater, reqScope.Kind, reqScope.HubGroupVersion, - *crd.Spec.PreserveUnknownFields, + crd.Spec.PreserveUnknownFields, ) if err != nil { return nil, err diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler_test.go index 9d1db5d27f3..951d02d6278 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler_test.go @@ -25,9 +25,9 @@ import ( "sigs.k8s.io/yaml" "testing" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/apiserver/conversion" - listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go new file mode 100644 index 00000000000..83ae312726f --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go @@ -0,0 +1,51 @@ +/* +Copyright 2019 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 apiserver + +import ( + "fmt" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc() + +// getColumnsForVersion returns the columns for given version or nil. +// NOTE: the newly logically-defaulted columns is not pointing to the original CRD object. +// One cannot mutate the original CRD columns using the logically-defaulted columns. Please iterate through +// the original CRD object instead. +func getColumnsForVersion(crd *apiextensionsv1.CustomResourceDefinition, version string) ([]apiextensionsv1.CustomResourceColumnDefinition, error) { + for _, v := range crd.Spec.Versions { + if version == v.Name { + return serveDefaultColumnsIfEmpty(v.AdditionalPrinterColumns), nil + } + } + return nil, fmt.Errorf("version %s not found in apiextensionsv1.CustomResourceDefinition: %v", version, crd.Name) +} + +// serveDefaultColumnsIfEmpty applies logically defaulting to columns, if the input columns is empty. +// NOTE: in this way, the newly logically-defaulted columns is not pointing to the original CRD object. +// One cannot mutate the original CRD columns using the logically-defaulted columns. Please iterate through +// the original CRD object instead. +func serveDefaultColumnsIfEmpty(columns []apiextensionsv1.CustomResourceColumnDefinition) []apiextensionsv1.CustomResourceColumnDefinition { + if len(columns) > 0 { + return columns + } + return []apiextensionsv1.CustomResourceColumnDefinition{ + {Name: "Age", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"], JSONPath: ".metadata.creationTimestamp"}, + } +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation_test.go index 54361461ede..2250c32c4e8 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation_test.go @@ -24,7 +24,7 @@ import ( "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" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime" @@ -43,7 +43,7 @@ func TestRoundTrip(t *testing.T) { if err := apiextensions.AddToScheme(scheme); err != nil { t.Fatal(err) } - if err := apiextensionsv1beta1.AddToScheme(scheme); err != nil { + if err := apiextensionsv1.AddToScheme(scheme); err != nil { t.Fatal(err) } @@ -81,7 +81,7 @@ func TestRoundTrip(t *testing.T) { } // JSON -> external - external := &apiextensionsv1beta1.JSONSchemaProps{} + external := &apiextensionsv1.JSONSchemaProps{} if err := json.Unmarshal(openAPIJSON, external); err != nil { t.Fatal(err) } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/apiapproval/apiapproval_controller.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/apiapproval/apiapproval_controller.go index 599f3d38f86..b2c860c8d43 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/apiapproval/apiapproval_controller.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/apiapproval/apiapproval_controller.go @@ -22,11 +22,10 @@ import ( "time" "k8s.io/apiextensions-apiserver/pkg/apihelpers" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion" - informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" - listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1" + listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" @@ -133,7 +132,7 @@ func (c *KubernetesAPIApprovalPolicyConformantConditionController) sync(key stri } // avoid repeated calculation for the same annotation - protectionAnnotationValue := inCustomResourceDefinition.Annotations[v1beta1.KubeAPIApprovedAnnotation] + protectionAnnotationValue := inCustomResourceDefinition.Annotations[apiextensions.KubeAPIApprovedAnnotation] c.lastSeenProtectedAnnotationLock.Lock() lastSeen, seenBefore := c.lastSeenProtectedAnnotation[inCustomResourceDefinition.Name] c.lastSeenProtectedAnnotationLock.Unlock() @@ -147,7 +146,7 @@ func (c *KubernetesAPIApprovalPolicyConformantConditionController) sync(key stri // because group is immutable, if we have no condition now, we have no need to remove a condition. return nil } - old := apiextensions.FindCRDCondition(inCustomResourceDefinition, apiextensions.KubernetesAPIApprovalPolicyConformant) + old := apihelpers.FindCRDCondition(inCustomResourceDefinition, apiextensions.KubernetesAPIApprovalPolicyConformant) // don't attempt a write if all the condition details are the same if old != nil && old.Status == cond.Status && old.Reason == cond.Reason && old.Message == cond.Message { @@ -157,7 +156,7 @@ func (c *KubernetesAPIApprovalPolicyConformantConditionController) sync(key stri // update condition crd := inCustomResourceDefinition.DeepCopy() - apiextensions.SetCRDCondition(crd, *cond) + apihelpers.SetCRDCondition(crd, *cond) _, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd) if apierrors.IsNotFound(err) || apierrors.IsConflict(err) { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/apiapproval/apiapproval_controller_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/apiapproval/apiapproval_controller_test.go index 49fa7a9fc08..387ea9b8729 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/apiapproval/apiapproval_controller_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/apiapproval/apiapproval_controller_test.go @@ -19,8 +19,7 @@ package apiapproval import ( "testing" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -89,7 +88,7 @@ func TestCalculateCondition(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { crd := &apiextensions.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: test.annotationValue}}, + ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: test.annotationValue}}, Spec: apiextensions.CustomResourceDefinitionSpec{ Group: test.group, }, diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/establish/establishing_controller.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/establish/establishing_controller.go index 6f61fe753a2..37c22791f9b 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/establish/establishing_controller.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/establish/establishing_controller.go @@ -27,10 +27,11 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/klog" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion" - informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" - listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1" + listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" ) // EstablishingController controls how and when CRD is established. @@ -119,8 +120,8 @@ func (ec *EstablishingController) sync(key string) error { return err } - if !apiextensions.IsCRDConditionTrue(cachedCRD, apiextensions.NamesAccepted) || - apiextensions.IsCRDConditionTrue(cachedCRD, apiextensions.Established) { + if !apiextensionshelpers.IsCRDConditionTrue(cachedCRD, apiextensions.NamesAccepted) || + apiextensionshelpers.IsCRDConditionTrue(cachedCRD, apiextensions.Established) { return nil } @@ -131,7 +132,7 @@ func (ec *EstablishingController) sync(key string) error { Reason: "InitialNamesAccepted", Message: "the initial names have been accepted", } - apiextensions.SetCRDCondition(crd, establishedCondition) + apiextensionshelpers.SetCRDCondition(crd, establishedCondition) // Update server with new CRD condition. _, err = ec.crdClient.CustomResourceDefinitions().UpdateStatus(crd) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer/crd_finalizer.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer/crd_finalizer.go index 81330540d7c..f3b1a43f954 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer/crd_finalizer.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer/crd_finalizer.go @@ -36,10 +36,11 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion" - informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" - listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1" + listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" ) // OverlappingBuiltInResources returns the set of built-in group/resources that are persisted @@ -113,14 +114,14 @@ func (c *CRDFinalizer) sync(key string) error { } // no work to do - if cachedCRD.DeletionTimestamp.IsZero() || !apiextensions.CRDHasFinalizer(cachedCRD, apiextensions.CustomResourceCleanupFinalizer) { + if cachedCRD.DeletionTimestamp.IsZero() || !apiextensionshelpers.CRDHasFinalizer(cachedCRD, apiextensions.CustomResourceCleanupFinalizer) { return nil } crd := cachedCRD.DeepCopy() // update the status condition. This cleanup could take a while. - apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ + apiextensionshelpers.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ Type: apiextensions.Terminating, Status: apiextensions.ConditionTrue, Reason: "InstanceDeletionInProgress", @@ -139,15 +140,15 @@ func (c *CRDFinalizer) sync(key string) error { // Since we control the endpoints, we know that delete collection works. No need to delete if not established. if OverlappingBuiltInResources()[schema.GroupResource{Group: crd.Spec.Group, Resource: crd.Spec.Names.Plural}] { // Skip deletion, explain why, and proceed to remove the finalizer and delete the CRD - apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ + apiextensionshelpers.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ Type: apiextensions.Terminating, Status: apiextensions.ConditionFalse, Reason: "OverlappingBuiltInResource", Message: "instances overlap with built-in resources in storage", }) - } else if apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) { + } else if apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Established) { cond, deleteErr := c.deleteInstances(crd) - apiextensions.SetCRDCondition(crd, cond) + apiextensionshelpers.SetCRDCondition(crd, cond) if deleteErr != nil { if _, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd); err != nil { utilruntime.HandleError(err) @@ -155,7 +156,7 @@ func (c *CRDFinalizer) sync(key string) error { return deleteErr } } else { - apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ + apiextensionshelpers.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ Type: apiextensions.Terminating, Status: apiextensions.ConditionFalse, Reason: "NeverEstablished", @@ -163,7 +164,7 @@ func (c *CRDFinalizer) sync(key string) error { }) } - apiextensions.CRDRemoveFinalizer(crd, apiextensions.CustomResourceCleanupFinalizer) + apiextensionshelpers.CRDRemoveFinalizer(crd, apiextensions.CustomResourceCleanupFinalizer) _, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd) if apierrors.IsNotFound(err) || apierrors.IsConflict(err) { // deleted or changed in the meantime, we'll get called again @@ -312,7 +313,7 @@ func (c *CRDFinalizer) enqueue(obj *apiextensions.CustomResourceDefinition) { func (c *CRDFinalizer) addCustomResourceDefinition(obj interface{}) { castObj := obj.(*apiextensions.CustomResourceDefinition) // only queue deleted things - if !castObj.DeletionTimestamp.IsZero() && apiextensions.CRDHasFinalizer(castObj, apiextensions.CustomResourceCleanupFinalizer) { + if !castObj.DeletionTimestamp.IsZero() && apiextensionshelpers.CRDHasFinalizer(castObj, apiextensions.CustomResourceCleanupFinalizer) { c.enqueue(castObj) } } @@ -321,7 +322,7 @@ func (c *CRDFinalizer) updateCustomResourceDefinition(oldObj, newObj interface{} oldCRD := oldObj.(*apiextensions.CustomResourceDefinition) newCRD := newObj.(*apiextensions.CustomResourceDefinition) // only queue deleted things that haven't been finalized by us - if newCRD.DeletionTimestamp.IsZero() || !apiextensions.CRDHasFinalizer(newCRD, apiextensions.CustomResourceCleanupFinalizer) { + if newCRD.DeletionTimestamp.IsZero() || !apiextensionshelpers.CRDHasFinalizer(newCRD, apiextensions.CustomResourceCleanupFinalizer) { return } @@ -339,8 +340,8 @@ func (c *CRDFinalizer) updateCustomResourceDefinition(oldObj, newObj interface{} newCopy := newCRD.DeepCopy() oldCopy.ResourceVersion = "" newCopy.ResourceVersion = "" - apiextensions.RemoveCRDCondition(oldCopy, apiextensions.Terminating) - apiextensions.RemoveCRDCondition(newCopy, apiextensions.Terminating) + apiextensionshelpers.RemoveCRDCondition(oldCopy, apiextensions.Terminating) + apiextensionshelpers.RemoveCRDCondition(newCopy, apiextensions.Terminating) if !reflect.DeepEqual(oldCopy, newCopy) { c.enqueue(newCRD) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema/nonstructuralschema_controller.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema/nonstructuralschema_controller.go index b6d64a786e3..18fe3a22a48 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema/nonstructuralschema_controller.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema/nonstructuralschema_controller.go @@ -30,11 +30,13 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/klog" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" + apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" - client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion" - informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" - listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1" + listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" ) // ConditionController is maintaining the NonStructuralSchema condition. @@ -87,25 +89,17 @@ func calculateCondition(in *apiextensions.CustomResourceDefinition) *apiextensio allErrs := field.ErrorList{} - if in.Spec.Validation != nil && in.Spec.Validation.OpenAPIV3Schema != nil { - s, err := schema.NewStructural(in.Spec.Validation.OpenAPIV3Schema) - if err != nil { - cond.Reason = "StructuralError" - cond.Message = fmt.Sprintf("failed to check global validation schema: %v", err) - return cond - } - - pth := field.NewPath("spec", "validation", "openAPIV3Schema") - - allErrs = append(allErrs, schema.ValidateStructural(pth, s)...) - } - for i, v := range in.Spec.Versions { if v.Schema == nil || v.Schema.OpenAPIV3Schema == nil { continue } - s, err := schema.NewStructural(v.Schema.OpenAPIV3Schema) + internalSchema := &apiextensionsinternal.CustomResourceValidation{} + if err := apiextensions.Convert_v1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(v.Schema, internalSchema, nil); err != nil { + klog.Errorf("failed to convert CRD validation to internal version: %v", err) + continue + } + s, err := schema.NewStructural(internalSchema.OpenAPIV3Schema) if err != nil { cond.Reason = "StructuralError" cond.Message = fmt.Sprintf("failed to check validation schema for version %s: %v", v.Name, err) @@ -147,7 +141,7 @@ func (c *ConditionController) sync(key string) error { // check old condition cond := calculateCondition(inCustomResourceDefinition) - old := apiextensions.FindCRDCondition(inCustomResourceDefinition, apiextensions.NonStructuralSchema) + old := apiextensionshelpers.FindCRDCondition(inCustomResourceDefinition, apiextensions.NonStructuralSchema) if cond == nil && old == nil { return nil @@ -159,10 +153,10 @@ func (c *ConditionController) sync(key string) error { // update condition crd := inCustomResourceDefinition.DeepCopy() if cond == nil { - apiextensions.RemoveCRDCondition(crd, apiextensions.NonStructuralSchema) + apiextensionshelpers.RemoveCRDCondition(crd, apiextensions.NonStructuralSchema) } else { cond.LastTransitionTime = metav1.NewTime(time.Now()) - apiextensions.SetCRDCondition(crd, *cond) + apiextensionshelpers.SetCRDCondition(crd, *cond) } _, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go index af8f0983d27..607f9e0e8fc 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go @@ -26,7 +26,9 @@ import ( "github.com/go-openapi/spec" v1 "k8s.io/api/autoscaling/v1" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" + apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" openapiv2 "k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2" @@ -86,14 +88,18 @@ type Options struct { // BuildSwagger builds swagger for the given crd in the given version func BuildSwagger(crd *apiextensions.CustomResourceDefinition, version string, opts Options) (*spec.Swagger, error) { var schema *structuralschema.Structural - s, err := apiextensions.GetSchemaForVersion(crd, version) + s, err := apiextensionshelpers.GetSchemaForVersion(crd, version) if err != nil { return nil, err } if s != nil && s.OpenAPIV3Schema != nil { - if !validation.SchemaHasInvalidTypes(s.OpenAPIV3Schema) { - if ss, err := structuralschema.NewStructural(s.OpenAPIV3Schema); err == nil { + internalCRDSchema := &apiextensionsinternal.CustomResourceValidation{} + if err := apiextensions.Convert_v1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(s, internalCRDSchema, nil); err != nil { + return nil, fmt.Errorf("failed converting CRD validation to internal version: %v", err) + } + if !validation.SchemaHasInvalidTypes(internalCRDSchema.OpenAPIV3Schema) { + if ss, err := structuralschema.NewStructural(internalCRDSchema.OpenAPIV3Schema); err == nil { // skip non-structural schemas unless explicitly asked to produce swagger from them if opts.AllowNonStructural || len(structuralschema.ValidateStructural(nil, ss)) == 0 { schema = ss @@ -151,7 +157,7 @@ func BuildSwagger(crd *apiextensions.CustomResourceDefinition, version string, o routes = append(routes, b.buildRoute(root, "/{name}", "DELETE", "delete", "delete", status)) routes = append(routes, b.buildRoute(root, "/{name}", "PATCH", "patch", "patch", sample).Reads(patch)) - subresources, err := apiextensions.GetSubresourcesForVersion(crd, version) + subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, version) if err != nil { return nil, err } @@ -517,8 +523,7 @@ func newBuilder(crd *apiextensions.CustomResourceDefinition, version string, sch } // Pre-build schema with Kubernetes native properties - preserveUnknownFields := crd.Spec.PreserveUnknownFields != nil && *crd.Spec.PreserveUnknownFields - b.schema = b.buildKubeNative(schema, v2, preserveUnknownFields) + b.schema = b.buildKubeNative(schema, v2, crd.Spec.PreserveUnknownFields) b.listSchema = b.buildListSchema() return b diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder_test.go index 3412532eec8..e2b7c2498d7 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder_test.go @@ -24,8 +24,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/json" @@ -352,12 +352,12 @@ func TestNewBuilder(t *testing.T) { t.Run(tt.name, func(t *testing.T) { var schema *structuralschema.Structural if len(tt.schema) > 0 { - v1beta1Schema := &v1beta1.JSONSchemaProps{} + v1beta1Schema := &apiextensions.JSONSchemaProps{} if err := json.Unmarshal([]byte(tt.schema), &v1beta1Schema); err != nil { t.Fatal(err) } - internalSchema := &apiextensions.JSONSchemaProps{} - v1beta1.Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(v1beta1Schema, internalSchema, nil) + internalSchema := &apiextensionsinternal.JSONSchemaProps{} + apiextensions.Convert_v1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(v1beta1Schema, internalSchema, nil) var err error schema, err = structuralschema.NewStructural(internalSchema) if err != nil { @@ -371,8 +371,12 @@ func TestNewBuilder(t *testing.T) { got := newBuilder(&apiextensions.CustomResourceDefinition{ Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "bar.k8s.io", - Version: "v1", + Group: "bar.k8s.io", + Versions: []apiextensions.CustomResourceDefinitionVersion{ + { + Name: "v1", + }, + }, Names: apiextensions.CustomResourceDefinitionNames{ Plural: "foos", Singular: "foo", @@ -478,12 +482,12 @@ func TestCRDRouteParameterBuilder(t *testing.T) { Versions: []apiextensions.CustomResourceDefinitionVersion{ { Name: testCRDVersion, + Subresources: &apiextensions.CustomResourceSubresources{ + Status: &apiextensions.CustomResourceSubresourceStatus{}, + Scale: &apiextensions.CustomResourceSubresourceScale{}, + }, }, }, - Subresources: &apiextensions.CustomResourceSubresources{ - Status: &apiextensions.CustomResourceSubresourceStatus{}, - Scale: &apiextensions.CustomResourceSubresourceScale{}, - }, }, } swagger, err := BuildSwagger(testNamespacedCRD, testCRDVersion, Options{V2: true, StripDefaults: true}) @@ -629,31 +633,35 @@ func TestBuildSwagger(t *testing.T) { t.Run(tt.name, func(t *testing.T) { var validation *apiextensions.CustomResourceValidation if len(tt.schema) > 0 { - v1beta1Schema := &v1beta1.JSONSchemaProps{} - if err := json.Unmarshal([]byte(tt.schema), &v1beta1Schema); err != nil { + v1Schema := &apiextensions.JSONSchemaProps{} + if err := json.Unmarshal([]byte(tt.schema), &v1Schema); err != nil { t.Fatal(err) } - internalSchema := &apiextensions.JSONSchemaProps{} - v1beta1.Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(v1beta1Schema, internalSchema, nil) validation = &apiextensions.CustomResourceValidation{ - OpenAPIV3Schema: internalSchema, + OpenAPIV3Schema: v1Schema, } } + if tt.preserveUnknownFields != nil && *tt.preserveUnknownFields { + validation.OpenAPIV3Schema.XPreserveUnknownFields = utilpointer.BoolPtr(true) + } // TODO: mostly copied from the test above. reuse code to cleanup got, err := BuildSwagger(&apiextensions.CustomResourceDefinition{ Spec: apiextensions.CustomResourceDefinitionSpec{ - Group: "bar.k8s.io", - Version: "v1", + Group: "bar.k8s.io", + Versions: []apiextensions.CustomResourceDefinitionVersion{ + { + Name: "v1", + Schema: validation, + }, + }, Names: apiextensions.CustomResourceDefinitionNames{ Plural: "foos", Singular: "foo", Kind: "Foo", ListKind: "FooList", }, - Scope: apiextensions.NamespaceScoped, - Validation: validation, - PreserveUnknownFields: tt.preserveUnknownFields, + Scope: apiextensions.NamespaceScoped, }, }, "v1", tt.opts) if err != nil { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/controller.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/controller.go index 57f6f525dae..f18c9f16c51 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/controller.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/controller.go @@ -33,9 +33,10 @@ import ( "k8s.io/klog" "k8s.io/kube-openapi/pkg/handler" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" - listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1" + listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder" ) @@ -99,7 +100,7 @@ func (c *Controller) Run(staticSpec *spec.Swagger, openAPIService *handler.OpenA return } for _, crd := range crds { - if !apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) { + if !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Established) { continue } newSpecs, changed, err := buildVersionSpecs(crd, nil) @@ -163,7 +164,7 @@ func (c *Controller) sync(name string) error { } // do we have to remove all specs of this CRD? - if errors.IsNotFound(err) || !apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) { + if errors.IsNotFound(err) || !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Established) { if _, found := c.crdSpecs[name]; !found { return nil } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go index 844dfa6b759..4c82065bc09 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go @@ -34,10 +34,11 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion" - informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" - listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1" + listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" ) // This controller is reserving names. To avoid conflicts, be sure to run only one instance of the worker at a time. @@ -202,7 +203,7 @@ func (c *NamingConditionController) calculateNamesAndConditions(in *apiextension Reason: "NotAccepted", Message: "not all names are accepted", } - if old := apiextensions.FindCRDCondition(in, apiextensions.Established); old != nil { + if old := apiextensionshelpers.FindCRDCondition(in, apiextensions.Established); old != nil { establishedCondition = *old } if establishedCondition.Status != apiextensions.ConditionTrue && namesAcceptedCondition.Status == apiextensions.ConditionTrue { @@ -251,14 +252,14 @@ func (c *NamingConditionController) sync(key string) error { // nothing to do if accepted names and NamesAccepted condition didn't change if reflect.DeepEqual(inCustomResourceDefinition.Status.AcceptedNames, acceptedNames) && - apiextensions.IsCRDConditionEquivalent(&namingCondition, apiextensions.FindCRDCondition(inCustomResourceDefinition, apiextensions.NamesAccepted)) { + apiextensionshelpers.IsCRDConditionEquivalent(&namingCondition, apiextensionshelpers.FindCRDCondition(inCustomResourceDefinition, apiextensions.NamesAccepted)) { return nil } crd := inCustomResourceDefinition.DeepCopy() crd.Status.AcceptedNames = acceptedNames - apiextensions.SetCRDCondition(crd, namingCondition) - apiextensions.SetCRDCondition(crd, establishedCondition) + apiextensionshelpers.SetCRDCondition(crd, namingCondition) + apiextensionshelpers.SetCRDCondition(crd, establishedCondition) updatedObj, err := c.crdClient.CustomResourceDefinitions().UpdateStatus(crd) if apierrors.IsNotFound(err) || apierrors.IsConflict(err) { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller_test.go index 717e5288484..8b141e2534a 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller_test.go @@ -22,8 +22,9 @@ import ( "testing" "time" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" + apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/cache" ) @@ -334,10 +335,10 @@ func TestSync(t *testing.T) { if e, a := tc.expectedNames, actualNames; !reflect.DeepEqual(e, a) { t.Errorf("%v expected %v, got %#v", tc.name, e, a) } - if e, a := tc.expectedNameConflictCondition, actualNameConflictCondition; !apiextensions.IsCRDConditionEquivalent(&e, &a) { + if e, a := tc.expectedNameConflictCondition, actualNameConflictCondition; !apiextensionshelpers.IsCRDConditionEquivalent(&e, &a) { t.Errorf("%v expected %v, got %v", tc.name, e, a) } - if e, a := tc.expectedEstablishedCondition, establishedCondition; !apiextensions.IsCRDConditionEquivalent(&e, &a) { + if e, a := tc.expectedEstablishedCondition, establishedCondition; !apiextensionshelpers.IsCRDConditionEquivalent(&e, &a) { t.Errorf("%v expected %v, got %v", tc.name, e, a) } } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go index 347e8fed8c9..66c749e9610 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go @@ -39,7 +39,8 @@ import ( "k8s.io/apiserver/pkg/registry/rest" etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/apiserver" "k8s.io/apiextensions-apiserver/pkg/crdserverscheme" "k8s.io/apiextensions-apiserver/pkg/registry/customresource" @@ -67,13 +68,13 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcd3testi kind := schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "Noxu"} labelSelectorPath := ".status.labelSelector" - scale := &apiextensions.CustomResourceSubresourceScale{ + scale := &apiextensionsinternal.CustomResourceSubresourceScale{ SpecReplicasPath: ".spec.replicas", StatusReplicasPath: ".status.replicas", LabelSelectorPath: &labelSelectorPath, } - status := &apiextensions.CustomResourceSubresourceStatus{} + status := &apiextensionsinternal.CustomResourceSubresourceStatus{} headers := []apiextensions.CustomResourceColumnDefinition{ {Name: "Age", Type: "date", JSONPath: ".metadata.creationTimestamp"}, diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor.go index 5b3a8e866d2..e335cecc04a 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor.go @@ -23,7 +23,7 @@ import ( "fmt" "reflect" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/meta" metatable "k8s.io/apimachinery/pkg/api/meta/table" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go index 059f49a3b8b..50d1a5e76cf 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go @@ -776,7 +776,7 @@ spec: if err != nil { t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v", cond) } - if v := "spec.validation.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields"; !strings.Contains(cond.Message, v) { + if v := "spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields"; !strings.Contains(cond.Message, v) { t.Fatalf("expected violation %q, but got: %v", v, cond.Message) } @@ -845,7 +845,7 @@ spec: if err != nil { t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v", cond) } - if v := "spec.validation.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields"; !strings.Contains(cond.Message, v) { + if v := "spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields"; !strings.Contains(cond.Message, v) { t.Fatalf("expected violation %q, but got: %v", v, cond.Message) } } @@ -926,12 +926,12 @@ x-kubernetes-embedded-resource: true type: object x-kubernetes-embedded-resource: true properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object + apiVersion: + type: string + kind: + type: string + metadata: + type: object `, expectedViolations: []string{}, }, @@ -972,7 +972,7 @@ x-kubernetes-preserve-unknown-fields: true type: "" `, expectedViolations: []string{ - "spec.validation.openAPIV3Schema.type: Required value: must not be empty at the root", + "spec.versions[0].schema.openAPIV3Schema.type: Required value: must not be empty at the root", }, }, { @@ -981,7 +981,7 @@ type: "" type: "integer" `, expectedViolations: []string{ - "spec.validation.openAPIV3Schema.type: Invalid value: \"integer\": must be object at the root", + "spec.versions[0].schema.openAPIV3Schema.type: Invalid value: \"integer\": must be object at the root", }, }, { @@ -989,63 +989,63 @@ type: "integer" globalSchema: ` type: object properties: - foo: - type: string + foo: + type: string not: - type: string - additionalProperties: true - title: hello - description: world - nullable: true + type: string + additionalProperties: true + title: hello + description: world + nullable: true allOf: - properties: - foo: - type: string - additionalProperties: true - title: hello - description: world - nullable: true + foo: + type: string + additionalProperties: true + title: hello + description: world + nullable: true anyOf: - items: - type: string - additionalProperties: true - title: hello - description: world - nullable: true + type: string + additionalProperties: true + title: hello + description: world + nullable: true oneOf: - properties: - foo: - type: string - additionalProperties: true - title: hello - description: world - nullable: true + foo: + type: string + additionalProperties: true + title: hello + description: world + nullable: true `, expectedViolations: []string{ - "spec.validation.openAPIV3Schema.anyOf[0].items.type: Forbidden: must be empty to be structural", - "spec.validation.openAPIV3Schema.anyOf[0].items.additionalProperties: Forbidden: must be undefined to be structural", - "spec.validation.openAPIV3Schema.anyOf[0].items.title: Forbidden: must be empty to be structural", - "spec.validation.openAPIV3Schema.anyOf[0].items.description: Forbidden: must be empty to be structural", - "spec.validation.openAPIV3Schema.anyOf[0].items.nullable: Forbidden: must be false to be structural", - "spec.validation.openAPIV3Schema.allOf[0].properties[foo].type: Forbidden: must be empty to be structural", - "spec.validation.openAPIV3Schema.allOf[0].properties[foo].additionalProperties: Forbidden: must be undefined to be structural", - "spec.validation.openAPIV3Schema.allOf[0].properties[foo].title: Forbidden: must be empty to be structural", - "spec.validation.openAPIV3Schema.allOf[0].properties[foo].description: Forbidden: must be empty to be structural", - "spec.validation.openAPIV3Schema.allOf[0].properties[foo].nullable: Forbidden: must be false to be structural", - "spec.validation.openAPIV3Schema.oneOf[0].properties[foo].type: Forbidden: must be empty to be structural", - "spec.validation.openAPIV3Schema.oneOf[0].properties[foo].additionalProperties: Forbidden: must be undefined to be structural", - "spec.validation.openAPIV3Schema.oneOf[0].properties[foo].title: Forbidden: must be empty to be structural", - "spec.validation.openAPIV3Schema.oneOf[0].properties[foo].description: Forbidden: must be empty to be structural", - "spec.validation.openAPIV3Schema.oneOf[0].properties[foo].nullable: Forbidden: must be false to be structural", - "spec.validation.openAPIV3Schema.not.type: Forbidden: must be empty to be structural", - "spec.validation.openAPIV3Schema.not.additionalProperties: Forbidden: must be undefined to be structural", - "spec.validation.openAPIV3Schema.not.title: Forbidden: must be empty to be structural", - "spec.validation.openAPIV3Schema.not.description: Forbidden: must be empty to be structural", - "spec.validation.openAPIV3Schema.not.nullable: Forbidden: must be false to be structural", - "spec.validation.openAPIV3Schema.items: Required value: because it is defined in spec.validation.openAPIV3Schema.anyOf[0].items", + "spec.versions[0].schema.openAPIV3Schema.anyOf[0].items.type: Forbidden: must be empty to be structural", + "spec.versions[0].schema.openAPIV3Schema.anyOf[0].items.additionalProperties: Forbidden: must be undefined to be structural", + "spec.versions[0].schema.openAPIV3Schema.anyOf[0].items.title: Forbidden: must be empty to be structural", + "spec.versions[0].schema.openAPIV3Schema.anyOf[0].items.description: Forbidden: must be empty to be structural", + "spec.versions[0].schema.openAPIV3Schema.anyOf[0].items.nullable: Forbidden: must be false to be structural", + "spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[foo].type: Forbidden: must be empty to be structural", + "spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[foo].additionalProperties: Forbidden: must be undefined to be structural", + "spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[foo].title: Forbidden: must be empty to be structural", + "spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[foo].description: Forbidden: must be empty to be structural", + "spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[foo].nullable: Forbidden: must be false to be structural", + "spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[foo].type: Forbidden: must be empty to be structural", + "spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[foo].additionalProperties: Forbidden: must be undefined to be structural", + "spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[foo].title: Forbidden: must be empty to be structural", + "spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[foo].description: Forbidden: must be empty to be structural", + "spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[foo].nullable: Forbidden: must be false to be structural", + "spec.versions[0].schema.openAPIV3Schema.not.type: Forbidden: must be empty to be structural", + "spec.versions[0].schema.openAPIV3Schema.not.additionalProperties: Forbidden: must be undefined to be structural", + "spec.versions[0].schema.openAPIV3Schema.not.title: Forbidden: must be empty to be structural", + "spec.versions[0].schema.openAPIV3Schema.not.description: Forbidden: must be empty to be structural", + "spec.versions[0].schema.openAPIV3Schema.not.nullable: Forbidden: must be false to be structural", + "spec.versions[0].schema.openAPIV3Schema.items: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.anyOf[0].items", }, unexpectedViolations: []string{ - "spec.validation.openAPIV3Schema.not.default", + "spec.versions[0].schema.openAPIV3Schema.not.default", }, }, { @@ -1053,12 +1053,12 @@ oneOf: globalSchema: ` type: object properties: - foo: - type: string - pattern: "+" + foo: + type: string + pattern: "+" `, expectedViolations: []string{ - "spec.validation.openAPIV3Schema.properties[foo].pattern: Invalid value: \"+\": must be a valid regular expression, but isn't: error parsing regexp: missing argument to repetition operator: `+`", + "spec.versions[0].schema.openAPIV3Schema.properties[foo].pattern: Invalid value: \"+\": must be a valid regular expression, but isn't: error parsing regexp: missing argument to repetition operator: `+`", }, }, { @@ -1066,40 +1066,40 @@ properties: globalSchema: ` type: object properties: - int-or-string: - x-kubernetes-int-or-string: true - embedded-resource: - type: object - x-kubernetes-embedded-resource: true - x-kubernetes-preserve-unknown-fields: true + int-or-string: + x-kubernetes-int-or-string: true + embedded-resource: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true not: - properties: - int-or-string: - x-kubernetes-int-or-string: true - embedded-resource: - x-kubernetes-embedded-resource: true - x-kubernetes-preserve-unknown-fields: true + properties: + int-or-string: + x-kubernetes-int-or-string: true + embedded-resource: + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true allOf: - properties: - int-or-string: - x-kubernetes-int-or-string: true - embedded-resource: - x-kubernetes-embedded-resource: true - x-kubernetes-preserve-unknown-fields: true + int-or-string: + x-kubernetes-int-or-string: true + embedded-resource: + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true anyOf: - properties: - int-or-string: - x-kubernetes-int-or-string: true - embedded-resource: - x-kubernetes-embedded-resource: true - x-kubernetes-preserve-unknown-fields: true + int-or-string: + x-kubernetes-int-or-string: true + embedded-resource: + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true oneOf: - properties: - int-or-string: - x-kubernetes-int-or-string: true - embedded-resource: - x-kubernetes-embedded-resource: true - x-kubernetes-preserve-unknown-fields: true + int-or-string: + x-kubernetes-int-or-string: true + embedded-resource: + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true `, expectedCreateErrors: []string{ "spec.validation.openAPIV3Schema.allOf[0].properties[embedded-resource].x-kubernetes-preserve-unknown-fields: Forbidden: must be false to be structural", @@ -1120,30 +1120,30 @@ oneOf: desc: "missing types with extensions", globalSchema: ` properties: - foo: - properties: - a: {} - bar: - items: - additionalProperties: - properties: - a: {} - items: {} - abc: - additionalProperties: - properties: - a: - items: - additionalProperties: - items: - json: - x-kubernetes-preserve-unknown-fields: true - properties: - a: {} - int-or-string: - x-kubernetes-int-or-string: true - properties: - a: {} + foo: + properties: + a: {} + bar: + items: + additionalProperties: + properties: + a: {} + items: {} + abc: + additionalProperties: + properties: + a: + items: + additionalProperties: + items: + json: + x-kubernetes-preserve-unknown-fields: true + properties: + a: {} + int-or-string: + x-kubernetes-int-or-string: true + properties: + a: {} `, expectedCreateErrors: []string{ "spec.validation.openAPIV3Schema.properties[foo].properties[a].type: Required value: must not be empty for specified object fields", @@ -1167,37 +1167,37 @@ properties: desc: "missing types without extensions", globalSchema: ` properties: - foo: - properties: - a: {} - bar: - items: - additionalProperties: - properties: - a: {} - items: {} - abc: - additionalProperties: - properties: - a: - items: - additionalProperties: - items: + foo: + properties: + a: {} + bar: + items: + additionalProperties: + properties: + a: {} + items: {} + abc: + additionalProperties: + properties: + a: + items: + additionalProperties: + items: `, expectedViolations: []string{ - "spec.validation.openAPIV3Schema.properties[foo].properties[a].type: Required value: must not be empty for specified object fields", - "spec.validation.openAPIV3Schema.properties[foo].type: Required value: must not be empty for specified object fields", - "spec.validation.openAPIV3Schema.properties[abc].additionalProperties.properties[a].items.additionalProperties.type: Required value: must not be empty for specified object fields", - "spec.validation.openAPIV3Schema.properties[abc].additionalProperties.properties[a].items.type: Required value: must not be empty for specified array items", - "spec.validation.openAPIV3Schema.properties[abc].additionalProperties.properties[a].type: Required value: must not be empty for specified object fields", - "spec.validation.openAPIV3Schema.properties[abc].additionalProperties.type: Required value: must not be empty for specified object fields", - "spec.validation.openAPIV3Schema.properties[abc].type: Required value: must not be empty for specified object fields", - "spec.validation.openAPIV3Schema.properties[bar].items.additionalProperties.items.type: Required value: must not be empty for specified array items", - "spec.validation.openAPIV3Schema.properties[bar].items.additionalProperties.properties[a].type: Required value: must not be empty for specified object fields", - "spec.validation.openAPIV3Schema.properties[bar].items.additionalProperties.type: Required value: must not be empty for specified object fields", - "spec.validation.openAPIV3Schema.properties[bar].items.type: Required value: must not be empty for specified array items", - "spec.validation.openAPIV3Schema.properties[bar].type: Required value: must not be empty for specified object fields", - "spec.validation.openAPIV3Schema.type: Required value: must not be empty at the root", + "spec.versions[0].schema.openAPIV3Schema.properties[foo].properties[a].type: Required value: must not be empty for specified object fields", + "spec.versions[0].schema.openAPIV3Schema.properties[foo].type: Required value: must not be empty for specified object fields", + "spec.versions[0].schema.openAPIV3Schema.properties[abc].additionalProperties.properties[a].items.additionalProperties.type: Required value: must not be empty for specified object fields", + "spec.versions[0].schema.openAPIV3Schema.properties[abc].additionalProperties.properties[a].items.type: Required value: must not be empty for specified array items", + "spec.versions[0].schema.openAPIV3Schema.properties[abc].additionalProperties.properties[a].type: Required value: must not be empty for specified object fields", + "spec.versions[0].schema.openAPIV3Schema.properties[abc].additionalProperties.type: Required value: must not be empty for specified object fields", + "spec.versions[0].schema.openAPIV3Schema.properties[abc].type: Required value: must not be empty for specified object fields", + "spec.versions[0].schema.openAPIV3Schema.properties[bar].items.additionalProperties.items.type: Required value: must not be empty for specified array items", + "spec.versions[0].schema.openAPIV3Schema.properties[bar].items.additionalProperties.properties[a].type: Required value: must not be empty for specified object fields", + "spec.versions[0].schema.openAPIV3Schema.properties[bar].items.additionalProperties.type: Required value: must not be empty for specified object fields", + "spec.versions[0].schema.openAPIV3Schema.properties[bar].items.type: Required value: must not be empty for specified array items", + "spec.versions[0].schema.openAPIV3Schema.properties[bar].type: Required value: must not be empty for specified object fields", + "spec.versions[0].schema.openAPIV3Schema.type: Required value: must not be empty at the root", }, }, { @@ -1205,48 +1205,48 @@ properties: globalSchema: ` type: object properties: - a: - x-kubernetes-int-or-string: true - b: - x-kubernetes-int-or-string: true - anyOf: - - type: integer - - type: string - allOf: - - pattern: abc - c: - x-kubernetes-int-or-string: true - allOf: - - anyOf: - - type: integer - - type: string - - pattern: abc - - pattern: abc - d: - x-kubernetes-int-or-string: true - anyOf: - - type: integer - - type: string - pattern: abc - e: - x-kubernetes-int-or-string: true - allOf: - - anyOf: - - type: integer - - type: string - pattern: abc - - pattern: abc - f: - x-kubernetes-int-or-string: true - anyOf: - - type: integer - - type: string - - pattern: abc - g: - x-kubernetes-int-or-string: true - anyOf: - - type: string - - type: integer + a: + x-kubernetes-int-or-string: true + b: + x-kubernetes-int-or-string: true + anyOf: + - type: integer + - type: string + allOf: + - pattern: abc + c: + x-kubernetes-int-or-string: true + allOf: + - anyOf: + - type: integer + - type: string + - pattern: abc + - pattern: abc + d: + x-kubernetes-int-or-string: true + anyOf: + - type: integer + - type: string + pattern: abc + e: + x-kubernetes-int-or-string: true + allOf: + - anyOf: + - type: integer + - type: string + pattern: abc + - pattern: abc + f: + x-kubernetes-int-or-string: true + anyOf: + - type: integer + - type: string + - pattern: abc + g: + x-kubernetes-int-or-string: true + anyOf: + - type: string + - type: integer `, expectedCreateErrors: []string{ "spec.validation.openAPIV3Schema.properties[d].anyOf[0].type: Forbidden: must be empty to be structural", @@ -1271,7 +1271,7 @@ type: object additionalProperties: false `, expectedViolations: []string{ - "spec.validation.openAPIV3Schema.additionalProperties: Forbidden: must not be used at the root", + "spec.versions[0].schema.openAPIV3Schema.additionalProperties: Forbidden: must not be used at the root", }, }, { @@ -1279,53 +1279,53 @@ additionalProperties: false globalSchema: ` type: object properties: - b: - type: object - properties: - b: - type: array - c: - type: array - items: - type: object - d: - type: array + b: + type: object + properties: + b: + type: array + c: + type: array + items: + type: object + d: + type: array not: - properties: - a: {} - b: - not: - properties: - a: {} - b: - items: {} - c: - items: - not: - items: - properties: - a: {} - d: - items: {} + properties: + a: {} + b: + not: + properties: + a: {} + b: + items: {} + c: + items: + not: + items: + properties: + a: {} + d: + items: {} allOf: - properties: - e: {} + e: {} anyOf: - properties: - f: {} + f: {} oneOf: - properties: - g: {} + g: {} `, expectedViolations: []string{ - "spec.validation.openAPIV3Schema.properties[d].items: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[d].items", - "spec.validation.openAPIV3Schema.properties[a]: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[a]", - "spec.validation.openAPIV3Schema.properties[b].properties[a]: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[b].not.properties[a]", - "spec.validation.openAPIV3Schema.properties[b].properties[b].items: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[b].not.properties[b].items", - "spec.validation.openAPIV3Schema.properties[c].items.items: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[c].items.not.items", - "spec.validation.openAPIV3Schema.properties[e]: Required value: because it is defined in spec.validation.openAPIV3Schema.allOf[0].properties[e]", - "spec.validation.openAPIV3Schema.properties[f]: Required value: because it is defined in spec.validation.openAPIV3Schema.anyOf[0].properties[f]", - "spec.validation.openAPIV3Schema.properties[g]: Required value: because it is defined in spec.validation.openAPIV3Schema.oneOf[0].properties[g]", + "spec.versions[0].schema.openAPIV3Schema.properties[d].items: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.not.properties[d].items", + "spec.versions[0].schema.openAPIV3Schema.properties[a]: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.not.properties[a]", + "spec.versions[0].schema.openAPIV3Schema.properties[b].properties[a]: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.not.properties[b].not.properties[a]", + "spec.versions[0].schema.openAPIV3Schema.properties[b].properties[b].items: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.not.properties[b].not.properties[b].items", + "spec.versions[0].schema.openAPIV3Schema.properties[c].items.items: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.not.properties[c].items.not.items", + "spec.versions[0].schema.openAPIV3Schema.properties[e]: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[e]", + "spec.versions[0].schema.openAPIV3Schema.properties[f]: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.anyOf[0].properties[f]", + "spec.versions[0].schema.openAPIV3Schema.properties[g]: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[g]", }, }, { @@ -1333,62 +1333,62 @@ oneOf: globalSchema: ` type: object properties: - a: - type: string - b: - type: object - properties: - a: - type: string - b: - type: array - items: - type: string - c: - type: array - items: - type: array - items: - type: object - properties: - a: - type: string - d: - type: array - items: - type: string - e: - type: string - f: - type: string - g: - type: string + a: + type: string + b: + type: object + properties: + a: + type: string + b: + type: array + items: + type: string + c: + type: array + items: + type: array + items: + type: object + properties: + a: + type: string + d: + type: array + items: + type: string + e: + type: string + f: + type: string + g: + type: string not: - properties: - a: {} - b: - not: - properties: - a: {} - b: - items: {} - c: - items: - not: - items: - properties: - a: {} - d: - items: {} + properties: + a: {} + b: + not: + properties: + a: {} + b: + items: {} + c: + items: + not: + items: + properties: + a: {} + d: + items: {} allOf: - properties: - e: {} + e: {} anyOf: - properties: - f: {} + f: {} oneOf: - properties: - g: {} + g: {} `, expectedViolations: nil, }, @@ -1397,16 +1397,16 @@ oneOf: v1beta1Schema: ` type: object properties: - a: {} + a: {} not: - properties: - b: {} + properties: + b: {} `, v1Schema: ` type: object properties: - a: - type: string + a: + type: string `, expectedViolations: []string{ "spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields", @@ -1418,18 +1418,18 @@ properties: v1beta1Schema: ` type: object properties: - a: {} + a: {} not: - properties: - b: {} + properties: + b: {} `, v1Schema: ` type: object properties: - c: {} + c: {} not: - properties: - d: {} + properties: + d: {} `, expectedViolations: []string{ "spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields", @@ -1443,12 +1443,12 @@ not: globalSchema: ` type: object properties: - metadata: - minimum: 42.0 + metadata: + minimum: 42.0 `, expectedViolations: []string{ - "spec.validation.openAPIV3Schema.properties[metadata]: Forbidden: must not specify anything other than name and generateName, but metadata is implicitly specified", - "spec.validation.openAPIV3Schema.properties[metadata].type: Required value: must not be empty for specified object fields", + "spec.versions[0].schema.openAPIV3Schema.properties[metadata]: Forbidden: must not specify anything other than name and generateName, but metadata is implicitly specified", + "spec.versions[0].schema.openAPIV3Schema.properties[metadata].type: Required value: must not be empty for specified object fields", }, }, { @@ -1456,18 +1456,18 @@ properties: globalSchema: ` type: object properties: - metadata: - properties: - name: - pattern: "^[a-z]+$" - labels: - type: object - maxLength: 4 + metadata: + properties: + name: + pattern: "^[a-z]+$" + labels: + type: object + maxLength: 4 `, expectedViolations: []string{ - "spec.validation.openAPIV3Schema.properties[metadata]: Forbidden: must not specify anything other than name and generateName, but metadata is implicitly specified", - "spec.validation.openAPIV3Schema.properties[metadata].type: Required value: must not be empty for specified object fields", - "spec.validation.openAPIV3Schema.properties[metadata].properties[name].type: Required value: must not be empty for specified object fields", + "spec.versions[0].schema.openAPIV3Schema.properties[metadata]: Forbidden: must not specify anything other than name and generateName, but metadata is implicitly specified", + "spec.versions[0].schema.openAPIV3Schema.properties[metadata].type: Required value: must not be empty for specified object fields", + "spec.versions[0].schema.openAPIV3Schema.properties[metadata].properties[name].type: Required value: must not be empty for specified object fields", }, }, { @@ -1475,12 +1475,12 @@ properties: globalSchema: ` type: object properties: - metadata: - type: object - properties: - name: - type: string - pattern: "^[a-z]+$" + metadata: + type: object + properties: + name: + type: string + pattern: "^[a-z]+$" `, expectedViolations: []string{}, }, @@ -1489,12 +1489,12 @@ properties: globalSchema: ` type: object properties: - metadata: - type: object - properties: - generateName: - type: string - pattern: "^[a-z]+$" + metadata: + type: object + properties: + generateName: + type: string + pattern: "^[a-z]+$" `, expectedViolations: []string{}, }, @@ -1503,15 +1503,15 @@ properties: globalSchema: ` type: object properties: - metadata: - type: object - properties: - name: - type: string - pattern: "^[a-z]+$" - generateName: - type: string - pattern: "^[a-z]+$" + metadata: + type: object + properties: + name: + type: string + pattern: "^[a-z]+$" + generateName: + type: string + pattern: "^[a-z]+$" `, expectedViolations: []string{}, }, @@ -1520,30 +1520,30 @@ properties: globalSchema: ` type: object properties: - metadata: - type: object - properties: - name: - type: string - pattern: "^[a-z]+$" + metadata: + type: object + properties: + name: + type: string + pattern: "^[a-z]+$" allOf: - properties: - metadata: {} + metadata: {} anyOf: - properties: - metadata: {} + metadata: {} oneOf: - properties: - metadata: {} + metadata: {} not: - properties: - metadata: {} + properties: + metadata: {} `, expectedViolations: []string{ - "spec.validation.openAPIV3Schema.anyOf[0].properties[metadata]: Forbidden: must not be specified in a nested context", - "spec.validation.openAPIV3Schema.allOf[0].properties[metadata]: Forbidden: must not be specified in a nested context", - "spec.validation.openAPIV3Schema.oneOf[0].properties[metadata]: Forbidden: must not be specified in a nested context", - "spec.validation.openAPIV3Schema.not.properties[metadata]: Forbidden: must not be specified in a nested context", + "spec.versions[0].schema.openAPIV3Schema.anyOf[0].properties[metadata]: Forbidden: must not be specified in a nested context", + "spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[metadata]: Forbidden: must not be specified in a nested context", + "spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[metadata]: Forbidden: must not be specified in a nested context", + "spec.versions[0].schema.openAPIV3Schema.not.properties[metadata]: Forbidden: must not be specified in a nested context", }, }, { @@ -1551,11 +1551,11 @@ not: globalSchema: ` type: object properties: - slice: - type: array + slice: + type: array `, expectedViolations: []string{ - "spec.validation.openAPIV3Schema.properties[slice].items: Required value: must be specified", + "spec.versions[0].schema.openAPIV3Schema.properties[slice].items: Required value: must be specified", }, }, { @@ -1563,11 +1563,11 @@ properties: globalSchema: ` type: object properties: - slice: - type: array - items: - - type: string - - type: integer + slice: + type: array + items: + - type: string + - type: integer `, expectedCreateErrors: []string{"spec.validation.openAPIV3Schema.properties[slice].items: Forbidden: items must be a schema object and not an array"}, }, @@ -1576,13 +1576,13 @@ properties: globalSchema: ` type: object properties: - slice: - type: array - items: - type: string - not: - items: - - type: string + slice: + type: array + items: + type: string + not: + items: + - type: string `, expectedCreateErrors: []string{"spec.validation.openAPIV3Schema.properties[slice].not.items: Forbidden: items must be a schema object and not an array"}, },