switch to v1 crd

switch api helper functions to v1 CRD api

switch v1 CRD for apiserver internal

switch to v1 CRD for internal controllers

api storage/validation related changes

move local-defaulting utils private to prevent spreading

boilerplate

keep the subresource status/scale spec nil unless it's enabled

clean up empty space
This commit is contained in:
yue9944882
2019-11-08 23:23:50 +08:00
parent be0750f6d6
commit 168f8f54f0
27 changed files with 1264 additions and 541 deletions

View File

@@ -28,7 +28,7 @@ import (
"k8s.io/klog" "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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@@ -140,7 +140,7 @@ func createAggregatorServer(aggregatorConfig *aggregatorapiserver.Config, delega
autoRegistrationController := autoregister.NewAutoRegisterController(aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(), apiRegistrationClient) autoRegistrationController := autoregister.NewAutoRegisterController(aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(), apiRegistrationClient)
apiServices := apiServicesToRegister(delegateAPIServer, autoRegistrationController) apiServices := apiServicesToRegister(delegateAPIServer, autoRegistrationController)
crdRegistrationController := crdregistration.NewCRDRegistrationController( crdRegistrationController := crdregistration.NewCRDRegistrationController(
apiExtensionInformers.Apiextensions().InternalVersion().CustomResourceDefinitions(), apiExtensionInformers.Apiextensions().V1().CustomResourceDefinitions(),
autoRegistrationController) autoRegistrationController)
err = aggregatorServer.GenericAPIServer.AddPostStartHook("kube-apiserver-autoregistration", func(context genericapiserver.PostStartHookContext) error { err = aggregatorServer.GenericAPIServer.AddPostStartHook("kube-apiserver-autoregistration", func(context genericapiserver.PostStartHookContext) error {

View File

@@ -22,9 +22,9 @@ import (
"k8s.io/klog" "k8s.io/klog"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
crdinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" crdinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1"
crdlisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" crdlisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"

View File

@@ -20,8 +20,8 @@ import (
"reflect" "reflect"
"testing" "testing"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
crdlisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" crdlisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"

View File

@@ -20,8 +20,10 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"strings" "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 // 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 // GetAPIApprovalState returns the state of the API approval and reason for that state
func GetAPIApprovalState(annotations map[string]string) (state APIApprovalState, reason string) { 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 // we use the result of this parsing in the switch/case below
url, annotationURLParseErr := url.ParseRequestURI(annotation) url, annotationURLParseErr := url.ParseRequestURI(annotation)
switch { switch {
case len(annotation) == 0: 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"): case strings.HasPrefix(annotation, "unapproved"):
return APIApprovalBypassed, fmt.Sprintf("not approved: %q", annotation) return APIApprovalBypassed, fmt.Sprintf("not approved: %q", annotation)
case annotationURLParseErr == nil && url != nil && len(url.Host) > 0 && len(url.Scheme) > 0: case annotationURLParseErr == nil && url != nil && len(url.Host) > 0 && len(url.Scheme) > 0:
return APIApproved, fmt.Sprintf("approved in %v", annotation) return APIApproved, fmt.Sprintf("approved in %v", annotation)
default: 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
}

View File

@@ -17,9 +17,12 @@ limitations under the License.
package apihelpers package apihelpers
import ( import (
"reflect"
"testing" "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) { func TestIsProtectedCommunityGroup(t *testing.T) {
@@ -76,22 +79,22 @@ func TestGetAPIApprovalState(t *testing.T) {
}{ }{
{ {
name: "bare unapproved", name: "bare unapproved",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "unapproved"}, annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "unapproved"},
expected: APIApprovalBypassed, expected: APIApprovalBypassed,
}, },
{ {
name: "unapproved with message", name: "unapproved with message",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "unapproved, experimental-only"}, annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "unapproved, experimental-only"},
expected: APIApprovalBypassed, expected: APIApprovalBypassed,
}, },
{ {
name: "mismatched case", name: "mismatched case",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "Unapproved"}, annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "Unapproved"},
expected: APIApprovalInvalid, expected: APIApprovalInvalid,
}, },
{ {
name: "empty", name: "empty",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: ""}, annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: ""},
expected: APIApprovalMissing, expected: APIApprovalMissing,
}, },
{ {
@@ -101,27 +104,27 @@ func TestGetAPIApprovalState(t *testing.T) {
}, },
{ {
name: "url", 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, expected: APIApproved,
}, },
{ {
name: "url - no scheme", 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, expected: APIApprovalInvalid,
}, },
{ {
name: "url - no host", 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, expected: APIApprovalInvalid,
}, },
{ {
name: "url - just path", name: "url - just path",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "/"}, annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "/"},
expected: APIApprovalInvalid, expected: APIApprovalInvalid,
}, },
{ {
name: "missing scheme", 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, 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,
},
}
}

View File

@@ -25,10 +25,10 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "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/clientset"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset"
_ "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions" _ "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/apiapproval"
"k8s.io/apiextensions-apiserver/pkg/controller/establish" "k8s.io/apiextensions-apiserver/pkg/controller/establish"
"k8s.io/apiextensions-apiserver/pkg/controller/finalizer" "k8s.io/apiextensions-apiserver/pkg/controller/finalizer"
@@ -109,7 +109,7 @@ type CustomResourceDefinitions struct {
GenericAPIServer *genericapiserver.GenericAPIServer GenericAPIServer *genericapiserver.GenericAPIServer
// provided for easier embedding // 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. // 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 return nil, err
} }
crdClient, err := internalclientset.NewForConfig(s.GenericAPIServer.LoopbackClientConfig) crdClient, err := clientset.NewForConfig(s.GenericAPIServer.LoopbackClientConfig)
if err != nil { 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), // 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 // we need to be able to move forward
return nil, fmt.Errorf("failed to create clientset: %v", err) 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() delegateHandler := delegationTarget.UnprotectedHandler()
if delegateHandler == nil { if delegateHandler == nil {
@@ -185,11 +185,11 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
discovery: map[string]*discovery.APIGroupHandler{}, discovery: map[string]*discovery.APIGroupHandler{},
delegate: delegateHandler, 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( crdHandler, err := NewCustomResourceDefinitionHandler(
versionDiscoveryHandler, versionDiscoveryHandler,
groupDiscoveryHandler, groupDiscoveryHandler,
s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), s.Informers.Apiextensions().V1().CustomResourceDefinitions(),
delegateHandler, delegateHandler,
c.ExtraConfig.CRDRESTOptionsGetter, c.ExtraConfig.CRDRESTOptionsGetter,
c.GenericConfig.AdmissionControl, 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.Handle("/apis", crdHandler)
s.GenericAPIServer.Handler.NonGoRestfulMux.HandlePrefix("/apis/", crdHandler) s.GenericAPIServer.Handler.NonGoRestfulMux.HandlePrefix("/apis/", crdHandler)
crdController := NewDiscoveryController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), versionDiscoveryHandler, groupDiscoveryHandler) crdController := NewDiscoveryController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), versionDiscoveryHandler, groupDiscoveryHandler)
namingController := status.NewNamingConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions()) namingController := status.NewNamingConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
nonStructuralSchemaController := nonstructuralschema.NewConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions()) nonStructuralSchemaController := nonstructuralschema.NewConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
apiApprovalController := apiapproval.NewKubernetesAPIApprovalPolicyConformantConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions()) apiApprovalController := apiapproval.NewKubernetesAPIApprovalPolicyConformantConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
finalizingController := finalizer.NewCRDFinalizer( finalizingController := finalizer.NewCRDFinalizer(
s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), s.Informers.Apiextensions().V1().CustomResourceDefinitions(),
crdClient.Apiextensions(), crdClient.ApiextensionsV1(),
crdHandler, crdHandler,
) )
var openapiController *openapicontroller.Controller var openapiController *openapicontroller.Controller
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourcePublishOpenAPI) { 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 { 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. // 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 { s.GenericAPIServer.AddPostStartHookOrDie("crd-informer-synced", func(context genericapiserver.PostStartHookContext) error {
return wait.PollImmediateUntil(100*time.Millisecond, func() (bool, 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) }, context.StopCh)
}) })

