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:
@@ -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 {
|
||||
|
@@ -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"
|
||||
|
@@ -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"
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
})
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
|
@@ -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"},
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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,
|
||||
},
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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"},
|
||||
|
@@ -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"
|
||||
|
@@ -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"},
|
||||
},
|
||||
|
Reference in New Issue
Block a user