Merge pull request #81965 from roycaihw/crd-feature-gates-to-ga

Bump the CRD feature gates to GA
This commit is contained in:
Kubernetes Prow Robot 2019-08-28 14:44:44 -07:00 committed by GitHub
commit f3828b776b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 298 additions and 418 deletions

View File

@ -575,10 +575,10 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
// inherited features from apiextensions-apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side:
apiextensionsfeatures.CustomResourceValidation: {Default: true, PreRelease: featuregate.Beta},
apiextensionsfeatures.CustomResourceSubresources: {Default: true, PreRelease: featuregate.Beta},
apiextensionsfeatures.CustomResourceWebhookConversion: {Default: true, PreRelease: featuregate.Beta},
apiextensionsfeatures.CustomResourcePublishOpenAPI: {Default: true, PreRelease: featuregate.Beta},
apiextensionsfeatures.CustomResourceValidation: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
apiextensionsfeatures.CustomResourceSubresources: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
apiextensionsfeatures.CustomResourceWebhookConversion: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
apiextensionsfeatures.CustomResourcePublishOpenAPI: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
apiextensionsfeatures.CustomResourceDefaulting: {Default: true, PreRelease: featuregate.Beta},
// features that enable backwards compatibility but are scheduled to be removed

View File

@ -31,12 +31,15 @@ const (
// owner: @sttts, @nikhita
// alpha: v1.8
// beta: v1.9
// GA: v1.16
//
// CustomResourceValidation is a list of validation methods for CustomResources
CustomResourceValidation featuregate.Feature = "CustomResourceValidation"
// owner: @roycaihw, @sttts
// alpha: v1.14
// beta: v1.15
// GA: v1.16
//
// CustomResourcePublishOpenAPI enables publishing of CRD OpenAPI specs.
CustomResourcePublishOpenAPI featuregate.Feature = "CustomResourcePublishOpenAPI"
@ -44,12 +47,15 @@ const (
// owner: @sttts, @nikhita
// alpha: v1.10
// beta: v1.11
// GA: v1.16
//
// CustomResourceSubresources defines the subresources for CustomResources
CustomResourceSubresources featuregate.Feature = "CustomResourceSubresources"
// owner: @mbohlool, @roycaihw
// alpha: v1.13
// beta: v1.15
// GA: v1.16
//
// CustomResourceWebhookConversion defines the webhook conversion for Custom Resources.
CustomResourceWebhookConversion featuregate.Feature = "CustomResourceWebhookConversion"
@ -69,9 +75,9 @@ func init() {
// To add a new feature, define a key for it above and add it here. The features will be
// available throughout Kubernetes binaries.
var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
CustomResourceValidation: {Default: true, PreRelease: featuregate.Beta},
CustomResourceSubresources: {Default: true, PreRelease: featuregate.Beta},
CustomResourceWebhookConversion: {Default: true, PreRelease: featuregate.Beta},
CustomResourcePublishOpenAPI: {Default: true, PreRelease: featuregate.Beta},
CustomResourceValidation: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
CustomResourceSubresources: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
CustomResourceWebhookConversion: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
CustomResourcePublishOpenAPI: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
CustomResourceDefaulting: {Default: true, PreRelease: featuregate.Beta},
}

View File

@ -55,13 +55,10 @@ go_test(
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
],
)

View File

@ -24,456 +24,333 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/utils/pointer"
)
func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
t.Log("testing unversioned validation..")
for _, validationEnabled := range []bool{true, false} {
crdWithUnversionedValidation := func() *apiextensions.CustomResourceDefinition {
// crd with non-versioned validation
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Validation: &apiextensions.CustomResourceValidation{
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{},
},
crdWithUnversionedValidation := func() *apiextensions.CustomResourceDefinition {
// crd with non-versioned validation
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Validation: &apiextensions.CustomResourceValidation{
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{},
},
}
}
crdWithoutUnversionedValidation := func() *apiextensions.CustomResourceDefinition {
// crd with non-versioned validation
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{},
}
}
crdInfos := []struct {
name string
hasCRValidation bool
crd func() *apiextensions.CustomResourceDefinition
}{
{
name: "has unversioned validation",
hasCRValidation: true,
crd: crdWithUnversionedValidation,
},
{
name: "doesn't have unversioned validation",
hasCRValidation: false,
crd: crdWithoutUnversionedValidation,
},
{
name: "nil",
hasCRValidation: false,
crd: func() *apiextensions.CustomResourceDefinition { return nil },
},
}
for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos {
oldCRDHasValidation, oldCRD := oldCRDInfo.hasCRValidation, oldCRDInfo.crd()
newCRDHasValidation, newCRD := newCRDInfo.hasCRValidation, newCRDInfo.crd()
if newCRD == nil {
continue
}
t.Run(fmt.Sprintf("validation feature enabled=%v, old CRD %v, new CRD %v", validationEnabled, oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceValidation, validationEnabled)()
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec
}
dropDisabledFields(&newCRD.Spec, oldCRDSpec)
// old CRD should never be changed
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
}
switch {
case validationEnabled || oldCRDHasValidation:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
case newCRDHasValidation:
if reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd was not changed")
}
if !reflect.DeepEqual(newCRD, crdWithoutUnversionedValidation()) {
t.Errorf("new crd had unversioned validation: %v", diff.ObjectReflectDiff(newCRD, crdWithoutUnversionedValidation()))
}
default:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
}
},
)
}
crdWithoutUnversionedValidation := func() *apiextensions.CustomResourceDefinition {
// crd with non-versioned validation
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{},
}
}
crdInfos := []struct {
name string
crd func() *apiextensions.CustomResourceDefinition
}{
{
name: "has unversioned validation",
crd: crdWithUnversionedValidation,
},
{
name: "doesn't have unversioned validation",
crd: crdWithoutUnversionedValidation,
},
{
name: "nil",
crd: func() *apiextensions.CustomResourceDefinition { return nil },
},
}
for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos {
oldCRD := oldCRDInfo.crd()
newCRD := newCRDInfo.crd()
if newCRD == nil {
continue
}
t.Run(fmt.Sprintf("old CRD %v, new CRD %v", oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) {
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec
}
dropDisabledFields(&newCRD.Spec, oldCRDSpec)
// old CRD should never be changed
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
}
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
},
)
}
}
t.Log("testing unversioned subresources...")
for _, validationEnabled := range []bool{true, false} {
crdWithUnversionedSubresources := func() *apiextensions.CustomResourceDefinition {
// crd with unversioned subresources
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Subresources: &apiextensions.CustomResourceSubresources{},
crdWithUnversionedSubresources := func() *apiextensions.CustomResourceDefinition {
// crd with unversioned subresources
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Subresources: &apiextensions.CustomResourceSubresources{},
},
}
}
crdWithoutUnversionedSubresources := func() *apiextensions.CustomResourceDefinition {
// crd without unversioned subresources
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{},
}
}
crdInfos = []struct {
name string
crd func() *apiextensions.CustomResourceDefinition
}{
{
name: "has unversioned subresources",
crd: crdWithUnversionedSubresources,
},
{
name: "doesn't have unversioned subresources",
crd: crdWithoutUnversionedSubresources,
},
{
name: "nil",
crd: func() *apiextensions.CustomResourceDefinition { return nil },
},
}
for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos {
oldCRD := oldCRDInfo.crd()
newCRD := newCRDInfo.crd()
if newCRD == nil {
continue
}
t.Run(fmt.Sprintf("old CRD %v, new CRD %v", oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) {
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec
}
dropDisabledFields(&newCRD.Spec, oldCRDSpec)
// old CRD should never be changed
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
}
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
},
}
}
crdWithoutUnversionedSubresources := func() *apiextensions.CustomResourceDefinition {
// crd without unversioned subresources
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{},
}
}
crdInfos := []struct {
name string
hasCRSubresources bool
crd func() *apiextensions.CustomResourceDefinition
}{
{
name: "has unversioned subresources",
hasCRSubresources: true,
crd: crdWithUnversionedSubresources,
},
{
name: "doesn't have unversioned subresources",
hasCRSubresources: false,
crd: crdWithoutUnversionedSubresources,
},
{
name: "nil",
hasCRSubresources: false,
crd: func() *apiextensions.CustomResourceDefinition { return nil },
},
}
for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos {
oldCRDHasSubresources, oldCRD := oldCRDInfo.hasCRSubresources, oldCRDInfo.crd()
newCRDHasSubresources, newCRD := newCRDInfo.hasCRSubresources, newCRDInfo.crd()
if newCRD == nil {
continue
}
t.Run(fmt.Sprintf("subresources feature enabled=%v, old CRD %v, new CRD %v", validationEnabled, oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceSubresources, validationEnabled)()
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec
}
dropDisabledFields(&newCRD.Spec, oldCRDSpec)
// old CRD should never be changed
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
}
switch {
case validationEnabled || oldCRDHasSubresources:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
case newCRDHasSubresources:
if reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd was not changed")
}
if !reflect.DeepEqual(newCRD, crdWithoutUnversionedSubresources()) {
t.Errorf("new crd had unversioned subresources: %v", diff.ObjectReflectDiff(newCRD, crdWithoutUnversionedSubresources()))
}
default:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
}
},
)
}
)
}
}
t.Log("testing versioned validation..")
for _, conversionEnabled := range []bool{true, false} {
for _, validationEnabled := range []bool{true, false} {
crdWithVersionedValidation := func() *apiextensions.CustomResourceDefinition {
// crd with versioned validation
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "v1",
Schema: &apiextensions.CustomResourceValidation{
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{},
},
},
crdWithVersionedValidation := func() *apiextensions.CustomResourceDefinition {
// crd with versioned validation
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "v1",
Schema: &apiextensions.CustomResourceValidation{
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{},
},
},
}
}
crdWithoutVersionedValidation := func() *apiextensions.CustomResourceDefinition {
// crd with versioned validation
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "v1",
},
},
},
},
}
}
crdWithoutVersionedValidation := func() *apiextensions.CustomResourceDefinition {
// crd with versioned validation
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "v1",
},
}
},
},
}
}
crdInfos = []struct {
name string
crd func() *apiextensions.CustomResourceDefinition
}{
{
name: "has versioned validation",
crd: crdWithVersionedValidation,
},
{
name: "doesn't have versioned validation",
crd: crdWithoutVersionedValidation,
},
{
name: "nil",
crd: func() *apiextensions.CustomResourceDefinition { return nil },
},
}
for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos {
oldCRD := oldCRDInfo.crd()
newCRD := newCRDInfo.crd()
if newCRD == nil {
continue
}
crdInfos := []struct {
name string
hasCRValidation bool
crd func() *apiextensions.CustomResourceDefinition
}{
{
name: "has versioned validation",
hasCRValidation: true,
crd: crdWithVersionedValidation,
},
{
name: "doesn't have versioned validation",
hasCRValidation: false,
crd: crdWithoutVersionedValidation,
},
{
name: "nil",
hasCRValidation: false,
crd: func() *apiextensions.CustomResourceDefinition { return nil },
},
}
for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos {
oldCRDHasValidation, oldCRD := oldCRDInfo.hasCRValidation, oldCRDInfo.crd()
newCRDHasValidation, newCRD := newCRDInfo.hasCRValidation, newCRDInfo.crd()
if newCRD == nil {
continue
t.Run(fmt.Sprintf("old CRD %v, new CRD %v", oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) {
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec
}
t.Run(fmt.Sprintf("validation feature enabled=%v, old CRD %v, new CRD %v", validationEnabled, oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceValidation, validationEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceWebhookConversion, conversionEnabled)()
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec
}
dropDisabledFields(&newCRD.Spec, oldCRDSpec)
// old CRD should never be changed
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
}
switch {
case (conversionEnabled && validationEnabled) || oldCRDHasValidation:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
case !conversionEnabled && !oldCRDHasValidation:
if !reflect.DeepEqual(newCRD, crdWithoutVersionedValidation()) {
t.Errorf("new crd was not changed")
}
case newCRDHasValidation:
if reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd was not changed")
}
if !reflect.DeepEqual(newCRD, crdWithoutVersionedValidation()) {
t.Errorf("new crd had unversioned validation: %v", diff.ObjectReflectDiff(newCRD, crdWithoutVersionedValidation()))
}
default:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
}
},
)
}
}
dropDisabledFields(&newCRD.Spec, oldCRDSpec)
// old CRD should never be changed
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
}
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
},
)
}
}
t.Log("testing versioned subresources w/ conversion enabled..")
for _, conversionEnabled := range []bool{true, false} {
for _, validationEnabled := range []bool{true, false} {
crdWithVersionedSubresources := func() *apiextensions.CustomResourceDefinition {
// crd with versioned subresources
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "v1",
Subresources: &apiextensions.CustomResourceSubresources{},
},
},
crdWithVersionedSubresources := func() *apiextensions.CustomResourceDefinition {
// crd with versioned subresources
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "v1",
Subresources: &apiextensions.CustomResourceSubresources{},
},
}
}
crdWithoutVersionedSubresources := func() *apiextensions.CustomResourceDefinition {
// crd without versioned subresources
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "v1",
},
},
},
},
}
}
crdWithoutVersionedSubresources := func() *apiextensions.CustomResourceDefinition {
// crd without versioned subresources
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Versions: []apiextensions.CustomResourceDefinitionVersion{
{
Name: "v1",
},
}
},
},
}
}
crdInfos = []struct {
name string
crd func() *apiextensions.CustomResourceDefinition
}{
{
name: "has versioned subresources",
crd: crdWithVersionedSubresources,
},
{
name: "doesn't have versioned subresources",
crd: crdWithoutVersionedSubresources,
},
{
name: "nil",
crd: func() *apiextensions.CustomResourceDefinition { return nil },
},
}
for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos {
oldCRD := oldCRDInfo.crd()
newCRD := newCRDInfo.crd()
if newCRD == nil {
continue
}
crdInfos := []struct {
name string
hasCRSubresources bool
crd func() *apiextensions.CustomResourceDefinition
}{
{
name: "has versioned subresources",
hasCRSubresources: true,
crd: crdWithVersionedSubresources,
},
{
name: "doesn't have versioned subresources",
hasCRSubresources: false,
crd: crdWithoutVersionedSubresources,
},
{
name: "nil",
hasCRSubresources: false,
crd: func() *apiextensions.CustomResourceDefinition { return nil },
},
}
for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos {
oldCRDHasSubresources, oldCRD := oldCRDInfo.hasCRSubresources, oldCRDInfo.crd()
newCRDHasSubresources, newCRD := newCRDInfo.hasCRSubresources, newCRDInfo.crd()
if newCRD == nil {
continue
t.Run(fmt.Sprintf("old CRD %v, new CRD %v", oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) {
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec
}
t.Run(fmt.Sprintf("subresources feature enabled=%v, old CRD %v, new CRD %v", validationEnabled, oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceSubresources, validationEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceWebhookConversion, conversionEnabled)()
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec
}
dropDisabledFields(&newCRD.Spec, oldCRDSpec)
// old CRD should never be changed
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
}
switch {
case (conversionEnabled && validationEnabled) || oldCRDHasSubresources:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
case !conversionEnabled && !oldCRDHasSubresources:
if !reflect.DeepEqual(newCRD, crdWithoutVersionedSubresources()) {
t.Errorf("new crd was not changed")
}
case newCRDHasSubresources:
if reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd was not changed")
}
if !reflect.DeepEqual(newCRD, crdWithoutVersionedSubresources()) {
t.Errorf("new crd had versioned subresources: %v", diff.ObjectReflectDiff(newCRD, crdWithoutVersionedSubresources()))
}
default:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
}
},
)
}
}
dropDisabledFields(&newCRD.Spec, oldCRDSpec)
// old CRD should never be changed
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
}
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
},
)
}
}
t.Log("testing conversion webhook..")
for _, validationEnabled := range []bool{true, false} {
crdWithUnversionedConversionWebhook := func() *apiextensions.CustomResourceDefinition {
// crd with conversion webhook
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Conversion: &apiextensions.CustomResourceConversion{
WebhookClientConfig: &apiextensions.WebhookClientConfig{},
},
crdWithUnversionedConversionWebhook := func() *apiextensions.CustomResourceDefinition {
// crd with conversion webhook
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Conversion: &apiextensions.CustomResourceConversion{
WebhookClientConfig: &apiextensions.WebhookClientConfig{},
},
}
}
crdWithoutUnversionedConversionWebhook := func() *apiextensions.CustomResourceDefinition {
// crd with conversion webhook
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Conversion: &apiextensions.CustomResourceConversion{},
},
}
}
crdInfos := []struct {
name string
hasCRConversionWebhook bool
crd func() *apiextensions.CustomResourceDefinition
}{
{
name: "has conversion webhook",
hasCRConversionWebhook: true,
crd: crdWithUnversionedConversionWebhook,
},
{
name: "doesn't have conversion webhook",
hasCRConversionWebhook: false,
crd: crdWithoutUnversionedConversionWebhook,
},
{
name: "nil",
hasCRConversionWebhook: false,
crd: func() *apiextensions.CustomResourceDefinition { return nil },
},
}
for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos {
oldCRDHasConversionWebhook, oldCRD := oldCRDInfo.hasCRConversionWebhook, oldCRDInfo.crd()
newCRDHasConversionWebhook, newCRD := newCRDInfo.hasCRConversionWebhook, newCRDInfo.crd()
if newCRD == nil {
continue
}
t.Run(fmt.Sprintf("subresources feature enabled=%v, old CRD %v, new CRD %v", validationEnabled, oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceWebhookConversion, validationEnabled)()
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec
}
dropDisabledFields(&newCRD.Spec, oldCRDSpec)
// old CRD should never be changed
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
}
switch {
case validationEnabled || oldCRDHasConversionWebhook:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
case newCRDHasConversionWebhook:
if reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd was not changed")
}
if !reflect.DeepEqual(newCRD, crdWithoutUnversionedConversionWebhook()) {
t.Errorf("new crd had webhook conversion: %v", diff.ObjectReflectDiff(newCRD, crdWithoutUnversionedConversionWebhook()))
}
default:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
}
},
)
}
}
}
crdWithoutUnversionedConversionWebhook := func() *apiextensions.CustomResourceDefinition {
// crd with conversion webhook
return &apiextensions.CustomResourceDefinition{
Spec: apiextensions.CustomResourceDefinitionSpec{
Conversion: &apiextensions.CustomResourceConversion{},
},
}
}
crdInfos = []struct {
name string
crd func() *apiextensions.CustomResourceDefinition
}{
{
name: "has conversion webhook",
crd: crdWithUnversionedConversionWebhook,
},
{
name: "doesn't have conversion webhook",
crd: crdWithoutUnversionedConversionWebhook,
},
{
name: "nil",
crd: func() *apiextensions.CustomResourceDefinition { return nil },
},
}
for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos {
oldCRD := oldCRDInfo.crd()
newCRD := newCRDInfo.crd()
if newCRD == nil {
continue
}
t.Run(fmt.Sprintf("old CRD %v, new CRD %v", oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) {
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec
}
dropDisabledFields(&newCRD.Spec, oldCRDSpec)
// old CRD should never be changed
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
}
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
},
)
}
}
}
func strPtr(in string) *string {