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

View File

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

View File

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

View File

@@ -20,8 +20,10 @@ import (
"fmt"
"net/url"
"strings"
"time"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// IsProtectedCommunityGroup returns whether or not a group specified for a CRD is protected for the community and needs
@@ -54,18 +56,205 @@ const (
// GetAPIApprovalState returns the state of the API approval and reason for that state
func GetAPIApprovalState(annotations map[string]string) (state APIApprovalState, reason string) {
annotation := annotations[v1beta1.KubeAPIApprovedAnnotation]
annotation := annotations[apiextensions.KubeAPIApprovedAnnotation]
// we use the result of this parsing in the switch/case below
url, annotationURLParseErr := url.ParseRequestURI(annotation)
switch {
case len(annotation) == 0:
return APIApprovalMissing, fmt.Sprintf("protected groups must have approval annotation %q, see https://github.com/kubernetes/enhancements/pull/1111", v1beta1.KubeAPIApprovedAnnotation)
return APIApprovalMissing, fmt.Sprintf("protected groups must have approval annotation %q, see https://github.com/kubernetes/enhancements/pull/1111", apiextensions.KubeAPIApprovedAnnotation)
case strings.HasPrefix(annotation, "unapproved"):
return APIApprovalBypassed, fmt.Sprintf("not approved: %q", annotation)
case annotationURLParseErr == nil && url != nil && len(url.Host) > 0 && len(url.Scheme) > 0:
return APIApproved, fmt.Sprintf("approved in %v", annotation)
default:
return APIApprovalInvalid, fmt.Sprintf("protected groups must have approval annotation %q with either a URL or a reason starting with \"unapproved\", see https://github.com/kubernetes/enhancements/pull/1111", v1beta1.KubeAPIApprovedAnnotation)
return APIApprovalInvalid, fmt.Sprintf("protected groups must have approval annotation %q with either a URL or a reason starting with \"unapproved\", see https://github.com/kubernetes/enhancements/pull/1111", apiextensions.KubeAPIApprovedAnnotation)
}
}
// SetCRDCondition sets the status condition. It either overwrites the existing one or creates a new one.
func SetCRDCondition(crd *apiextensions.CustomResourceDefinition, newCondition apiextensions.CustomResourceDefinitionCondition) {
newCondition.LastTransitionTime = metav1.NewTime(time.Now())
existingCondition := FindCRDCondition(crd, newCondition.Type)
if existingCondition == nil {
crd.Status.Conditions = append(crd.Status.Conditions, newCondition)
return
}
if existingCondition.Status != newCondition.Status || existingCondition.LastTransitionTime.IsZero() {
existingCondition.LastTransitionTime = newCondition.LastTransitionTime
}
existingCondition.Status = newCondition.Status
existingCondition.Reason = newCondition.Reason
existingCondition.Message = newCondition.Message
}
// RemoveCRDCondition removes the status condition.
func RemoveCRDCondition(crd *apiextensions.CustomResourceDefinition, conditionType apiextensions.CustomResourceDefinitionConditionType) {
newConditions := []apiextensions.CustomResourceDefinitionCondition{}
for _, condition := range crd.Status.Conditions {
if condition.Type != conditionType {
newConditions = append(newConditions, condition)
}
}
crd.Status.Conditions = newConditions
}
// FindCRDCondition returns the condition you're looking for or nil.
func FindCRDCondition(crd *apiextensions.CustomResourceDefinition, conditionType apiextensions.CustomResourceDefinitionConditionType) *apiextensions.CustomResourceDefinitionCondition {
for i := range crd.Status.Conditions {
if crd.Status.Conditions[i].Type == conditionType {
return &crd.Status.Conditions[i]
}
}
return nil
}
// IsCRDConditionTrue indicates if the condition is present and strictly true.
func IsCRDConditionTrue(crd *apiextensions.CustomResourceDefinition, conditionType apiextensions.CustomResourceDefinitionConditionType) bool {
return IsCRDConditionPresentAndEqual(crd, conditionType, apiextensions.ConditionTrue)
}
// IsCRDConditionFalse indicates if the condition is present and false.
func IsCRDConditionFalse(crd *apiextensions.CustomResourceDefinition, conditionType apiextensions.CustomResourceDefinitionConditionType) bool {
return IsCRDConditionPresentAndEqual(crd, conditionType, apiextensions.ConditionFalse)
}
// IsCRDConditionPresentAndEqual indicates if the condition is present and equal to the given status.
func IsCRDConditionPresentAndEqual(crd *apiextensions.CustomResourceDefinition, conditionType apiextensions.CustomResourceDefinitionConditionType, status apiextensions.ConditionStatus) bool {
for _, condition := range crd.Status.Conditions {
if condition.Type == conditionType {
return condition.Status == status
}
}
return false
}
// IsCRDConditionEquivalent returns true if the lhs and rhs are equivalent except for times.
func IsCRDConditionEquivalent(lhs, rhs *apiextensions.CustomResourceDefinitionCondition) bool {
if lhs == nil && rhs == nil {
return true
}
if lhs == nil || rhs == nil {
return false
}
return lhs.Message == rhs.Message && lhs.Reason == rhs.Reason && lhs.Status == rhs.Status && lhs.Type == rhs.Type
}
// CRDHasFinalizer returns true if the finalizer is in the list.
func CRDHasFinalizer(crd *apiextensions.CustomResourceDefinition, needle string) bool {
for _, finalizer := range crd.Finalizers {
if finalizer == needle {
return true
}
}
return false
}
// CRDRemoveFinalizer removes the finalizer if present.
func CRDRemoveFinalizer(crd *apiextensions.CustomResourceDefinition, needle string) {
newFinalizers := []string{}
for _, finalizer := range crd.Finalizers {
if finalizer != needle {
newFinalizers = append(newFinalizers, finalizer)
}
}
crd.Finalizers = newFinalizers
}
// HasServedCRDVersion returns true if the given version is in the list of CRD's versions and the Served flag is set.
func HasServedCRDVersion(crd *apiextensions.CustomResourceDefinition, version string) bool {
for _, v := range crd.Spec.Versions {
if v.Name == version {
return v.Served
}
}
return false
}
// GetCRDStorageVersion returns the storage version for given CRD.
func GetCRDStorageVersion(crd *apiextensions.CustomResourceDefinition) (string, error) {
for _, v := range crd.Spec.Versions {
if v.Storage {
return v.Name, nil
}
}
// This should not happened if crd is valid
return "", fmt.Errorf("invalid apiextensions.CustomResourceDefinition, no storage version")
}
// IsStoredVersion returns whether the given version is the storage version of the CRD.
func IsStoredVersion(crd *apiextensions.CustomResourceDefinition, version string) bool {
for _, v := range crd.Status.StoredVersions {
if version == v {
return true
}
}
return false
}
// GetSchemaForVersion returns the validation schema for the given version or nil.
func GetSchemaForVersion(crd *apiextensions.CustomResourceDefinition, version string) (*apiextensions.CustomResourceValidation, error) {
for _, v := range crd.Spec.Versions {
if version == v.Name {
return v.Schema, nil
}
}
return nil, fmt.Errorf("version %s not found in apiextensions.CustomResourceDefinition: %v", version, crd.Name)
}
// GetSubresourcesForVersion returns the subresources for given version or nil.
func GetSubresourcesForVersion(crd *apiextensions.CustomResourceDefinition, version string) (*apiextensions.CustomResourceSubresources, error) {
for _, v := range crd.Spec.Versions {
if version == v.Name {
return v.Subresources, nil
}
}
return nil, fmt.Errorf("version %s not found in apiextensions.CustomResourceDefinition: %v", version, crd.Name)
}
// HasPerVersionSchema returns true if a CRD uses per-version schema.
func HasPerVersionSchema(versions []apiextensions.CustomResourceDefinitionVersion) bool {
for _, v := range versions {
if v.Schema != nil {
return true
}
}
return false
}
// HasPerVersionSubresources returns true if a CRD uses per-version subresources.
func HasPerVersionSubresources(versions []apiextensions.CustomResourceDefinitionVersion) bool {
for _, v := range versions {
if v.Subresources != nil {
return true
}
}
return false
}
// HasPerVersionColumns returns true if a CRD uses per-version columns.
func HasPerVersionColumns(versions []apiextensions.CustomResourceDefinitionVersion) bool {
for _, v := range versions {
if len(v.AdditionalPrinterColumns) > 0 {
return true
}
}
return false
}
// HasVersionServed returns true if given CRD has given version served.
func HasVersionServed(crd *apiextensions.CustomResourceDefinition, version string) bool {
for _, v := range crd.Spec.Versions {
if !v.Served || v.Name != version {
continue
}
return true
}
return false
}

View File

@@ -17,9 +17,12 @@ limitations under the License.
package apihelpers
import (
"reflect"
"testing"
"time"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestIsProtectedCommunityGroup(t *testing.T) {
@@ -76,22 +79,22 @@ func TestGetAPIApprovalState(t *testing.T) {
}{
{
name: "bare unapproved",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "unapproved"},
annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "unapproved"},
expected: APIApprovalBypassed,
},
{
name: "unapproved with message",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "unapproved, experimental-only"},
annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "unapproved, experimental-only"},
expected: APIApprovalBypassed,
},
{
name: "mismatched case",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "Unapproved"},
annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "Unapproved"},
expected: APIApprovalInvalid,
},
{
name: "empty",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: ""},
annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: ""},
expected: APIApprovalMissing,
},
{
@@ -101,27 +104,27 @@ func TestGetAPIApprovalState(t *testing.T) {
},
{
name: "url",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "https://github.com/kubernetes/kubernetes/pull/78458"},
annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "https://github.com/kubernetes/kubernetes/pull/78458"},
expected: APIApproved,
},
{
name: "url - no scheme",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "github.com/kubernetes/kubernetes/pull/78458"},
annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "github.com/kubernetes/kubernetes/pull/78458"},
expected: APIApprovalInvalid,
},
{
name: "url - no host",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "http:///kubernetes/kubernetes/pull/78458"},
annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "http:///kubernetes/kubernetes/pull/78458"},
expected: APIApprovalInvalid,
},
{
name: "url - just path",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "/"},
annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "/"},
expected: APIApprovalInvalid,
},
{
name: "missing scheme",
annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: "github.com/kubernetes/kubernetes/pull/78458"},
annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: "github.com/kubernetes/kubernetes/pull/78458"},
expected: APIApprovalInvalid,
},
}
@@ -136,3 +139,461 @@ func TestGetAPIApprovalState(t *testing.T) {
})
}
}
func TestCRDHasFinalizer(t *testing.T) {
tests := []struct {
name string
crd *apiextensions.CustomResourceDefinition
finalizerToCheck string
expected bool
}{
{
name: "missing",
crd: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it"}},
},
finalizerToCheck: "it",
expected: false,
},
{
name: "present",
crd: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it", "it"}},
},
finalizerToCheck: "it",
expected: true,
},
}
for _, tc := range tests {
actual := CRDHasFinalizer(tc.crd, tc.finalizerToCheck)
if tc.expected != actual {
t.Errorf("%v expected %v, got %v", tc.name, tc.expected, actual)
}
}
}
func TestCRDRemoveFinalizer(t *testing.T) {
tests := []struct {
name string
crd *apiextensions.CustomResourceDefinition
finalizerToCheck string
expected []string
}{
{
name: "missing",
crd: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it"}},
},
finalizerToCheck: "it",
expected: []string{"not-it"},
},
{
name: "present",
crd: &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it", "it"}},
},
finalizerToCheck: "it",
expected: []string{"not-it"},
},
}
for _, tc := range tests {
CRDRemoveFinalizer(tc.crd, tc.finalizerToCheck)
if !reflect.DeepEqual(tc.expected, tc.crd.Finalizers) {
t.Errorf("%v expected %v, got %v", tc.name, tc.expected, tc.crd.Finalizers)
}
}
}
func TestSetCRDCondition(t *testing.T) {
tests := []struct {
name string
crdCondition []apiextensions.CustomResourceDefinitionCondition
newCondition apiextensions.CustomResourceDefinitionCondition
expectedcrdCondition []apiextensions.CustomResourceDefinitionCondition
}{
{
name: "test setCRDcondition when one condition",
crdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
newCondition: apiextensions.CustomResourceDefinitionCondition{
Type: apiextensions.Established,
Status: apiextensions.ConditionFalse,
Reason: "NotAccepted",
Message: "Not accepted",
LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
},
expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionFalse,
Reason: "NotAccepted",
Message: "Not accepted",
LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
},
},
},
{
name: "test setCRDcondition when two condition",
crdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
Type: apiextensions.NamesAccepted,
Status: apiextensions.ConditionTrue,
Reason: "NoConflicts",
Message: "no conflicts found",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
newCondition: apiextensions.CustomResourceDefinitionCondition{
Type: apiextensions.NamesAccepted,
Status: apiextensions.ConditionFalse,
Reason: "Conflicts",
Message: "conflicts found",
LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
},
expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
Type: apiextensions.NamesAccepted,
Status: apiextensions.ConditionFalse,
Reason: "Conflicts",
Message: "conflicts found",
LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
},
},
},
{
name: "test setCRDcondition when condition needs to be appended",
crdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
newCondition: apiextensions.CustomResourceDefinitionCondition{
Type: apiextensions.Terminating,
Status: apiextensions.ConditionFalse,
Reason: "Neverapiextensions.Established",
Message: "resource was never apiextensions.Established",
LastTransitionTime: metav1.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
},
expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
Type: apiextensions.Terminating,
Status: apiextensions.ConditionFalse,
Reason: "Neverapiextensions.Established",
Message: "resource was never apiextensions.Established",
LastTransitionTime: metav1.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
},
},
},
{
name: "set new condition which doesn't have lastTransitionTime set",
crdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
newCondition: apiextensions.CustomResourceDefinitionCondition{
Type: apiextensions.Established,
Status: apiextensions.ConditionFalse,
Reason: "NotAccepted",
Message: "Not accepted",
},
expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionFalse,
Reason: "NotAccepted",
Message: "Not accepted",
LastTransitionTime: metav1.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
},
},
},
{
name: "append new condition which doesn't have lastTransitionTime set",
crdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
newCondition: apiextensions.CustomResourceDefinitionCondition{
Type: apiextensions.Terminating,
Status: apiextensions.ConditionFalse,
Reason: "Neverapiextensions.Established",
Message: "resource was never apiextensions.Established",
},
expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
Type: apiextensions.Terminating,
Status: apiextensions.ConditionFalse,
Reason: "Neverapiextensions.Established",
Message: "resource was never apiextensions.Established",
LastTransitionTime: metav1.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
},
},
},
}
for _, tc := range tests {
crd := generateCRDwithCondition(tc.crdCondition)
SetCRDCondition(crd, tc.newCondition)
if len(tc.expectedcrdCondition) != len(crd.Status.Conditions) {
t.Errorf("%v expected %v, got %v", tc.name, tc.expectedcrdCondition, crd.Status.Conditions)
}
for i := range tc.expectedcrdCondition {
if !IsCRDConditionEquivalent(&tc.expectedcrdCondition[i], &crd.Status.Conditions[i]) {
t.Errorf("%v expected %v, got %v", tc.name, tc.expectedcrdCondition[i], crd.Status.Conditions[i])
}
if crd.Status.Conditions[i].LastTransitionTime.IsZero() {
t.Errorf("%q/%d lastTransitionTime should not be null: %v", tc.name, i, crd.Status.Conditions[i])
}
}
}
}
func TestRemoveCRDCondition(t *testing.T) {
tests := []struct {
name string
crdCondition []apiextensions.CustomResourceDefinitionCondition
conditionType apiextensions.CustomResourceDefinitionConditionType
expectedcrdCondition []apiextensions.CustomResourceDefinitionCondition
}{
{
name: "test remove CRDCondition when the conditionType meets",
crdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
Type: apiextensions.NamesAccepted,
Status: apiextensions.ConditionTrue,
Reason: "NoConflicts",
Message: "no conflicts found",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
conditionType: apiextensions.NamesAccepted,
expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2011, 1, 2, 0, 0, 0, 0, time.UTC),
},
},
},
{
name: "test remove CRDCondition when the conditionType not meets",
crdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
Type: apiextensions.NamesAccepted,
Status: apiextensions.ConditionTrue,
Reason: "NoConflicts",
Message: "no conflicts found",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
conditionType: apiextensions.Terminating,
expectedcrdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
Type: apiextensions.NamesAccepted,
Status: apiextensions.ConditionTrue,
Reason: "NoConflicts",
Message: "no conflicts found",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
},
}
for _, tc := range tests {
crd := generateCRDwithCondition(tc.crdCondition)
RemoveCRDCondition(crd, tc.conditionType)
if len(tc.expectedcrdCondition) != len(crd.Status.Conditions) {
t.Errorf("%v expected %v, got %v", tc.name, tc.expectedcrdCondition, crd.Status.Conditions)
}
for i := range tc.expectedcrdCondition {
if !IsCRDConditionEquivalent(&tc.expectedcrdCondition[i], &crd.Status.Conditions[i]) {
t.Errorf("%v expected %v, got %v", tc.name, tc.expectedcrdCondition, crd.Status.Conditions)
}
}
}
}
func TestIsCRDConditionPresentAndEqual(t *testing.T) {
tests := []struct {
name string
crdCondition []apiextensions.CustomResourceDefinitionCondition
conditionType apiextensions.CustomResourceDefinitionConditionType
status apiextensions.ConditionStatus
expectresult bool
}{
{
name: "test CRDCondition is not Present",
crdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
Type: apiextensions.NamesAccepted,
Status: apiextensions.ConditionTrue,
Reason: "NoConflicts",
Message: "no conflicts found",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
conditionType: apiextensions.Terminating,
status: apiextensions.ConditionTrue,
expectresult: false,
},
{
name: "test CRDCondition is Present but not Equal",
crdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
Type: apiextensions.NamesAccepted,
Status: apiextensions.ConditionTrue,
Reason: "NoConflicts",
Message: "no conflicts found",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
conditionType: apiextensions.Established,
status: apiextensions.ConditionFalse,
expectresult: false,
},
{
name: "test CRDCondition is Present and Equal",
crdCondition: []apiextensions.CustomResourceDefinitionCondition{
{
Type: apiextensions.Established,
Status: apiextensions.ConditionTrue,
Reason: "Accepted",
Message: "the initial names have been accepted",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
Type: apiextensions.NamesAccepted,
Status: apiextensions.ConditionTrue,
Reason: "NoConflicts",
Message: "no conflicts found",
LastTransitionTime: metav1.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
conditionType: apiextensions.NamesAccepted,
status: apiextensions.ConditionTrue,
expectresult: true,
},
}
for _, tc := range tests {
crd := generateCRDwithCondition(tc.crdCondition)
res := IsCRDConditionPresentAndEqual(crd, tc.conditionType, tc.status)
if res != tc.expectresult {
t.Errorf("%v expected %t, got %t", tc.name, tc.expectresult, res)
}
}
}
func generateCRDwithCondition(conditions []apiextensions.CustomResourceDefinitionCondition) *apiextensions.CustomResourceDefinition {
testCRDObjectMeta := metav1.ObjectMeta{
Name: "plural.group.com",
ResourceVersion: "12",
}
testCRDSpec := apiextensions.CustomResourceDefinitionSpec{
Group: "group.com",
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "kind",
ListKind: "listkind",
},
}
testCRDAcceptedNames := apiextensions.CustomResourceDefinitionNames{
Plural: "plural",
Singular: "singular",
Kind: "kind",
ListKind: "listkind",
}
return &apiextensions.CustomResourceDefinition{
ObjectMeta: testCRDObjectMeta,
Spec: testCRDSpec,
Status: apiextensions.CustomResourceDefinitionStatus{
AcceptedNames: testCRDAcceptedNames,
Conditions: conditions,
},
}
}

View File

@@ -25,10 +25,10 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
_ "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset"
_ "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
internalinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion"
externalinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
"k8s.io/apiextensions-apiserver/pkg/controller/apiapproval"
"k8s.io/apiextensions-apiserver/pkg/controller/establish"
"k8s.io/apiextensions-apiserver/pkg/controller/finalizer"
@@ -109,7 +109,7 @@ type CustomResourceDefinitions struct {
GenericAPIServer *genericapiserver.GenericAPIServer
// provided for easier embedding
Informers internalinformers.SharedInformerFactory
Informers externalinformers.SharedInformerFactory
}
// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
@@ -164,13 +164,13 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
return nil, err
}
crdClient, err := internalclientset.NewForConfig(s.GenericAPIServer.LoopbackClientConfig)
crdClient, err := clientset.NewForConfig(s.GenericAPIServer.LoopbackClientConfig)
if err != nil {
// it's really bad that this is leaking here, but until we can fix the test (which I'm pretty sure isn't even testing what it wants to test),
// we need to be able to move forward
return nil, fmt.Errorf("failed to create clientset: %v", err)
}
s.Informers = internalinformers.NewSharedInformerFactory(crdClient, 5*time.Minute)
s.Informers = externalinformers.NewSharedInformerFactory(crdClient, 5*time.Minute)
delegateHandler := delegationTarget.UnprotectedHandler()
if delegateHandler == nil {
@@ -185,11 +185,11 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
discovery: map[string]*discovery.APIGroupHandler{},
delegate: delegateHandler,
}
establishingController := establish.NewEstablishingController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions())
establishingController := establish.NewEstablishingController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
crdHandler, err := NewCustomResourceDefinitionHandler(
versionDiscoveryHandler,
groupDiscoveryHandler,
s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(),
s.Informers.Apiextensions().V1().CustomResourceDefinitions(),
delegateHandler,
c.ExtraConfig.CRDRESTOptionsGetter,
c.GenericConfig.AdmissionControl,
@@ -209,18 +209,18 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/apis", crdHandler)
s.GenericAPIServer.Handler.NonGoRestfulMux.HandlePrefix("/apis/", crdHandler)
crdController := NewDiscoveryController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), versionDiscoveryHandler, groupDiscoveryHandler)
namingController := status.NewNamingConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions())
nonStructuralSchemaController := nonstructuralschema.NewConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions())
apiApprovalController := apiapproval.NewKubernetesAPIApprovalPolicyConformantConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions())
crdController := NewDiscoveryController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), versionDiscoveryHandler, groupDiscoveryHandler)
namingController := status.NewNamingConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
nonStructuralSchemaController := nonstructuralschema.NewConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
apiApprovalController := apiapproval.NewKubernetesAPIApprovalPolicyConformantConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
finalizingController := finalizer.NewCRDFinalizer(
s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(),
crdClient.Apiextensions(),
s.Informers.Apiextensions().V1().CustomResourceDefinitions(),
crdClient.ApiextensionsV1(),
crdHandler,
)
var openapiController *openapicontroller.Controller
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourcePublishOpenAPI) {
openapiController = openapicontroller.NewController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions())
openapiController = openapicontroller.NewController(s.Informers.Apiextensions().V1().CustomResourceDefinitions())
}
s.GenericAPIServer.AddPostStartHookOrDie("start-apiextensions-informers", func(context genericapiserver.PostStartHookContext) error {
@@ -249,7 +249,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
// but we won't go healthy until we can handle the ones already present.
s.GenericAPIServer.AddPostStartHookOrDie("crd-informer-synced", func(context genericapiserver.PostStartHookContext) error {
return wait.PollImmediateUntil(100*time.Millisecond, func() (bool, error) {
return s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions().Informer().HasSynced(), nil
return s.Informers.Apiextensions().V1().CustomResourceDefinitions().Informer().HasSynced(), nil
}, context.StopCh)
})

View File

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

View File

@@ -21,7 +21,7 @@ import (
"strings"
"testing"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"

View File

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

View File

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

View File

@@ -29,15 +29,17 @@ import (
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/conversion"
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
structuralpruning "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning"
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/controller/establish"
"k8s.io/apiextensions-apiserver/pkg/controller/finalizer"
"k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder"
@@ -287,7 +289,7 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
if !apiextensions.HasServedCRDVersion(crd, requestInfo.APIVersion) {
if !apiextensionshelpers.HasServedCRDVersion(crd, requestInfo.APIVersion) {
r.delegate.ServeHTTP(w, req)
return
}
@@ -296,13 +298,13 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// but it becomes "unserved" because another names update leads to a conflict
// and EstablishingController wasn't fast enough to put the CRD into the Established condition.
// We accept this as the problem is small and self-healing.
if !apiextensions.IsCRDConditionTrue(crd, apiextensions.NamesAccepted) &&
!apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) {
if !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.NamesAccepted) &&
!apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Established) {
r.delegate.ServeHTTP(w, req)
return
}
terminating := apiextensions.IsCRDConditionTrue(crd, apiextensions.Terminating)
terminating := apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Terminating)
crdInfo, err := r.getOrCreateServingInfoFor(crd.UID, crd.Name)
if apierrors.IsNotFound(err) {
@@ -335,7 +337,7 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
var handlerFunc http.HandlerFunc
subresources, err := apiextensions.GetSubresourcesForVersion(crd, requestInfo.APIVersion)
subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, requestInfo.APIVersion)
if err != nil {
utilruntime.HandleError(err)
responsewriters.ErrorNegotiated(
@@ -467,8 +469,8 @@ func (r *crdHandler) updateCustomResourceDefinition(oldObj, newObj interface{})
// For HA clusters, we want to prevent race conditions when changing status to Established,
// so we want to be sure that CRD is Installing at least for 5 seconds before Establishing it.
// TODO: find a real HA safe checkpointing mechanism instead of an arbitrary wait.
if !apiextensions.IsCRDConditionTrue(newCRD, apiextensions.Established) &&
apiextensions.IsCRDConditionTrue(newCRD, apiextensions.NamesAccepted) {
if !apiextensionshelpers.IsCRDConditionTrue(newCRD, apiextensions.Established) &&
apiextensionshelpers.IsCRDConditionTrue(newCRD, apiextensions.NamesAccepted) {
if r.masterCount > 1 {
r.establishingController.QueueCRD(newCRD.Name, 5*time.Second)
} else {
@@ -607,7 +609,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
return ret, nil
}
storageVersion, err := apiextensions.GetCRDStorageVersion(crd)
storageVersion, err := apiextensionshelpers.GetCRDStorageVersion(crd)
if err != nil {
return nil, err
}
@@ -622,7 +624,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
structuralSchemas := map[string]*structuralschema.Structural{}
for _, v := range crd.Spec.Versions {
val, err := apiextensions.GetSchemaForVersion(crd, v.Name)
val, err := apiextensionshelpers.GetSchemaForVersion(crd, v.Name)
if err != nil {
utilruntime.HandleError(err)
return nil, fmt.Errorf("the server could not properly serve the CR schema")
@@ -630,14 +632,18 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
if val == nil {
continue
}
s, err := structuralschema.NewStructural(val.OpenAPIV3Schema)
if *crd.Spec.PreserveUnknownFields == false && err != nil {
internalValidation := &apiextensionsinternal.CustomResourceValidation{}
if err := apiextensions.Convert_v1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(val, internalValidation, nil); err != nil {
return nil, fmt.Errorf("failed converting CRD validation to internal version: %v", err)
}
s, err := structuralschema.NewStructural(internalValidation.OpenAPIV3Schema)
if crd.Spec.PreserveUnknownFields == false && err != nil {
// This should never happen. If it does, it is a programming error.
utilruntime.HandleError(fmt.Errorf("failed to convert schema to structural: %v", err))
return nil, fmt.Errorf("the server could not properly serve the CR schema") // validation should avoid this
}
if *crd.Spec.PreserveUnknownFields == false {
if crd.Spec.PreserveUnknownFields == false {
// we don't own s completely, e.g. defaults are not deep-copied. So better make a copy here.
s = s.DeepCopy()
@@ -679,36 +685,39 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
typer := newUnstructuredObjectTyper(parameterScheme)
creator := unstructuredCreator{}
validationSchema, err := apiextensions.GetSchemaForVersion(crd, v.Name)
validationSchema, err := apiextensionshelpers.GetSchemaForVersion(crd, v.Name)
if err != nil {
utilruntime.HandleError(err)
return nil, fmt.Errorf("the server could not properly serve the CR schema")
}
validator, _, err := apiservervalidation.NewSchemaValidator(validationSchema)
var internalValidationSchema *apiextensionsinternal.CustomResourceValidation
if validationSchema != nil {
internalValidationSchema = &apiextensionsinternal.CustomResourceValidation{}
if err := apiextensions.Convert_v1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(validationSchema, internalValidationSchema, nil); err != nil {
return nil, fmt.Errorf("failed to convert CRD validation to internal version: %v", err)
}
}
validator, _, err := apiservervalidation.NewSchemaValidator(internalValidationSchema)
if err != nil {
return nil, err
}
// Check for nil because we dereference this throughout the handler code.
// Note: we always default this to non-nil. But we should guard these dereferences any way.
if crd.Spec.PreserveUnknownFields == nil {
return nil, fmt.Errorf("unexpected nil spec.preserveUnknownFields in the CustomResourceDefinition")
}
var statusSpec *apiextensions.CustomResourceSubresourceStatus
var statusSpec *apiextensionsinternal.CustomResourceSubresourceStatus
var statusValidator *validate.SchemaValidator
subresources, err := apiextensions.GetSubresourcesForVersion(crd, v.Name)
subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, v.Name)
if err != nil {
utilruntime.HandleError(err)
return nil, fmt.Errorf("the server could not properly serve the CR subresources")
}
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Status != nil {
equivalentResourceRegistry.RegisterKindFor(resource, "status", kind)
statusSpec = subresources.Status
statusSpec = &apiextensionsinternal.CustomResourceSubresourceStatus{}
if err := apiextensions.Convert_v1_CustomResourceSubresourceStatus_To_apiextensions_CustomResourceSubresourceStatus(subresources.Status, statusSpec, nil); err != nil {
return nil, fmt.Errorf("failed converting CRD status subresource to internal version: %v", err)
}
// for the status subresource, validate only against the status schema
if validationSchema != nil && validationSchema.OpenAPIV3Schema != nil && validationSchema.OpenAPIV3Schema.Properties != nil {
if statusSchema, ok := validationSchema.OpenAPIV3Schema.Properties["status"]; ok {
if internalValidationSchema != nil && internalValidationSchema.OpenAPIV3Schema != nil && internalValidationSchema.OpenAPIV3Schema.Properties != nil {
if statusSchema, ok := internalValidationSchema.OpenAPIV3Schema.Properties["status"]; ok {
openapiSchema := &spec.Schema{}
if err := apiservervalidation.ConvertJSONSchemaPropsWithPostProcess(&statusSchema, openapiSchema, apiservervalidation.StripUnsupportedFormatsPostProcess); err != nil {
return nil, err
@@ -718,14 +727,16 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
}
}
var scaleSpec *apiextensions.CustomResourceSubresourceScale
var scaleSpec *apiextensionsinternal.CustomResourceSubresourceScale
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Scale != nil {
equivalentResourceRegistry.RegisterKindFor(resource, "scale", autoscalingv1.SchemeGroupVersion.WithKind("Scale"))
scaleSpec = subresources.Scale
scaleSpec = &apiextensionsinternal.CustomResourceSubresourceScale{}
if err := apiextensions.Convert_v1_CustomResourceSubresourceScale_To_apiextensions_CustomResourceSubresourceScale(subresources.Scale, scaleSpec, nil); err != nil {
return nil, fmt.Errorf("failed converting CRD status subresource to internal version: %v", err)
}
}
columns, err := apiextensions.GetColumnsForVersion(crd, v.Name)
columns, err := getColumnsForVersion(crd, v.Name)
if err != nil {
utilruntime.HandleError(err)
return nil, fmt.Errorf("the server could not properly serve the CR columns")
@@ -756,7 +767,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
encoderVersion: schema.GroupVersion{Group: crd.Spec.Group, Version: storageVersion},
structuralSchemas: structuralSchemas,
structuralSchemaGK: kind.GroupKind(),
preserveUnknownFields: *crd.Spec.PreserveUnknownFields,
preserveUnknownFields: crd.Spec.PreserveUnknownFields,
},
crd.Status.AcceptedNames.Categories,
table,
@@ -779,7 +790,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
converter: safeConverter,
structuralSchemas: structuralSchemas,
structuralSchemaGK: kind.GroupKind(),
preserveUnknownFields: *crd.Spec.PreserveUnknownFields,
preserveUnknownFields: crd.Spec.PreserveUnknownFields,
}
var standardSerializers []runtime.SerializerInfo
for _, s := range negotiatedSerializer.SupportedMediaTypes() {
@@ -830,7 +841,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
reqScope.Creater,
reqScope.Kind,
reqScope.HubGroupVersion,
*crd.Spec.PreserveUnknownFields,
crd.Spec.PreserveUnknownFields,
)
if err != nil {
return nil, err

View File

@@ -25,9 +25,9 @@ import (
"sigs.k8s.io/yaml"
"testing"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/conversion"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/protobuf"

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

View File

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

View File

@@ -19,8 +19,7 @@ package apiapproval
import (
"testing"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -89,7 +88,7 @@ func TestCalculateCondition(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
crd := &apiextensions.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{v1beta1.KubeAPIApprovedAnnotation: test.annotationValue}},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{apiextensions.KubeAPIApprovedAnnotation: test.annotationValue}},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: test.group,
},

View File

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

View File

@@ -36,10 +36,11 @@ import (
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
client "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
)
// OverlappingBuiltInResources returns the set of built-in group/resources that are persisted
@@ -113,14 +114,14 @@ func (c *CRDFinalizer) sync(key string) error {
}
// no work to do
if cachedCRD.DeletionTimestamp.IsZero() || !apiextensions.CRDHasFinalizer(cachedCRD, apiextensions.CustomResourceCleanupFinalizer) {
if cachedCRD.DeletionTimestamp.IsZero() || !apiextensionshelpers.CRDHasFinalizer(cachedCRD, apiextensions.CustomResourceCleanupFinalizer) {
return nil
}
crd := cachedCRD.DeepCopy()
// update the status condition. This cleanup could take a while.
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
apiextensionshelpers.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
Type: apiextensions.Terminating,
Status: apiextensions.ConditionTrue,
Reason: "InstanceDeletionInProgress",
@@ -139,15 +140,15 @@ func (c *CRDFinalizer) sync(key string) error {
// Since we control the endpoints, we know that delete collection works. No need to delete if not established.
if OverlappingBuiltInResources()[schema.GroupResource{Group: crd.Spec.Group, Resource: crd.Spec.Names.Plural}] {
// Skip deletion, explain why, and proceed to remove the finalizer and delete the CRD
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
apiextensionshelpers.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
Type: apiextensions.Terminating,
Status: apiextensions.ConditionFalse,
Reason: "OverlappingBuiltInResource",
Message: "instances overlap with built-in resources in storage",
})
} else if apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) {
} else if apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Established) {
cond, deleteErr := c.deleteInstances(crd)
apiextensions.SetCRDCondition(crd, cond)
apiextensionshelpers.SetCRDCondition(crd, cond)
if deleteErr != nil {
if _, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd); err != nil {
utilruntime.HandleError(err)
@@ -155,7 +156,7 @@ func (c *CRDFinalizer) sync(key string) error {
return deleteErr
}
} else {
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
apiextensionshelpers.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
Type: apiextensions.Terminating,
Status: apiextensions.ConditionFalse,
Reason: "NeverEstablished",
@@ -163,7 +164,7 @@ func (c *CRDFinalizer) sync(key string) error {
})
}
apiextensions.CRDRemoveFinalizer(crd, apiextensions.CustomResourceCleanupFinalizer)
apiextensionshelpers.CRDRemoveFinalizer(crd, apiextensions.CustomResourceCleanupFinalizer)
_, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
if apierrors.IsNotFound(err) || apierrors.IsConflict(err) {
// deleted or changed in the meantime, we'll get called again
@@ -312,7 +313,7 @@ func (c *CRDFinalizer) enqueue(obj *apiextensions.CustomResourceDefinition) {
func (c *CRDFinalizer) addCustomResourceDefinition(obj interface{}) {
castObj := obj.(*apiextensions.CustomResourceDefinition)
// only queue deleted things
if !castObj.DeletionTimestamp.IsZero() && apiextensions.CRDHasFinalizer(castObj, apiextensions.CustomResourceCleanupFinalizer) {
if !castObj.DeletionTimestamp.IsZero() && apiextensionshelpers.CRDHasFinalizer(castObj, apiextensions.CustomResourceCleanupFinalizer) {
c.enqueue(castObj)
}
}
@@ -321,7 +322,7 @@ func (c *CRDFinalizer) updateCustomResourceDefinition(oldObj, newObj interface{}
oldCRD := oldObj.(*apiextensions.CustomResourceDefinition)
newCRD := newObj.(*apiextensions.CustomResourceDefinition)
// only queue deleted things that haven't been finalized by us
if newCRD.DeletionTimestamp.IsZero() || !apiextensions.CRDHasFinalizer(newCRD, apiextensions.CustomResourceCleanupFinalizer) {
if newCRD.DeletionTimestamp.IsZero() || !apiextensionshelpers.CRDHasFinalizer(newCRD, apiextensions.CustomResourceCleanupFinalizer) {
return
}
@@ -339,8 +340,8 @@ func (c *CRDFinalizer) updateCustomResourceDefinition(oldObj, newObj interface{}
newCopy := newCRD.DeepCopy()
oldCopy.ResourceVersion = ""
newCopy.ResourceVersion = ""
apiextensions.RemoveCRDCondition(oldCopy, apiextensions.Terminating)
apiextensions.RemoveCRDCondition(newCopy, apiextensions.Terminating)
apiextensionshelpers.RemoveCRDCondition(oldCopy, apiextensions.Terminating)
apiextensionshelpers.RemoveCRDCondition(newCopy, apiextensions.Terminating)
if !reflect.DeepEqual(oldCopy, newCopy) {
c.enqueue(newCRD)

View File

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

View File

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

View File

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

View File

@@ -33,9 +33,10 @@ import (
"k8s.io/klog"
"k8s.io/kube-openapi/pkg/handler"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder"
)
@@ -99,7 +100,7 @@ func (c *Controller) Run(staticSpec *spec.Swagger, openAPIService *handler.OpenA
return
}
for _, crd := range crds {
if !apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) {
if !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Established) {
continue
}
newSpecs, changed, err := buildVersionSpecs(crd, nil)
@@ -163,7 +164,7 @@ func (c *Controller) sync(name string) error {
}
// do we have to remove all specs of this CRD?
if errors.IsNotFound(err) || !apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) {
if errors.IsNotFound(err) || !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensions.Established) {
if _, found := c.crdSpecs[name]; !found {
return nil
}

View File

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

View File

@@ -22,8 +22,9 @@ import (
"testing"
"time"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"
)
@@ -334,10 +335,10 @@ func TestSync(t *testing.T) {
if e, a := tc.expectedNames, actualNames; !reflect.DeepEqual(e, a) {
t.Errorf("%v expected %v, got %#v", tc.name, e, a)
}
if e, a := tc.expectedNameConflictCondition, actualNameConflictCondition; !apiextensions.IsCRDConditionEquivalent(&e, &a) {
if e, a := tc.expectedNameConflictCondition, actualNameConflictCondition; !apiextensionshelpers.IsCRDConditionEquivalent(&e, &a) {
t.Errorf("%v expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expectedEstablishedCondition, establishedCondition; !apiextensions.IsCRDConditionEquivalent(&e, &a) {
if e, a := tc.expectedEstablishedCondition, establishedCondition; !apiextensionshelpers.IsCRDConditionEquivalent(&e, &a) {
t.Errorf("%v expected %v, got %v", tc.name, e, a)
}
}

View File

@@ -39,7 +39,8 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver"
"k8s.io/apiextensions-apiserver/pkg/crdserverscheme"
"k8s.io/apiextensions-apiserver/pkg/registry/customresource"
@@ -67,13 +68,13 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcd3testi
kind := schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "Noxu"}
labelSelectorPath := ".status.labelSelector"
scale := &apiextensions.CustomResourceSubresourceScale{
scale := &apiextensionsinternal.CustomResourceSubresourceScale{
SpecReplicasPath: ".spec.replicas",
StatusReplicasPath: ".status.replicas",
LabelSelectorPath: &labelSelectorPath,
}
status := &apiextensions.CustomResourceSubresourceStatus{}
status := &apiextensionsinternal.CustomResourceSubresourceStatus{}
headers := []apiextensions.CustomResourceColumnDefinition{
{Name: "Age", Type: "date", JSONPath: ".metadata.creationTimestamp"},

View File

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

View File

@@ -776,7 +776,7 @@ spec:
if err != nil {
t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v", cond)
}
if v := "spec.validation.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields"; !strings.Contains(cond.Message, v) {
if v := "spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields"; !strings.Contains(cond.Message, v) {
t.Fatalf("expected violation %q, but got: %v", v, cond.Message)
}
@@ -845,7 +845,7 @@ spec:
if err != nil {
t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v", cond)
}
if v := "spec.validation.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields"; !strings.Contains(cond.Message, v) {
if v := "spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields"; !strings.Contains(cond.Message, v) {
t.Fatalf("expected violation %q, but got: %v", v, cond.Message)
}
}
@@ -926,12 +926,12 @@ x-kubernetes-embedded-resource: true
type: object
x-kubernetes-embedded-resource: true
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
apiVersion:
type: string
kind:
type: string
metadata:
type: object
`,
expectedViolations: []string{},
},
@@ -972,7 +972,7 @@ x-kubernetes-preserve-unknown-fields: true
type: ""
`,
expectedViolations: []string{
"spec.validation.openAPIV3Schema.type: Required value: must not be empty at the root",
"spec.versions[0].schema.openAPIV3Schema.type: Required value: must not be empty at the root",
},
},
{
@@ -981,7 +981,7 @@ type: ""
type: "integer"
`,
expectedViolations: []string{
"spec.validation.openAPIV3Schema.type: Invalid value: \"integer\": must be object at the root",
"spec.versions[0].schema.openAPIV3Schema.type: Invalid value: \"integer\": must be object at the root",
},
},
{
@@ -989,63 +989,63 @@ type: "integer"
globalSchema: `
type: object
properties:
foo:
type: string
foo:
type: string
not:
type: string
additionalProperties: true
title: hello
description: world
nullable: true
type: string
additionalProperties: true
title: hello
description: world
nullable: true
allOf:
- properties:
foo:
type: string
additionalProperties: true
title: hello
description: world
nullable: true
foo:
type: string
additionalProperties: true
title: hello
description: world
nullable: true
anyOf:
- items:
type: string
additionalProperties: true
title: hello
description: world
nullable: true
type: string
additionalProperties: true
title: hello
description: world
nullable: true
oneOf:
- properties:
foo:
type: string
additionalProperties: true
title: hello
description: world
nullable: true
foo:
type: string
additionalProperties: true
title: hello
description: world
nullable: true
`,
expectedViolations: []string{
"spec.validation.openAPIV3Schema.anyOf[0].items.type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.anyOf[0].items.additionalProperties: Forbidden: must be undefined to be structural",
"spec.validation.openAPIV3Schema.anyOf[0].items.title: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.anyOf[0].items.description: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.anyOf[0].items.nullable: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.allOf[0].properties[foo].type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.allOf[0].properties[foo].additionalProperties: Forbidden: must be undefined to be structural",
"spec.validation.openAPIV3Schema.allOf[0].properties[foo].title: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.allOf[0].properties[foo].description: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.allOf[0].properties[foo].nullable: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[foo].type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[foo].additionalProperties: Forbidden: must be undefined to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[foo].title: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[foo].description: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.oneOf[0].properties[foo].nullable: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.not.type: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.not.additionalProperties: Forbidden: must be undefined to be structural",
"spec.validation.openAPIV3Schema.not.title: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.not.description: Forbidden: must be empty to be structural",
"spec.validation.openAPIV3Schema.not.nullable: Forbidden: must be false to be structural",
"spec.validation.openAPIV3Schema.items: Required value: because it is defined in spec.validation.openAPIV3Schema.anyOf[0].items",
"spec.versions[0].schema.openAPIV3Schema.anyOf[0].items.type: Forbidden: must be empty to be structural",
"spec.versions[0].schema.openAPIV3Schema.anyOf[0].items.additionalProperties: Forbidden: must be undefined to be structural",
"spec.versions[0].schema.openAPIV3Schema.anyOf[0].items.title: Forbidden: must be empty to be structural",
"spec.versions[0].schema.openAPIV3Schema.anyOf[0].items.description: Forbidden: must be empty to be structural",
"spec.versions[0].schema.openAPIV3Schema.anyOf[0].items.nullable: Forbidden: must be false to be structural",
"spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[foo].type: Forbidden: must be empty to be structural",
"spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[foo].additionalProperties: Forbidden: must be undefined to be structural",
"spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[foo].title: Forbidden: must be empty to be structural",
"spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[foo].description: Forbidden: must be empty to be structural",
"spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[foo].nullable: Forbidden: must be false to be structural",
"spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[foo].type: Forbidden: must be empty to be structural",
"spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[foo].additionalProperties: Forbidden: must be undefined to be structural",
"spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[foo].title: Forbidden: must be empty to be structural",
"spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[foo].description: Forbidden: must be empty to be structural",
"spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[foo].nullable: Forbidden: must be false to be structural",
"spec.versions[0].schema.openAPIV3Schema.not.type: Forbidden: must be empty to be structural",
"spec.versions[0].schema.openAPIV3Schema.not.additionalProperties: Forbidden: must be undefined to be structural",
"spec.versions[0].schema.openAPIV3Schema.not.title: Forbidden: must be empty to be structural",
"spec.versions[0].schema.openAPIV3Schema.not.description: Forbidden: must be empty to be structural",
"spec.versions[0].schema.openAPIV3Schema.not.nullable: Forbidden: must be false to be structural",
"spec.versions[0].schema.openAPIV3Schema.items: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.anyOf[0].items",
},
unexpectedViolations: []string{
"spec.validation.openAPIV3Schema.not.default",
"spec.versions[0].schema.openAPIV3Schema.not.default",
},
},
{
@@ -1053,12 +1053,12 @@ oneOf:
globalSchema: `
type: object
properties:
foo:
type: string
pattern: "+"
foo:
type: string
pattern: "+"
`,
expectedViolations: []string{
"spec.validation.openAPIV3Schema.properties[foo].pattern: Invalid value: \"+\": must be a valid regular expression, but isn't: error parsing regexp: missing argument to repetition operator: `+`",
"spec.versions[0].schema.openAPIV3Schema.properties[foo].pattern: Invalid value: \"+\": must be a valid regular expression, but isn't: error parsing regexp: missing argument to repetition operator: `+`",
},
},
{
@@ -1066,40 +1066,40 @@ properties:
globalSchema: `
type: object
properties:
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
not:
properties:
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
properties:
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
allOf:
- properties:
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
anyOf:
- properties:
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
oneOf:
- properties:
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
int-or-string:
x-kubernetes-int-or-string: true
embedded-resource:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
`,
expectedCreateErrors: []string{
"spec.validation.openAPIV3Schema.allOf[0].properties[embedded-resource].x-kubernetes-preserve-unknown-fields: Forbidden: must be false to be structural",
@@ -1120,30 +1120,30 @@ oneOf:
desc: "missing types with extensions",
globalSchema: `
properties:
foo:
properties:
a: {}
bar:
items:
additionalProperties:
properties:
a: {}
items: {}
abc:
additionalProperties:
properties:
a:
items:
additionalProperties:
items:
json:
x-kubernetes-preserve-unknown-fields: true
properties:
a: {}
int-or-string:
x-kubernetes-int-or-string: true
properties:
a: {}
foo:
properties:
a: {}
bar:
items:
additionalProperties:
properties:
a: {}
items: {}
abc:
additionalProperties:
properties:
a:
items:
additionalProperties:
items:
json:
x-kubernetes-preserve-unknown-fields: true
properties:
a: {}
int-or-string:
x-kubernetes-int-or-string: true
properties:
a: {}
`,
expectedCreateErrors: []string{
"spec.validation.openAPIV3Schema.properties[foo].properties[a].type: Required value: must not be empty for specified object fields",
@@ -1167,37 +1167,37 @@ properties:
desc: "missing types without extensions",
globalSchema: `
properties:
foo:
properties:
a: {}
bar:
items:
additionalProperties:
properties:
a: {}
items: {}
abc:
additionalProperties:
properties:
a:
items:
additionalProperties:
items:
foo:
properties:
a: {}
bar:
items:
additionalProperties:
properties:
a: {}
items: {}
abc:
additionalProperties:
properties:
a:
items:
additionalProperties:
items:
`,
expectedViolations: []string{
"spec.validation.openAPIV3Schema.properties[foo].properties[a].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[foo].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[abc].additionalProperties.properties[a].items.additionalProperties.type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[abc].additionalProperties.properties[a].items.type: Required value: must not be empty for specified array items",
"spec.validation.openAPIV3Schema.properties[abc].additionalProperties.properties[a].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[abc].additionalProperties.type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[abc].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[bar].items.additionalProperties.items.type: Required value: must not be empty for specified array items",
"spec.validation.openAPIV3Schema.properties[bar].items.additionalProperties.properties[a].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[bar].items.additionalProperties.type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[bar].items.type: Required value: must not be empty for specified array items",
"spec.validation.openAPIV3Schema.properties[bar].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.type: Required value: must not be empty at the root",
"spec.versions[0].schema.openAPIV3Schema.properties[foo].properties[a].type: Required value: must not be empty for specified object fields",
"spec.versions[0].schema.openAPIV3Schema.properties[foo].type: Required value: must not be empty for specified object fields",
"spec.versions[0].schema.openAPIV3Schema.properties[abc].additionalProperties.properties[a].items.additionalProperties.type: Required value: must not be empty for specified object fields",
"spec.versions[0].schema.openAPIV3Schema.properties[abc].additionalProperties.properties[a].items.type: Required value: must not be empty for specified array items",
"spec.versions[0].schema.openAPIV3Schema.properties[abc].additionalProperties.properties[a].type: Required value: must not be empty for specified object fields",
"spec.versions[0].schema.openAPIV3Schema.properties[abc].additionalProperties.type: Required value: must not be empty for specified object fields",
"spec.versions[0].schema.openAPIV3Schema.properties[abc].type: Required value: must not be empty for specified object fields",
"spec.versions[0].schema.openAPIV3Schema.properties[bar].items.additionalProperties.items.type: Required value: must not be empty for specified array items",
"spec.versions[0].schema.openAPIV3Schema.properties[bar].items.additionalProperties.properties[a].type: Required value: must not be empty for specified object fields",
"spec.versions[0].schema.openAPIV3Schema.properties[bar].items.additionalProperties.type: Required value: must not be empty for specified object fields",
"spec.versions[0].schema.openAPIV3Schema.properties[bar].items.type: Required value: must not be empty for specified array items",
"spec.versions[0].schema.openAPIV3Schema.properties[bar].type: Required value: must not be empty for specified object fields",
"spec.versions[0].schema.openAPIV3Schema.type: Required value: must not be empty at the root",
},
},
{
@@ -1205,48 +1205,48 @@ properties:
globalSchema: `
type: object
properties:
a:
x-kubernetes-int-or-string: true
b:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
allOf:
- pattern: abc
c:
x-kubernetes-int-or-string: true
allOf:
- anyOf:
- type: integer
- type: string
- pattern: abc
- pattern: abc
d:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
pattern: abc
e:
x-kubernetes-int-or-string: true
allOf:
- anyOf:
- type: integer
- type: string
pattern: abc
- pattern: abc
f:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
- pattern: abc
g:
x-kubernetes-int-or-string: true
anyOf:
- type: string
- type: integer
a:
x-kubernetes-int-or-string: true
b:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
allOf:
- pattern: abc
c:
x-kubernetes-int-or-string: true
allOf:
- anyOf:
- type: integer
- type: string
- pattern: abc
- pattern: abc
d:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
pattern: abc
e:
x-kubernetes-int-or-string: true
allOf:
- anyOf:
- type: integer
- type: string
pattern: abc
- pattern: abc
f:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
- pattern: abc
g:
x-kubernetes-int-or-string: true
anyOf:
- type: string
- type: integer
`,
expectedCreateErrors: []string{
"spec.validation.openAPIV3Schema.properties[d].anyOf[0].type: Forbidden: must be empty to be structural",
@@ -1271,7 +1271,7 @@ type: object
additionalProperties: false
`,
expectedViolations: []string{
"spec.validation.openAPIV3Schema.additionalProperties: Forbidden: must not be used at the root",
"spec.versions[0].schema.openAPIV3Schema.additionalProperties: Forbidden: must not be used at the root",
},
},
{
@@ -1279,53 +1279,53 @@ additionalProperties: false
globalSchema: `
type: object
properties:
b:
type: object
properties:
b:
type: array
c:
type: array
items:
type: object
d:
type: array
b:
type: object
properties:
b:
type: array
c:
type: array
items:
type: object
d:
type: array
not:
properties:
a: {}
b:
not:
properties:
a: {}
b:
items: {}
c:
items:
not:
items:
properties:
a: {}
d:
items: {}
properties:
a: {}
b:
not:
properties:
a: {}
b:
items: {}
c:
items:
not:
items:
properties:
a: {}
d:
items: {}
allOf:
- properties:
e: {}
e: {}
anyOf:
- properties:
f: {}
f: {}
oneOf:
- properties:
g: {}
g: {}
`,
expectedViolations: []string{
"spec.validation.openAPIV3Schema.properties[d].items: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[d].items",
"spec.validation.openAPIV3Schema.properties[a]: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[a]",
"spec.validation.openAPIV3Schema.properties[b].properties[a]: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[b].not.properties[a]",
"spec.validation.openAPIV3Schema.properties[b].properties[b].items: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[b].not.properties[b].items",
"spec.validation.openAPIV3Schema.properties[c].items.items: Required value: because it is defined in spec.validation.openAPIV3Schema.not.properties[c].items.not.items",
"spec.validation.openAPIV3Schema.properties[e]: Required value: because it is defined in spec.validation.openAPIV3Schema.allOf[0].properties[e]",
"spec.validation.openAPIV3Schema.properties[f]: Required value: because it is defined in spec.validation.openAPIV3Schema.anyOf[0].properties[f]",
"spec.validation.openAPIV3Schema.properties[g]: Required value: because it is defined in spec.validation.openAPIV3Schema.oneOf[0].properties[g]",
"spec.versions[0].schema.openAPIV3Schema.properties[d].items: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.not.properties[d].items",
"spec.versions[0].schema.openAPIV3Schema.properties[a]: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.not.properties[a]",
"spec.versions[0].schema.openAPIV3Schema.properties[b].properties[a]: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.not.properties[b].not.properties[a]",
"spec.versions[0].schema.openAPIV3Schema.properties[b].properties[b].items: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.not.properties[b].not.properties[b].items",
"spec.versions[0].schema.openAPIV3Schema.properties[c].items.items: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.not.properties[c].items.not.items",
"spec.versions[0].schema.openAPIV3Schema.properties[e]: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[e]",
"spec.versions[0].schema.openAPIV3Schema.properties[f]: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.anyOf[0].properties[f]",
"spec.versions[0].schema.openAPIV3Schema.properties[g]: Required value: because it is defined in spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[g]",
},
},
{
@@ -1333,62 +1333,62 @@ oneOf:
globalSchema: `
type: object
properties:
a:
type: string
b:
type: object
properties:
a:
type: string
b:
type: array
items:
type: string
c:
type: array
items:
type: array
items:
type: object
properties:
a:
type: string
d:
type: array
items:
type: string
e:
type: string
f:
type: string
g:
type: string
a:
type: string
b:
type: object
properties:
a:
type: string
b:
type: array
items:
type: string
c:
type: array
items:
type: array
items:
type: object
properties:
a:
type: string
d:
type: array
items:
type: string
e:
type: string
f:
type: string
g:
type: string
not:
properties:
a: {}
b:
not:
properties:
a: {}
b:
items: {}
c:
items:
not:
items:
properties:
a: {}
d:
items: {}
properties:
a: {}
b:
not:
properties:
a: {}
b:
items: {}
c:
items:
not:
items:
properties:
a: {}
d:
items: {}
allOf:
- properties:
e: {}
e: {}
anyOf:
- properties:
f: {}
f: {}
oneOf:
- properties:
g: {}
g: {}
`,
expectedViolations: nil,
},
@@ -1397,16 +1397,16 @@ oneOf:
v1beta1Schema: `
type: object
properties:
a: {}
a: {}
not:
properties:
b: {}
properties:
b: {}
`,
v1Schema: `
type: object
properties:
a:
type: string
a:
type: string
`,
expectedViolations: []string{
"spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields",
@@ -1418,18 +1418,18 @@ properties:
v1beta1Schema: `
type: object
properties:
a: {}
a: {}
not:
properties:
b: {}
properties:
b: {}
`,
v1Schema: `
type: object
properties:
c: {}
c: {}
not:
properties:
d: {}
properties:
d: {}
`,
expectedViolations: []string{
"spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields",
@@ -1443,12 +1443,12 @@ not:
globalSchema: `
type: object
properties:
metadata:
minimum: 42.0
metadata:
minimum: 42.0
`,
expectedViolations: []string{
"spec.validation.openAPIV3Schema.properties[metadata]: Forbidden: must not specify anything other than name and generateName, but metadata is implicitly specified",
"spec.validation.openAPIV3Schema.properties[metadata].type: Required value: must not be empty for specified object fields",
"spec.versions[0].schema.openAPIV3Schema.properties[metadata]: Forbidden: must not specify anything other than name and generateName, but metadata is implicitly specified",
"spec.versions[0].schema.openAPIV3Schema.properties[metadata].type: Required value: must not be empty for specified object fields",
},
},
{
@@ -1456,18 +1456,18 @@ properties:
globalSchema: `
type: object
properties:
metadata:
properties:
name:
pattern: "^[a-z]+$"
labels:
type: object
maxLength: 4
metadata:
properties:
name:
pattern: "^[a-z]+$"
labels:
type: object
maxLength: 4
`,
expectedViolations: []string{
"spec.validation.openAPIV3Schema.properties[metadata]: Forbidden: must not specify anything other than name and generateName, but metadata is implicitly specified",
"spec.validation.openAPIV3Schema.properties[metadata].type: Required value: must not be empty for specified object fields",
"spec.validation.openAPIV3Schema.properties[metadata].properties[name].type: Required value: must not be empty for specified object fields",
"spec.versions[0].schema.openAPIV3Schema.properties[metadata]: Forbidden: must not specify anything other than name and generateName, but metadata is implicitly specified",
"spec.versions[0].schema.openAPIV3Schema.properties[metadata].type: Required value: must not be empty for specified object fields",
"spec.versions[0].schema.openAPIV3Schema.properties[metadata].properties[name].type: Required value: must not be empty for specified object fields",
},
},
{
@@ -1475,12 +1475,12 @@ properties:
globalSchema: `
type: object
properties:
metadata:
type: object
properties:
name:
type: string
pattern: "^[a-z]+$"
metadata:
type: object
properties:
name:
type: string
pattern: "^[a-z]+$"
`,
expectedViolations: []string{},
},
@@ -1489,12 +1489,12 @@ properties:
globalSchema: `
type: object
properties:
metadata:
type: object
properties:
generateName:
type: string
pattern: "^[a-z]+$"
metadata:
type: object
properties:
generateName:
type: string
pattern: "^[a-z]+$"
`,
expectedViolations: []string{},
},
@@ -1503,15 +1503,15 @@ properties:
globalSchema: `
type: object
properties:
metadata:
type: object
properties:
name:
type: string
pattern: "^[a-z]+$"
generateName:
type: string
pattern: "^[a-z]+$"
metadata:
type: object
properties:
name:
type: string
pattern: "^[a-z]+$"
generateName:
type: string
pattern: "^[a-z]+$"
`,
expectedViolations: []string{},
},
@@ -1520,30 +1520,30 @@ properties:
globalSchema: `
type: object
properties:
metadata:
type: object
properties:
name:
type: string
pattern: "^[a-z]+$"
metadata:
type: object
properties:
name:
type: string
pattern: "^[a-z]+$"
allOf:
- properties:
metadata: {}
metadata: {}
anyOf:
- properties:
metadata: {}
metadata: {}
oneOf:
- properties:
metadata: {}
metadata: {}
not:
properties:
metadata: {}
properties:
metadata: {}
`,
expectedViolations: []string{
"spec.validation.openAPIV3Schema.anyOf[0].properties[metadata]: Forbidden: must not be specified in a nested context",
"spec.validation.openAPIV3Schema.allOf[0].properties[metadata]: Forbidden: must not be specified in a nested context",
"spec.validation.openAPIV3Schema.oneOf[0].properties[metadata]: Forbidden: must not be specified in a nested context",
"spec.validation.openAPIV3Schema.not.properties[metadata]: Forbidden: must not be specified in a nested context",
"spec.versions[0].schema.openAPIV3Schema.anyOf[0].properties[metadata]: Forbidden: must not be specified in a nested context",
"spec.versions[0].schema.openAPIV3Schema.allOf[0].properties[metadata]: Forbidden: must not be specified in a nested context",
"spec.versions[0].schema.openAPIV3Schema.oneOf[0].properties[metadata]: Forbidden: must not be specified in a nested context",
"spec.versions[0].schema.openAPIV3Schema.not.properties[metadata]: Forbidden: must not be specified in a nested context",
},
},
{
@@ -1551,11 +1551,11 @@ not:
globalSchema: `
type: object
properties:
slice:
type: array
slice:
type: array
`,
expectedViolations: []string{
"spec.validation.openAPIV3Schema.properties[slice].items: Required value: must be specified",
"spec.versions[0].schema.openAPIV3Schema.properties[slice].items: Required value: must be specified",
},
},
{
@@ -1563,11 +1563,11 @@ properties:
globalSchema: `
type: object
properties:
slice:
type: array
items:
- type: string
- type: integer
slice:
type: array
items:
- type: string
- type: integer
`,
expectedCreateErrors: []string{"spec.validation.openAPIV3Schema.properties[slice].items: Forbidden: items must be a schema object and not an array"},
},
@@ -1576,13 +1576,13 @@ properties:
globalSchema: `
type: object
properties:
slice:
type: array
items:
type: string
not:
items:
- type: string
slice:
type: array
items:
type: string
not:
items:
- type: string
`,
expectedCreateErrors: []string{"spec.validation.openAPIV3Schema.properties[slice].not.items: Forbidden: items must be a schema object and not an array"},
},