View File

@@ -20,7 +20,7 @@ import (
"fmt" "fmt"
autoscalingv1 "k8s.io/api/autoscaling/v1" 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" apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "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 // Determine whether we should expect to be asked to "convert" autoscaling/v1 Scale types
convertScale := false convertScale := false
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) { if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) {
convertScale = crd.Spec.Subresources != nil && crd.Spec.Subresources.Scale != nil
for _, version := range crd.Spec.Versions { for _, version := range crd.Spec.Versions {
if version.Subresources != nil && version.Subresources.Scale != nil { if version.Subresources != nil && version.Subresources.Scale != nil {
convertScale = true convertScale = true

View File

@@ -21,7 +21,7 @@ import (
"strings" "strings"
"testing" "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/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"

View File

@@ -22,7 +22,7 @@ import (
"fmt" "fmt"
"time" "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" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apivalidation "k8s.io/apimachinery/pkg/api/validation" apivalidation "k8s.io/apimachinery/pkg/api/validation"
@@ -73,8 +73,8 @@ type webhookConverter struct {
conversionReviewVersions []string conversionReviewVersions []string
} }
func webhookClientConfigForCRD(crd *internal.CustomResourceDefinition) *webhook.ClientConfig { func webhookClientConfigForCRD(crd *apiextensions.CustomResourceDefinition) *webhook.ClientConfig {
apiConfig := crd.Spec.Conversion.WebhookClientConfig apiConfig := crd.Spec.Conversion.Webhook.ClientConfig
ret := webhook.ClientConfig{ ret := webhook.ClientConfig{
Name: fmt.Sprintf("conversion_webhook_for_%s", crd.Name), Name: fmt.Sprintf("conversion_webhook_for_%s", crd.Name),
CABundle: apiConfig.CABundle, CABundle: apiConfig.CABundle,
@@ -86,7 +86,7 @@ func webhookClientConfigForCRD(crd *internal.CustomResourceDefinition) *webhook.
ret.Service = &webhook.ClientConfigService{ ret.Service = &webhook.ClientConfigService{
Name: apiConfig.Service.Name, Name: apiConfig.Service.Name,
Namespace: apiConfig.Service.Namespace, Namespace: apiConfig.Service.Namespace,
Port: apiConfig.Service.Port, Port: *apiConfig.Service.Port,
} }
if apiConfig.Service.Path != nil { if apiConfig.Service.Path != nil {
ret.Service.Path = *apiConfig.Service.Path ret.Service.Path = *apiConfig.Service.Path
@@ -97,7 +97,7 @@ func webhookClientConfigForCRD(crd *internal.CustomResourceDefinition) *webhook.
var _ crConverterInterface = &webhookConverter{} 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)) restClient, err := f.clientManager.HookClient(*webhookClientConfigForCRD(crd))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -108,7 +108,7 @@ func (f *webhookConverterFactory) NewWebhookConverter(crd *internal.CustomResour
name: crd.Name, name: crd.Name,
nopConverter: nopConverter{}, nopConverter: nopConverter{},
conversionReviewVersions: crd.Spec.Conversion.ConversionReviewVersions, conversionReviewVersions: crd.Spec.Conversion.Webhook.ConversionReviewVersions,
}, nil }, nil
} }

View File

@@ -34,9 +34,10 @@ import (
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue" "k8s.io/client-go/util/workqueue"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
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"
) )
type DiscoveryController struct { type DiscoveryController struct {
@@ -86,7 +87,7 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error {
foundVersion := false foundVersion := false
foundGroup := false foundGroup := false
for _, crd := range crds { for _, crd := range crds {
if !apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) { if !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Established) {
continue 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"}) verbs := metav1.Verbs([]string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"})
// if we're terminating we don't allow some verbs // 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"}) verbs = metav1.Verbs([]string{"delete", "deletecollection", "get", "list", "watch"})
} }
@@ -141,7 +142,7 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error {
StorageVersionHash: storageVersionHash, StorageVersionHash: storageVersionHash,
}) })
subresources, err := apiextensions.GetSubresourcesForVersion(crd, version.Version) subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, version.Version)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -29,15 +29,17 @@ import (
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
"github.com/go-openapi/validate" "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" "k8s.io/apiextensions-apiserver/pkg/apiserver/conversion"
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting" structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta" schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
structuralpruning "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning" structuralpruning "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning"
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation" apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/controller/establish" "k8s.io/apiextensions-apiserver/pkg/controller/establish"
"k8s.io/apiextensions-apiserver/pkg/controller/finalizer" "k8s.io/apiextensions-apiserver/pkg/controller/finalizer"
"k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder" "k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder"
@@ -287,7 +289,7 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
if !apiextensions.HasServedCRDVersion(crd, requestInfo.APIVersion) { if !apiextensionshelpers.HasServedCRDVersion(crd, requestInfo.APIVersion) {
r.delegate.ServeHTTP(w, req) r.delegate.ServeHTTP(w, req)
return 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 // 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. // 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. // We accept this as the problem is small and self-healing.
if !apiextensions.IsCRDConditionTrue(crd, apiextensions.NamesAccepted) && if !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.NamesAccepted) &&
!apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) { !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Established) {
r.delegate.ServeHTTP(w, req) r.delegate.ServeHTTP(w, req)
return return
} }
terminating := apiextensions.IsCRDConditionTrue(crd, apiextensions.Terminating) terminating := apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Terminating)
crdInfo, err := r.getOrCreateServingInfoFor(crd.UID, crd.Name) crdInfo, err := r.getOrCreateServingInfoFor(crd.UID, crd.Name)
if apierrors.IsNotFound(err) { if apierrors.IsNotFound(err) {
@@ -335,7 +337,7 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
var handlerFunc http.HandlerFunc var handlerFunc http.HandlerFunc
subresources, err := apiextensions.GetSubresourcesForVersion(crd, requestInfo.APIVersion) subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, requestInfo.APIVersion)
if err != nil { if err != nil {
utilruntime.HandleError(err) utilruntime.HandleError(err)
responsewriters.ErrorNegotiated( 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, // 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. // 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. // TODO: find a real HA safe checkpointing mechanism instead of an arbitrary wait.
if !apiextensions.IsCRDConditionTrue(newCRD, apiextensions.Established) && if !apiextensionshelpers.IsCRDConditionTrue(newCRD, apiextensions.Established) &&
apiextensions.IsCRDConditionTrue(newCRD, apiextensions.NamesAccepted) { apiextensionshelpers.IsCRDConditionTrue(newCRD, apiextensions.NamesAccepted) {
if r.masterCount > 1 { if r.masterCount > 1 {
r.establishingController.QueueCRD(newCRD.Name, 5*time.Second) r.establishingController.QueueCRD(newCRD.Name, 5*time.Second)
} else { } else {
@@ -607,7 +609,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
return ret, nil return ret, nil
} }
storageVersion, err := apiextensions.GetCRDStorageVersion(crd) storageVersion, err := apiextensionshelpers.GetCRDStorageVersion(crd)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -622,7 +624,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
structuralSchemas := map[string]*structuralschema.Structural{} structuralSchemas := map[string]*structuralschema.Structural{}
for _, v := range crd.Spec.Versions { for _, v := range crd.Spec.Versions {
val, err := apiextensions.GetSchemaForVersion(crd, v.Name) val, err := apiextensionshelpers.GetSchemaForVersion(crd, v.Name)
if err != nil { if err != nil {
utilruntime.HandleError(err) utilruntime.HandleError(err)
return nil, fmt.Errorf("the server could not properly serve the CR schema") 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 { if val == nil {
continue continue
} }
s, err := structuralschema.NewStructural(val.OpenAPIV3Schema) internalValidation := &apiextensionsinternal.CustomResourceValidation{}
if *crd.Spec.PreserveUnknownFields == false && err != nil { 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. // This should never happen. If it does, it is a programming error.
utilruntime.HandleError(fmt.Errorf("failed to convert schema to structural: %v", err)) 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 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. // we don't own s completely, e.g. defaults are not deep-copied. So better make a copy here.
s = s.DeepCopy() s = s.DeepCopy()
@@ -679,36 +685,39 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
typer := newUnstructuredObjectTyper(parameterScheme) typer := newUnstructuredObjectTyper(parameterScheme)
creator := unstructuredCreator{} creator := unstructuredCreator{}
validationSchema, err := apiextensions.GetSchemaForVersion(crd, v.Name) validationSchema, err := apiextensionshelpers.GetSchemaForVersion(crd, v.Name)
if err != nil { if err != nil {
utilruntime.HandleError(err) utilruntime.HandleError(err)
return nil, fmt.Errorf("the server could not properly serve the CR schema") 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 { if err != nil {
return nil, err return nil, err
} }
// Check for nil because we dereference this throughout the handler code. var statusSpec *apiextensionsinternal.CustomResourceSubresourceStatus
// 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 statusValidator *validate.SchemaValidator var statusValidator *validate.SchemaValidator
subresources, err := apiextensions.GetSubresourcesForVersion(crd, v.Name) subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, v.Name)
if err != nil { if err != nil {
utilruntime.HandleError(err) utilruntime.HandleError(err)
return nil, fmt.Errorf("the server could not properly serve the CR subresources") return nil, fmt.Errorf("the server could not properly serve the CR subresources")
} }
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Status != nil { if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Status != nil {
equivalentResourceRegistry.RegisterKindFor(resource, "status", kind) equivalentResourceRegistry.RegisterKindFor(resource, "status", kind)
statusSpec = &apiextensionsinternal.CustomResourceSubresourceStatus{}
statusSpec = subresources.Status 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 // for the status subresource, validate only against the status schema
if validationSchema != nil && validationSchema.OpenAPIV3Schema != nil && validationSchema.OpenAPIV3Schema.Properties != nil { if internalValidationSchema != nil && internalValidationSchema.OpenAPIV3Schema != nil && internalValidationSchema.OpenAPIV3Schema.Properties != nil {
if statusSchema, ok := validationSchema.OpenAPIV3Schema.Properties["status"]; ok { if statusSchema, ok := internalValidationSchema.OpenAPIV3Schema.Properties["status"]; ok {
openapiSchema := &spec.Schema{} openapiSchema := &spec.Schema{}
if err := apiservervalidation.ConvertJSONSchemaPropsWithPostProcess(&statusSchema, openapiSchema, apiservervalidation.StripUnsupportedFormatsPostProcess); err != nil { if err := apiservervalidation.ConvertJSONSchemaPropsWithPostProcess(&statusSchema, openapiSchema, apiservervalidation.StripUnsupportedFormatsPostProcess); err != nil {
return nil, err 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 { if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Scale != nil {
equivalentResourceRegistry.RegisterKindFor(resource, "scale", autoscalingv1.SchemeGroupVersion.WithKind("Scale")) equivalentResourceRegistry.RegisterKindFor(resource, "scale", autoscalingv1.SchemeGroupVersion.WithKind("Scale"))
scaleSpec = &apiextensionsinternal.CustomResourceSubresourceScale{}
scaleSpec = subresources.Scale 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 { if err != nil {
utilruntime.HandleError(err) utilruntime.HandleError(err)
return nil, fmt.Errorf("the server could not properly serve the CR columns") 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}, encoderVersion: schema.GroupVersion{Group: crd.Spec.Group, Version: storageVersion},
structuralSchemas: structuralSchemas, structuralSchemas: structuralSchemas,
structuralSchemaGK: kind.GroupKind(), structuralSchemaGK: kind.GroupKind(),
preserveUnknownFields: *crd.Spec.PreserveUnknownFields, preserveUnknownFields: crd.Spec.PreserveUnknownFields,
}, },
crd.Status.AcceptedNames.Categories, crd.Status.AcceptedNames.Categories,
table, table,
@@ -779,7 +790,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
converter: safeConverter, converter: safeConverter,
structuralSchemas: structuralSchemas, structuralSchemas: structuralSchemas,
structuralSchemaGK: kind.GroupKind(), structuralSchemaGK: kind.GroupKind(),
preserveUnknownFields: *crd.Spec.PreserveUnknownFields, preserveUnknownFields: crd.Spec.PreserveUnknownFields,
} }
var standardSerializers []runtime.SerializerInfo var standardSerializers []runtime.SerializerInfo
for _, s := range negotiatedSerializer.SupportedMediaTypes() { for _, s := range negotiatedSerializer.SupportedMediaTypes() {
@@ -830,7 +841,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
reqScope.Creater, reqScope.Creater,
reqScope.Kind, reqScope.Kind,
reqScope.HubGroupVersion, reqScope.HubGroupVersion,
*crd.Spec.PreserveUnknownFields, crd.Spec.PreserveUnknownFields,
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -25,9 +25,9 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"testing" "testing"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/conversion" "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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/protobuf" "k8s.io/apimachinery/pkg/runtime/serializer/protobuf"

View File

@@ -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"},
}
}

View File

@@ -24,7 +24,7 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer" apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@@ -43,7 +43,7 @@ func TestRoundTrip(t *testing.T) {
if err := apiextensions.AddToScheme(scheme); err != nil { if err := apiextensions.AddToScheme(scheme); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := apiextensionsv1beta1.AddToScheme(scheme); err != nil { if err := apiextensionsv1.AddToScheme(scheme); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -81,7 +81,7 @@ func TestRoundTrip(t *testing.T) {
} }
// JSON -> external // JSON -> external
external := &apiextensionsv1beta1.JSONSchemaProps{} external := &apiextensionsv1.JSONSchemaProps{}
if err := json.Unmarshal(openAPIJSON, external); err != nil { if err := json.Unmarshal(openAPIJSON, external); err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -22,11 +22,10 @@ import (
"time" "time"
"k8s.io/apiextensions-apiserver/pkg/apihelpers" "k8s.io/apiextensions-apiserver/pkg/apihelpers"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion" informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
@@ -133,7 +132,7 @@ func (c *KubernetesAPIApprovalPolicyConformantConditionController) sync(key stri
} }
// avoid repeated calculation for the same annotation // avoid repeated calculation for the same annotation
protectionAnnotationValue := inCustomResourceDefinition.Annotations[v1beta1.KubeAPIApprovedAnnotation] protectionAnnotationValue := inCustomResourceDefinition.Annotations[apiextensions.KubeAPIApprovedAnnotation]
c.lastSeenProtectedAnnotationLock.Lock() c.lastSeenProtectedAnnotationLock.Lock()
lastSeen, seenBefore := c.lastSeenProtectedAnnotation[inCustomResourceDefinition.Name] lastSeen, seenBefore := c.lastSeenProtectedAnnotation[inCustomResourceDefinition.Name]
c.lastSeenProtectedAnnotationLock.Unlock() 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. // because group is immutable, if we have no condition now, we have no need to remove a condition.
return nil 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 // 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 { 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 // update condition
crd := inCustomResourceDefinition.DeepCopy() crd := inCustomResourceDefinition.DeepCopy()
apiextensions.SetCRDCondition(crd, *cond) apihelpers.SetCRDCondition(crd, *cond)
_, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd) _, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
if apierrors.IsNotFound(err) || apierrors.IsConflict(err) { if apierrors.IsNotFound(err) || apierrors.IsConflict(err) {

View File

@@ -19,8 +19,7 @@ package apiapproval
import ( import (
"testing" "testing"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@@ -89,7 +88,7 @@ func TestCalculateCondition(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
crd := &apiextensions.CustomResourceDefinition{ 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{ Spec: apiextensions.CustomResourceDefinitionSpec{
Group: test.group, Group: test.group,
}, },

View File

@@ -27,10 +27,11 @@ import (
"k8s.io/client-go/util/workqueue" "k8s.io/client-go/util/workqueue"
"k8s.io/klog" "k8s.io/klog"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
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"
) )
// EstablishingController controls how and when CRD is established. // EstablishingController controls how and when CRD is established.
@@ -119,8 +120,8 @@ func (ec *EstablishingController) sync(key string) error {
return err return err
} }
if !apiextensions.IsCRDConditionTrue(cachedCRD, apiextensions.NamesAccepted) || if !apiextensionshelpers.IsCRDConditionTrue(cachedCRD, apiextensions.NamesAccepted) ||
apiextensions.IsCRDConditionTrue(cachedCRD, apiextensions.Established) { apiextensionshelpers.IsCRDConditionTrue(cachedCRD, apiextensions.Established) {
return nil return nil
} }
@@ -131,7 +132,7 @@ func (ec *EstablishingController) sync(key string) error {
Reason: "InitialNamesAccepted", Reason: "InitialNamesAccepted",
Message: "the initial names have been accepted", Message: "the initial names have been accepted",
} }
apiextensions.SetCRDCondition(crd, establishedCondition) apiextensionshelpers.SetCRDCondition(crd, establishedCondition)
// Update server with new CRD condition. // Update server with new CRD condition.
_, err = ec.crdClient.CustomResourceDefinitions().UpdateStatus(crd) _, err = ec.crdClient.CustomResourceDefinitions().UpdateStatus(crd)

View File

@@ -36,10 +36,11 @@ import (
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue" "k8s.io/client-go/util/workqueue"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
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"
) )
// OverlappingBuiltInResources returns the set of built-in group/resources that are persisted // 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 // no work to do
if cachedCRD.DeletionTimestamp.IsZero() || !apiextensions.CRDHasFinalizer(cachedCRD, apiextensions.CustomResourceCleanupFinalizer) { if cachedCRD.DeletionTimestamp.IsZero() || !apiextensionshelpers.CRDHasFinalizer(cachedCRD, apiextensions.CustomResourceCleanupFinalizer) {
return nil return nil
} }
crd := cachedCRD.DeepCopy() crd := cachedCRD.DeepCopy()
// update the status condition. This cleanup could take a while. // update the status condition. This cleanup could take a while.
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ apiextensionshelpers.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
Type: apiextensions.Terminating, Type: apiextensions.Terminating,
Status: apiextensions.ConditionTrue, Status: apiextensions.ConditionTrue,
Reason: "InstanceDeletionInProgress", 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. // 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}] { 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 // 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, Type: apiextensions.Terminating,
Status: apiextensions.ConditionFalse, Status: apiextensions.ConditionFalse,
Reason: "OverlappingBuiltInResource", Reason: "OverlappingBuiltInResource",
Message: "instances overlap with built-in resources in storage", 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) cond, deleteErr := c.deleteInstances(crd)
apiextensions.SetCRDCondition(crd, cond) apiextensionshelpers.SetCRDCondition(crd, cond)
if deleteErr != nil { if deleteErr != nil {
if _, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd); err != nil { if _, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd); err != nil {
utilruntime.HandleError(err) utilruntime.HandleError(err)
@@ -155,7 +156,7 @@ func (c *CRDFinalizer) sync(key string) error {
return deleteErr return deleteErr
} }
} else { } else {
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ apiextensionshelpers.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
Type: apiextensions.Terminating, Type: apiextensions.Terminating,
Status: apiextensions.ConditionFalse, Status: apiextensions.ConditionFalse,
Reason: "NeverEstablished", 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) _, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
if apierrors.IsNotFound(err) || apierrors.IsConflict(err) { if apierrors.IsNotFound(err) || apierrors.IsConflict(err) {
// deleted or changed in the meantime, we'll get called again // 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{}) { func (c *CRDFinalizer) addCustomResourceDefinition(obj interface{}) {
castObj := obj.(*apiextensions.CustomResourceDefinition) castObj := obj.(*apiextensions.CustomResourceDefinition)
// only queue deleted things // 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) c.enqueue(castObj)
} }
} }
@@ -321,7 +322,7 @@ func (c *CRDFinalizer) updateCustomResourceDefinition(oldObj, newObj interface{}
oldCRD := oldObj.(*apiextensions.CustomResourceDefinition) oldCRD := oldObj.(*apiextensions.CustomResourceDefinition)
newCRD := newObj.(*apiextensions.CustomResourceDefinition) newCRD := newObj.(*apiextensions.CustomResourceDefinition)
// only queue deleted things that haven't been finalized by us // 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 return
} }
@@ -339,8 +340,8 @@ func (c *CRDFinalizer) updateCustomResourceDefinition(oldObj, newObj interface{}
newCopy := newCRD.DeepCopy() newCopy := newCRD.DeepCopy()
oldCopy.ResourceVersion = "" oldCopy.ResourceVersion = ""
newCopy.ResourceVersion = "" newCopy.ResourceVersion = ""
apiextensions.RemoveCRDCondition(oldCopy, apiextensions.Terminating) apiextensionshelpers.RemoveCRDCondition(oldCopy, apiextensions.Terminating)
apiextensions.RemoveCRDCondition(newCopy, apiextensions.Terminating) apiextensionshelpers.RemoveCRDCondition(newCopy, apiextensions.Terminating)
if !reflect.DeepEqual(oldCopy, newCopy) { if !reflect.DeepEqual(oldCopy, newCopy) {
c.enqueue(newCRD) c.enqueue(newCRD)

View File

@@ -30,11 +30,13 @@ import (
"k8s.io/client-go/util/workqueue" "k8s.io/client-go/util/workqueue"
"k8s.io/klog" "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" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion" client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
) )
// ConditionController is maintaining the NonStructuralSchema condition. // ConditionController is maintaining the NonStructuralSchema condition.
@@ -87,25 +89,17 @@ func calculateCondition(in *apiextensions.CustomResourceDefinition) *apiextensio
allErrs := field.ErrorList{} 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 { for i, v := range in.Spec.Versions {
if v.Schema == nil || v.Schema.OpenAPIV3Schema == nil { if v.Schema == nil || v.Schema.OpenAPIV3Schema == nil {
continue 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 { if err != nil {
cond.Reason = "StructuralError" cond.Reason = "StructuralError"
cond.Message = fmt.Sprintf("failed to check validation schema for version %s: %v", v.Name, err) 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 // check old condition
cond := calculateCondition(inCustomResourceDefinition) cond := calculateCondition(inCustomResourceDefinition)
old := apiextensions.FindCRDCondition(inCustomResourceDefinition, apiextensions.NonStructuralSchema) old := apiextensionshelpers.FindCRDCondition(inCustomResourceDefinition, apiextensions.NonStructuralSchema)
if cond == nil && old == nil { if cond == nil && old == nil {
return nil return nil
@@ -159,10 +153,10 @@ func (c *ConditionController) sync(key string) error {
// update condition // update condition
crd := inCustomResourceDefinition.DeepCopy() crd := inCustomResourceDefinition.DeepCopy()
if cond == nil { if cond == nil {
apiextensions.RemoveCRDCondition(crd, apiextensions.NonStructuralSchema) apiextensionshelpers.RemoveCRDCondition(crd, apiextensions.NonStructuralSchema)
} else { } else {
cond.LastTransitionTime = metav1.NewTime(time.Now()) cond.LastTransitionTime = metav1.NewTime(time.Now())
apiextensions.SetCRDCondition(crd, *cond) apiextensionshelpers.SetCRDCondition(crd, *cond)
} }
_, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd) _, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)

View File

@@ -26,7 +26,9 @@ import (
"github.com/go-openapi/spec" "github.com/go-openapi/spec"
v1 "k8s.io/api/autoscaling/v1" 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" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
openapiv2 "k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2" 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 // BuildSwagger builds swagger for the given crd in the given version
func BuildSwagger(crd *apiextensions.CustomResourceDefinition, version string, opts Options) (*spec.Swagger, error) { func BuildSwagger(crd *apiextensions.CustomResourceDefinition, version string, opts Options) (*spec.Swagger, error) {
var schema *structuralschema.Structural var schema *structuralschema.Structural
s, err := apiextensions.GetSchemaForVersion(crd, version) s, err := apiextensionshelpers.GetSchemaForVersion(crd, version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if s != nil && s.OpenAPIV3Schema != nil { if s != nil && s.OpenAPIV3Schema != nil {
if !validation.SchemaHasInvalidTypes(s.OpenAPIV3Schema) { internalCRDSchema := &apiextensionsinternal.CustomResourceValidation{}
if ss, err := structuralschema.NewStructural(s.OpenAPIV3Schema); err == nil { 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 // skip non-structural schemas unless explicitly asked to produce swagger from them
if opts.AllowNonStructural || len(structuralschema.ValidateStructural(nil, ss)) == 0 { if opts.AllowNonStructural || len(structuralschema.ValidateStructural(nil, ss)) == 0 {
schema = ss 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}", "DELETE", "delete", "delete", status))
routes = append(routes, b.buildRoute(root, "/{name}", "PATCH", "patch", "patch", sample).Reads(patch)) 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 { if err != nil {
return nil, err return nil, err
} }
@@ -517,8 +523,7 @@ func newBuilder(crd *apiextensions.CustomResourceDefinition, version string, sch
} }
// Pre-build schema with Kubernetes native properties // Pre-build schema with Kubernetes native properties
preserveUnknownFields := crd.Spec.PreserveUnknownFields != nil && *crd.Spec.PreserveUnknownFields b.schema = b.buildKubeNative(schema, v2, crd.Spec.PreserveUnknownFields)
b.schema = b.buildKubeNative(schema, v2, preserveUnknownFields)
b.listSchema = b.buildListSchema() b.listSchema = b.buildListSchema()
return b return b

View File

@@ -24,8 +24,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
@@ -352,12 +352,12 @@ func TestNewBuilder(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
var schema *structuralschema.Structural var schema *structuralschema.Structural
if len(tt.schema) > 0 { if len(tt.schema) > 0 {
v1beta1Schema := &v1beta1.JSONSchemaProps{} v1beta1Schema := &apiextensions.JSONSchemaProps{}
if err := json.Unmarshal([]byte(tt.schema), &v1beta1Schema); err != nil { if err := json.Unmarshal([]byte(tt.schema), &v1beta1Schema); err != nil {
t.Fatal(err) t.Fatal(err)
} }
internalSchema := &apiextensions.JSONSchemaProps{} internalSchema := &apiextensionsinternal.JSONSchemaProps{}
v1beta1.Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(v1beta1Schema, internalSchema, nil) apiextensions.Convert_v1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(v1beta1Schema, internalSchema, nil)
var err error var err error
schema, err = structuralschema.NewStructural(internalSchema) schema, err = structuralschema.NewStructural(internalSchema)
if err != nil { if err != nil {
@@ -371,8 +371,12 @@ func TestNewBuilder(t *testing.T) {
got := newBuilder(&apiextensions.CustomResourceDefinition{ got := newBuilder(&apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{ Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "bar.k8s.io", Group: "bar.k8s.io",
Version: "v1", Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "v1",
},
},
Names: apiextensions.CustomResourceDefinitionNames{ Names: apiextensions.CustomResourceDefinitionNames{
Plural: "foos", Plural: "foos",
Singular: "foo", Singular: "foo",
@@ -478,12 +482,12 @@ func TestCRDRouteParameterBuilder(t *testing.T) {
Versions: []apiextensions.CustomResourceDefinitionVersion{ Versions: []apiextensions.CustomResourceDefinitionVersion{
{ {
Name: testCRDVersion, 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}) 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) { t.Run(tt.name, func(t *testing.T) {
var validation *apiextensions.CustomResourceValidation var validation *apiextensions.CustomResourceValidation
if len(tt.schema) > 0 { if len(tt.schema) > 0 {
v1beta1Schema := &v1beta1.JSONSchemaProps{} v1Schema := &apiextensions.JSONSchemaProps{}
if err := json.Unmarshal([]byte(tt.schema), &v1beta1Schema); err != nil { if err := json.Unmarshal([]byte(tt.schema), &v1Schema); err != nil {
t.Fatal(err) t.Fatal(err)
} }
internalSchema := &apiextensions.JSONSchemaProps{}
v1beta1.Convert_v1beta1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(v1beta1Schema, internalSchema, nil)
validation = &apiextensions.CustomResourceValidation{ 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 // TODO: mostly copied from the test above. reuse code to cleanup
got, err := BuildSwagger(&apiextensions.CustomResourceDefinition{ got, err := BuildSwagger(&apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{ Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "bar.k8s.io", Group: "bar.k8s.io",
Version: "v1", Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "v1",
Schema: validation,
},
},
Names: apiextensions.CustomResourceDefinitionNames{ Names: apiextensions.CustomResourceDefinitionNames{
Plural: "foos", Plural: "foos",
Singular: "foo", Singular: "foo",
Kind: "Foo", Kind: "Foo",
ListKind: "FooList", ListKind: "FooList",
}, },
Scope: apiextensions.NamespaceScoped, Scope: apiextensions.NamespaceScoped,
Validation: validation,
PreserveUnknownFields: tt.preserveUnknownFields,
}, },
}, "v1", tt.opts) }, "v1", tt.opts)
if err != nil { if err != nil {

View File

@@ -33,9 +33,10 @@ import (
"k8s.io/klog" "k8s.io/klog"
"k8s.io/kube-openapi/pkg/handler" "k8s.io/kube-openapi/pkg/handler"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
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/openapi/builder" "k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder"
) )
@@ -99,7 +100,7 @@ func (c *Controller) Run(staticSpec *spec.Swagger, openAPIService *handler.OpenA
return return
} }
for _, crd := range crds { for _, crd := range crds {
if !apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) { if !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Established) {
continue continue
} }
newSpecs, changed, err := buildVersionSpecs(crd, nil) 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? // 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 { if _, found := c.crdSpecs[name]; !found {
return nil return nil
} }

View File

@@ -34,10 +34,11 @@ import (
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue" "k8s.io/client-go/util/workqueue"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
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"
) )
// This controller is reserving names. To avoid conflicts, be sure to run only one instance of the worker at a time. // 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", Reason: "NotAccepted",
Message: "not all names are accepted", 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 establishedCondition = *old
} }
if establishedCondition.Status != apiextensions.ConditionTrue && namesAcceptedCondition.Status == apiextensions.ConditionTrue { 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 // nothing to do if accepted names and NamesAccepted condition didn't change
if reflect.DeepEqual(inCustomResourceDefinition.Status.AcceptedNames, acceptedNames) && 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 return nil
} }
crd := inCustomResourceDefinition.DeepCopy() crd := inCustomResourceDefinition.DeepCopy()
crd.Status.AcceptedNames = acceptedNames crd.Status.AcceptedNames = acceptedNames
apiextensions.SetCRDCondition(crd, namingCondition) apiextensionshelpers.SetCRDCondition(crd, namingCondition)
apiextensions.SetCRDCondition(crd, establishedCondition) apiextensionshelpers.SetCRDCondition(crd, establishedCondition)
updatedObj, err := c.crdClient.CustomResourceDefinitions().UpdateStatus(crd) updatedObj, err := c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
if apierrors.IsNotFound(err) || apierrors.IsConflict(err) { if apierrors.IsNotFound(err) || apierrors.IsConflict(err) {

View File

@@ -22,8 +22,9 @@ import (
"testing" "testing"
"time" "time"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" 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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache" "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) { if e, a := tc.expectedNames, actualNames; !reflect.DeepEqual(e, a) {
t.Errorf("%v expected %v, got %#v", tc.name, 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) 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) t.Errorf("%v expected %v, got %v", tc.name, e, a)
} }
} }

View File

@@ -39,7 +39,8 @@ import (
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing" 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/apiserver"
"k8s.io/apiextensions-apiserver/pkg/crdserverscheme" "k8s.io/apiextensions-apiserver/pkg/crdserverscheme"
"k8s.io/apiextensions-apiserver/pkg/registry/customresource" "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"} kind := schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "Noxu"}
labelSelectorPath := ".status.labelSelector" labelSelectorPath := ".status.labelSelector"
scale := &apiextensions.CustomResourceSubresourceScale{ scale := &apiextensionsinternal.CustomResourceSubresourceScale{
SpecReplicasPath: ".spec.replicas", SpecReplicasPath: ".spec.replicas",
StatusReplicasPath: ".status.replicas", StatusReplicasPath: ".status.replicas",
LabelSelectorPath: &labelSelectorPath, LabelSelectorPath: &labelSelectorPath,
} }
status := &apiextensions.CustomResourceSubresourceStatus{} status := &apiextensionsinternal.CustomResourceSubresourceStatus{}
headers := []apiextensions.CustomResourceColumnDefinition{ headers := []apiextensions.CustomResourceColumnDefinition{
{Name: "Age", Type: "date", JSONPath: ".metadata.creationTimestamp"}, {Name: "Age", Type: "date", JSONPath: ".metadata.creationTimestamp"},

View File

@@ -23,7 +23,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metatable "k8s.io/apimachinery/pkg/api/meta/table" metatable "k8s.io/apimachinery/pkg/api/meta/table"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

View File

@@ -776,7 +776,7 @@ spec:
if err != nil { if err != nil {
t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v", cond) 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) t.Fatalf("expected violation %q, but got: %v", v, cond.Message)
} }
@@ -845,7 +845,7 @@ spec:
if err != nil { if err != nil {
t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v", cond) 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) t.Fatalf("expected violation %q, but got: %v", v, cond.Message)
} }
} }
@@ -926,12 +926,12 @@ x-kubernetes-embedded-resource: true
type: object type: object
x-kubernetes-embedded-resource: true x-kubernetes-embedded-resource: true
properties: properties:
apiVersion: apiVersion:
type: string type: string
kind: kind:
type: string type: string
metadata: metadata:
type: object type: object
`, `,
expectedViolations: []string{}, expectedViolations: []string{},
}, },
@@ -972,7 +972,7 @@ x-kubernetes-preserve-unknown-fields: true
type: "" type: ""
`, `,
expectedViolations: []string{ 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" type: "integer"
`, `,
expectedViolations: []string{ 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: ` globalSchema: `
type: object type: object
properties: properties:
foo: foo:
type: string type: string
not: not:
type: string type: string
additionalProperties: true additionalProperties: true
title: hello title: hello
description: world description: world
nullable: true nullable: true
allOf: allOf:
- properties: - properties:
foo: foo:
type: string type: string
additionalProperties: true additionalProperties: true
title: hello title: hello
description: world description: world
nullable: true nullable: true
anyOf: anyOf:
- items: - items:
type: string type: string
additionalProperties: true additionalProperties: true
title: hello title: hello
description: world description: world
nullable: true nullable: true
oneOf: oneOf:
- properties: - properties:
foo: foo:
type: string type: string
additionalProperties: true additionalProperties: true
title: hello title: hello
description: world description: world
nullable: true nullable: true
`, `,
expectedViolations: []string{ expectedViolations: []string{
"spec.validation.openAPIV3Schema.anyOf[0].items.type: Forbidden: must be empty to be structural", "spec.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.openAPIV3Schema.not.type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.not.additionalProperties: Forbidden: must be undefined to be structural", "spec.versions[0].schema.openAPIV3Schema.not.additionalProperties: Forbidden: must be undefined to be structural",
"spec.validation.openAPIV3Schema.not.title: Forbidden: must be empty to be structural", "spec.versions[0].schema.openAPIV3Schema.not.title: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.not.description: Forbidden: must be empty to be structural", "spec.versions[0].schema.openAPIV3Schema.not.description: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.not.nullable: Forbidden: must be false to be structural", "spec.versions[0].schema.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.items: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.anyOf[0].items",
}, },
unexpectedViolations: []string{ unexpectedViolations: []string{
"spec.validation.openAPIV3Schema.not.default", "spec.versions[0].schema.openAPIV3Schema.not.default",
}, },
}, },
{ {
@@ -1053,12 +1053,12 @@ oneOf:
globalSchema: ` globalSchema: `
type: object type: object
properties: properties:
foo: foo:
type: string type: string
pattern: "+" pattern: "+"
`, `,
expectedViolations: []string{ 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: ` globalSchema: `
type: object type: object
properties: properties:
int-or-string: int-or-string:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
embedded-resource: embedded-resource:
type: object type: object
x-kubernetes-embedded-resource: true x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
not: not:
properties: properties:
int-or-string: int-or-string:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
embedded-resource: embedded-resource:
x-kubernetes-embedded-resource: true x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
allOf: allOf:
- properties: - properties:
int-or-string: int-or-string:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
embedded-resource: embedded-resource:
x-kubernetes-embedded-resource: true x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
anyOf: anyOf:
- properties: - properties:
int-or-string: int-or-string:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
embedded-resource: embedded-resource:
x-kubernetes-embedded-resource: true x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
oneOf: oneOf:
- properties: - properties:
int-or-string: int-or-string:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
embedded-resource: embedded-resource:
x-kubernetes-embedded-resource: true x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
`, `,
expectedCreateErrors: []string{ expectedCreateErrors: []string{
"spec.validation.openAPIV3Schema.allOf[0].properties[embedded-resource].x-kubernetes-preserve-unknown-fields: Forbidden: must be false to be structural", "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", desc: "missing types with extensions",
globalSchema: ` globalSchema: `
properties: properties:
foo: foo:
properties: properties:
a: {} a: {}
bar: bar:
items: items:
additionalProperties: additionalProperties:
properties: properties:
a: {} a: {}
items: {} items: {}
abc: abc:
additionalProperties: additionalProperties:
properties: properties:
a: a:
items: items:
additionalProperties: additionalProperties:
items: items:
json: json:
x-kubernetes-preserve-unknown-fields: true x-kubernetes-preserve-unknown-fields: true
properties: properties:
a: {} a: {}
int-or-string: int-or-string:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
properties: properties:
a: {} a: {}
`, `,
expectedCreateErrors: []string{ expectedCreateErrors: []string{
"spec.validation.openAPIV3Schema.properties[foo].properties[a].type: Required value: must not be empty for specified object fields", "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", desc: "missing types without extensions",
globalSchema: ` globalSchema: `
properties: properties:
foo: foo:
properties: properties:
a: {} a: {}
bar: bar:
items: items:
additionalProperties: additionalProperties:
properties: properties:
a: {} a: {}
items: {} items: {}
abc: abc:
additionalProperties: additionalProperties:
properties: properties:
a: a:
items: items:
additionalProperties: additionalProperties:
items: items:
`, `,
expectedViolations: []string{ expectedViolations: []string{
"spec.validation.openAPIV3Schema.properties[foo].properties[a].type: Required value: must not be empty for specified object fields", "spec.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.versions[0].schema.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.type: Required value: must not be empty at the root",
}, },
}, },
{ {
@@ -1205,48 +1205,48 @@ properties:
globalSchema: ` globalSchema: `
type: object type: object
properties: properties:
a: a:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
b: b:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
anyOf: anyOf:
- type: integer - type: integer
- type: string - type: string
allOf: allOf:
- pattern: abc - pattern: abc
c: c:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
allOf: allOf:
- anyOf: - anyOf:
- type: integer - type: integer
- type: string - type: string
- pattern: abc - pattern: abc
- pattern: abc - pattern: abc
d: d:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
anyOf: anyOf:
- type: integer - type: integer
- type: string - type: string
pattern: abc pattern: abc
e: e:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
allOf: allOf:
- anyOf: - anyOf:
- type: integer - type: integer
- type: string - type: string
pattern: abc pattern: abc
- pattern: abc - pattern: abc
f: f:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
anyOf: anyOf:
- type: integer - type: integer
- type: string - type: string
- pattern: abc - pattern: abc
g: g:
x-kubernetes-int-or-string: true x-kubernetes-int-or-string: true
anyOf: anyOf:
- type: string - type: string
- type: integer - type: integer
`, `,
expectedCreateErrors: []string{ expectedCreateErrors: []string{
"spec.validation.openAPIV3Schema.properties[d].anyOf[0].type: Forbidden: must be empty to be structural", "spec.validation.openAPIV3Schema.properties[d].anyOf[0].type: Forbidden: must be empty to be structural",
@@ -1271,7 +1271,7 @@ type: object
additionalProperties: false additionalProperties: false
`, `,
expectedViolations: []string{ 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: ` globalSchema: `
type: object type: object
properties: properties:
b: b:
type: object type: object
properties: properties:
b: b:
type: array type: array
c: c:
type: array type: array
items: items:
type: object type: object
d: d:
type: array type: array
not: not:
properties: properties:
a: {} a: {}
b: b:
not: not:
properties: properties:
a: {} a: {}
b: b:
items: {} items: {}
c: c:
items: items:
not: not:
items: items:
properties: properties:
a: {} a: {}
d: d:
items: {} items: {}
allOf: allOf:
- properties: - properties:
e: {} e: {}
anyOf: anyOf:
- properties: - properties:
f: {} f: {}
oneOf: oneOf:
- properties: - properties:
g: {} g: {}
`, `,
expectedViolations: []string{ expectedViolations: []string{
"spec.validation.openAPIV3Schema.properties[d].items: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[d].items", "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.validation.openAPIV3Schema.properties[a]: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[a]", "spec.versions[0].schema.openAPIV3Schema.properties[a]: Required value: because it is defined in spec.versions[0].schema.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.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.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.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.validation.openAPIV3Schema.properties[c].items.items: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[c].items.not.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.validation.openAPIV3Schema.properties[e]: Required value: because it is defined in spec.validation.openAPIV3Schema.allOf[0].properties[e]", "spec.versions[0].schema.openAPIV3Schema.properties[e]: Required value: because it is defined in spec.versions[0].schema.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.versions[0].schema.openAPIV3Schema.properties[f]: Required value: because it is defined in spec.versions[0].schema.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[g]: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[g]",
}, },
}, },
{ {
@@ -1333,62 +1333,62 @@ oneOf:
globalSchema: ` globalSchema: `
type: object type: object
properties: properties:
a: a:
type: string type: string
b: b:
type: object type: object
properties: properties:
a: a:
type: string type: string
b: b:
type: array type: array
items: items:
type: string type: string
c: c:
type: array type: array
items: items:
type: array type: array
items: items:
type: object type: object
properties: properties:
a: a:
type: string type: string
d: d:
type: array type: array
items: items:
type: string type: string
e: e:
type: string type: string
f: f:
type: string type: string
g: g:
type: string type: string
not: not:
properties: properties:
a: {} a: {}
b: b:
not: not:
properties: properties:
a: {} a: {}
b: b:
items: {} items: {}
c: c:
items: items:
not: not:
items: items:
properties: properties:
a: {} a: {}
d: d:
items: {} items: {}
allOf: allOf:
- properties: - properties:
e: {} e: {}
anyOf: anyOf:
- properties: - properties:
f: {} f: {}
oneOf: oneOf:
- properties: - properties:
g: {} g: {}
`, `,
expectedViolations: nil, expectedViolations: nil,
}, },
@@ -1397,16 +1397,16 @@ oneOf:
v1beta1Schema: ` v1beta1Schema: `
type: object type: object
properties: properties:
a: {} a: {}
not: not:
properties: properties:
b: {} b: {}
`, `,
v1Schema: ` v1Schema: `
type: object type: object
properties: properties:
a: a:
type: string type: string
`, `,
expectedViolations: []string{ expectedViolations: []string{
"spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields", "spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields",
@@ -1418,18 +1418,18 @@ properties:
v1beta1Schema: ` v1beta1Schema: `
type: object type: object
properties: properties:
a: {} a: {}
not: not:
properties: properties:
b: {} b: {}
`, `,
v1Schema: ` v1Schema: `
type: object type: object
properties: properties:
c: {} c: {}
not: not:
properties: properties:
d: {} d: {}
`, `,
expectedViolations: []string{ expectedViolations: []string{
"spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields", "spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields",
@@ -1443,12 +1443,12 @@ not:
globalSchema: ` globalSchema: `
type: object type: object
properties: properties:
metadata: metadata:
minimum: 42.0 minimum: 42.0
`, `,
expectedViolations: []string{ expectedViolations: []string{
"spec.validation.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]: 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].type: Required value: must not be empty for specified object fields",
}, },
}, },
{ {
@@ -1456,18 +1456,18 @@ properties:
globalSchema: ` globalSchema: `
type: object type: object
properties: properties:
metadata: metadata:
properties: properties:
name: name:
pattern: "^[a-z]+$" pattern: "^[a-z]+$"
labels: labels:
type: object type: object
maxLength: 4 maxLength: 4
`, `,
expectedViolations: []string{ expectedViolations: []string{
"spec.validation.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]: 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].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].properties[name].type: Required value: must not be empty for specified object fields",
}, },
}, },
{ {
@@ -1475,12 +1475,12 @@ properties:
globalSchema: ` globalSchema: `
type: object type: object
properties: properties:
metadata: metadata:
type: object type: object
properties: properties:
name: name:
type: string type: string
pattern: "^[a-z]+$" pattern: "^[a-z]+$"
`, `,
expectedViolations: []string{}, expectedViolations: []string{},
}, },
@@ -1489,12 +1489,12 @@ properties:
globalSchema: ` globalSchema: `
type: object type: object
properties: properties:
metadata: metadata:
type: object type: object
properties: properties:
generateName: generateName:
type: string type: string
pattern: "^[a-z]+$" pattern: "^[a-z]+$"
`, `,
expectedViolations: []string{}, expectedViolations: []string{},
}, },
@@ -1503,15 +1503,15 @@ properties:
globalSchema: ` globalSchema: `
type: object type: object
properties: properties:
metadata: metadata:
type: object type: object
properties: properties:
name: name:
type: string type: string
pattern: "^[a-z]+$" pattern: "^[a-z]+$"
generateName: generateName:
type: string type: string
pattern: "^[a-z]+$" pattern: "^[a-z]+$"
`, `,
expectedViolations: []string{}, expectedViolations: []string{},
}, },
@@ -1520,30 +1520,30 @@ properties:
globalSchema: ` globalSchema: `
type: object type: object
properties: properties:
metadata: metadata:
type: object type: object
properties: properties:
name: name:
type: string type: string
pattern: "^[a-z]+$" pattern: "^[a-z]+$"
allOf: allOf:
- properties: - properties:
metadata: {} metadata: {}
anyOf: anyOf:
- properties: - properties:
metadata: {} metadata: {}
oneOf: oneOf:
- properties: - properties:
metadata: {} metadata: {}
not: not:
properties: properties:
metadata: {} metadata: {}
`, `,
expectedViolations: []string{ expectedViolations: []string{
"spec.validation.openAPIV3Schema.anyOf[0].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.validation.openAPIV3Schema.allOf[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.validation.openAPIV3Schema.oneOf[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.validation.openAPIV3Schema.not.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: ` globalSchema: `
type: object type: object
properties: properties:
slice: slice:
type: array type: array
`, `,
expectedViolations: []string{ 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: ` globalSchema: `
type: object type: object
properties: properties:
slice: slice:
type: array type: array
items: items:
- type: string - type: string
- type: integer - type: integer
`, `,
expectedCreateErrors: []string{"spec.validation.openAPIV3Schema.properties[slice].items: Forbidden: items must be a schema object and not an array"}, 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: ` globalSchema: `
type: object type: object
properties: properties:
slice: slice:
type: array type: array
items: items:
type: string type: string
not: not:
items: items:
- type: string - type: string
`, `,
expectedCreateErrors: []string{"spec.validation.openAPIV3Schema.properties[slice].not.items: Forbidden: items must be a schema object and not an array"}, expectedCreateErrors: []string{"spec.validation.openAPIV3Schema.properties[slice].not.items: Forbidden: items must be a schema object and not an array"},
}, },