From 8c73d03cd64cf5d9960fdb0a7194bc54e83aa971 Mon Sep 17 00:00:00 2001 From: Axel Siebenborn Date: Mon, 6 Feb 2023 17:55:09 +0100 Subject: [PATCH] Make `.spec.networking.nodes` in `Shoot`s mutable (#7368) * Make Shoot.Spec.Networking.Nodes mutable. * Add immutability check to managedseed shoot admission. * Add featuregate. * Update docs/deployment/feature_gates.md Co-authored-by: Rafael Franzke * Add additional owners to feature gate. * Enable feature gate for local deployment. --------- Co-authored-by: Rafael Franzke --- docs/api-reference/core.md | 3 +- docs/deployment/feature_gates.md | 2 + .../gardener-local/controlplane/values.yaml | 1 + hack/local-development/start-apiserver | 2 +- pkg/apis/core/types_shoot.go | 3 +- pkg/apis/core/v1alpha1/generated.proto | 3 +- pkg/apis/core/v1alpha1/types_shoot.go | 3 +- pkg/apis/core/v1beta1/generated.proto | 3 +- pkg/apis/core/v1beta1/types_shoot.go | 3 +- pkg/apis/core/validation/shoot.go | 8 +++- pkg/apis/core/validation/shoot_test.go | 24 ++++++++++ .../validation/managedseedset_test.go | 13 +++++- .../validation/validation_suite_test.go | 3 ++ pkg/apiserver/features/features.go | 1 + pkg/features/features.go | 16 ++++--- pkg/openapi/openapi_generated.go | 4 +- plugin/pkg/shoot/managedseed/admission.go | 9 ++++ .../pkg/shoot/managedseed/admission_test.go | 44 +++++++++++++------ 18 files changed, 116 insertions(+), 29 deletions(-) diff --git a/docs/api-reference/core.md b/docs/api-reference/core.md index 1c39cb0c7a0..599dc7126de 100644 --- a/docs/api-reference/core.md +++ b/docs/api-reference/core.md @@ -6783,7 +6783,8 @@ string (Optional) -

Nodes is the CIDR of the entire node network. This field is immutable.

+

Nodes is the CIDR of the entire node network. +This field is immutable if the feature gate MutableShootSpecNetworkingNodes is disabled.

diff --git a/docs/deployment/feature_gates.md b/docs/deployment/feature_gates.md index 67857dc0741..4511fce228a 100644 --- a/docs/deployment/feature_gates.md +++ b/docs/deployment/feature_gates.md @@ -37,6 +37,7 @@ The following tables are a summary of the feature gates that you can set on diff | DefaultSeccompProfile | `false` | `Alpha` | `1.54` | | | CoreDNSQueryRewriting | `false` | `Alpha` | `1.55` | | | IPv6SingleStack | `false` | `Alpha` | `1.63` | | +| MutableShootSpecNetworkingNodes | `false` | `Alpha` | `1.64` | | ## Feature Gates for Graduated or Deprecated Features @@ -162,3 +163,4 @@ A *General Availability* (GA) feature is also referred to as a *stable* feature. | DefaultSeccompProfile | `gardenlet`, `gardener-operator` | Enables the defaulting of the seccomp profile for Gardener managed workload in the garden or seed to `RuntimeDefault`. | | CoreDNSQueryRewriting | `gardenlet` | Enables automatic DNS query rewriting in shoot cluster's CoreDNS to shortcut name resolution of fully qualified in-cluster and out-of-cluster names, which follow a user-defined pattern. Details can be found in [DNS Search Path Optimization](../usage/dns-search-path-optimization.md). | | IPv6SingleStack | `gardener-apiserver` | Allows creating shoot clusters with [IPv6 single-stack networking](../usage/ipv6.md) ([GEP-21](../proposals/21-ipv6-singlestack-local.md)). | +| MutableShootSpecNetworkingNodes | `gardener-apiserver` | Allows updating the field `spec.networking.nodes`. The validity of the values has to be checked in the provider extensions. Only enable this feature gate when your system runs provider extensions which have implemented the validation. | diff --git a/example/gardener-local/controlplane/values.yaml b/example/gardener-local/controlplane/values.yaml index 52eb9ebebca..69c8e171382 100644 --- a/example/gardener-local/controlplane/values.yaml +++ b/example/gardener-local/controlplane/values.yaml @@ -161,6 +161,7 @@ global: SeedChange: true HAControlPlanes: true IPv6SingleStack: true + MutableShootSpecNetworkingNodes: true resources: {} # Gardener admission controller configuration values diff --git a/hack/local-development/start-apiserver b/hack/local-development/start-apiserver index c8a0b00283e..8df621f81a1 100755 --- a/hack/local-development/start-apiserver +++ b/hack/local-development/start-apiserver @@ -57,7 +57,7 @@ apiserver_flags=" --secure-port=8443 \ --tls-cert-file $TLS_CERT_FILE \ --tls-private-key-file $TLS_KEY_FILE \ - --feature-gates HAControlPlanes=true,SeedChange=true,IPv6SingleStack=true \ + --feature-gates HAControlPlanes=true,SeedChange=true,IPv6SingleStack=true,MutableShootSpecNetworkingNodes=true \ --shoot-admin-kubeconfig-max-expiration=24h \ --enable-admission-plugins=ShootVPAEnabledByDefault \ --v 2" diff --git a/pkg/apis/core/types_shoot.go b/pkg/apis/core/types_shoot.go index 0829768446f..24baa88e8e7 100644 --- a/pkg/apis/core/types_shoot.go +++ b/pkg/apis/core/types_shoot.go @@ -932,7 +932,8 @@ type Networking struct { ProviderConfig *runtime.RawExtension // Pods is the CIDR of the pod network. This field is immutable. Pods *string - // Nodes is the CIDR of the entire node network. This field is immutable. + // Nodes is the CIDR of the entire node network. + // This field is immutable if the feature gate MutableShootSpecNetworkingNodes is disabled. Nodes *string // Services is the CIDR of the service network. This field is immutable. Services *string diff --git a/pkg/apis/core/v1alpha1/generated.proto b/pkg/apis/core/v1alpha1/generated.proto index c4f14da80d4..7a256688d36 100644 --- a/pkg/apis/core/v1alpha1/generated.proto +++ b/pkg/apis/core/v1alpha1/generated.proto @@ -1603,7 +1603,8 @@ message Networking { // +optional optional string pods = 3; - // Nodes is the CIDR of the entire node network. This field is immutable. + // Nodes is the CIDR of the entire node network. + // This field is immutable if the feature gate MutableShootSpecNetworkingNodes is disabled. // +optional optional string nodes = 4; diff --git a/pkg/apis/core/v1alpha1/types_shoot.go b/pkg/apis/core/v1alpha1/types_shoot.go index 52f2c8cae8b..3c2034e8813 100644 --- a/pkg/apis/core/v1alpha1/types_shoot.go +++ b/pkg/apis/core/v1alpha1/types_shoot.go @@ -1186,7 +1186,8 @@ type Networking struct { // Pods is the CIDR of the pod network. This field is immutable. // +optional Pods *string `json:"pods,omitempty" protobuf:"bytes,3,opt,name=pods"` - // Nodes is the CIDR of the entire node network. This field is immutable. + // Nodes is the CIDR of the entire node network. + // This field is immutable if the feature gate MutableShootSpecNetworkingNodes is disabled. // +optional Nodes *string `json:"nodes,omitempty" protobuf:"bytes,4,opt,name=nodes"` // Services is the CIDR of the service network. This field is immutable. diff --git a/pkg/apis/core/v1beta1/generated.proto b/pkg/apis/core/v1beta1/generated.proto index 0a0574f83b4..f3d901d259e 100644 --- a/pkg/apis/core/v1beta1/generated.proto +++ b/pkg/apis/core/v1beta1/generated.proto @@ -1528,7 +1528,8 @@ message Networking { // +optional optional string pods = 3; - // Nodes is the CIDR of the entire node network. This field is immutable. + // Nodes is the CIDR of the entire node network. + // This field is immutable if the feature gate MutableShootSpecNetworkingNodes is disabled. // +optional optional string nodes = 4; diff --git a/pkg/apis/core/v1beta1/types_shoot.go b/pkg/apis/core/v1beta1/types_shoot.go index 028338a28de..b0b158e1f3e 100644 --- a/pkg/apis/core/v1beta1/types_shoot.go +++ b/pkg/apis/core/v1beta1/types_shoot.go @@ -1198,7 +1198,8 @@ type Networking struct { // Pods is the CIDR of the pod network. This field is immutable. // +optional Pods *string `json:"pods,omitempty" protobuf:"bytes,3,opt,name=pods"` - // Nodes is the CIDR of the entire node network. This field is immutable. + // Nodes is the CIDR of the entire node network. + // This field is immutable if the feature gate MutableShootSpecNetworkingNodes is disabled. // +optional Nodes *string `json:"nodes,omitempty" protobuf:"bytes,4,opt,name=nodes"` // Services is the CIDR of the service network. This field is immutable. diff --git a/pkg/apis/core/validation/shoot.go b/pkg/apis/core/validation/shoot.go index b67aa5da0c7..8b36ad29a3e 100644 --- a/pkg/apis/core/validation/shoot.go +++ b/pkg/apis/core/validation/shoot.go @@ -36,12 +36,14 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/utils/pointer" "k8s.io/utils/strings/slices" "github.com/gardener/gardener/pkg/apis/core" "github.com/gardener/gardener/pkg/apis/core/helper" v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" + "github.com/gardener/gardener/pkg/features" "github.com/gardener/gardener/pkg/utils" gardenerutils "github.com/gardener/gardener/pkg/utils/gardener" "github.com/gardener/gardener/pkg/utils/timewindow" @@ -169,6 +171,10 @@ func ValidateShootTemplateUpdate(newShootTemplate, oldShootTemplate *core.ShootT allErrs = append(allErrs, ValidateShootSpecUpdate(&newShootTemplate.Spec, &oldShootTemplate.Spec, newShootTemplate.ObjectMeta, fldPath.Child("spec"))...) + if oldShootTemplate.Spec.Networking.Nodes != nil { + allErrs = append(allErrs, apivalidation.ValidateImmutableField(newShootTemplate.Spec.Networking.Nodes, oldShootTemplate.Spec.Networking.Nodes, fldPath.Child("spec", "networking", "nodes"))...) + } + return allErrs } @@ -598,7 +604,7 @@ func validateNetworkingUpdate(newNetworking, oldNetworking core.Networking, fldP if oldNetworking.Services != nil { allErrs = append(allErrs, apivalidation.ValidateImmutableField(newNetworking.Services, oldNetworking.Services, fldPath.Child("services"))...) } - if oldNetworking.Nodes != nil { + if !utilfeature.DefaultFeatureGate.Enabled(features.MutableShootSpecNetworkingNodes) && oldNetworking.Nodes != nil { allErrs = append(allErrs, apivalidation.ValidateImmutableField(newNetworking.Nodes, oldNetworking.Nodes, fldPath.Child("nodes"))...) } diff --git a/pkg/apis/core/validation/shoot_test.go b/pkg/apis/core/validation/shoot_test.go index ba7d9e45399..9395d4b19e5 100644 --- a/pkg/apis/core/validation/shoot_test.go +++ b/pkg/apis/core/validation/shoot_test.go @@ -2671,6 +2671,30 @@ var _ = Describe("Shoot Validation Tests", func() { })))) }) + It("should forbid changing the networking nodes range", func() { + shoot.Spec.Networking.Nodes = pointer.String("10.181.0.0/18") + newShoot := prepareShootForUpdate(shoot) + newShoot.Spec.Networking.Nodes = pointer.String("10.181.0.0/16") + + errorList := ValidateShootUpdate(newShoot, shoot) + + Expect(errorList).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeInvalid), + "Field": Equal("spec.networking.nodes"), + })))) + }) + + It("should allow increasing the networking nodes range if feature gate is enabled", func() { + DeferCleanup(test.WithFeatureGate(utilfeature.DefaultMutableFeatureGate, features.MutableShootSpecNetworkingNodes, true)) + shoot.Spec.Networking.Nodes = pointer.String("10.181.0.0/18") + newShoot := prepareShootForUpdate(shoot) + newShoot.Spec.Networking.Nodes = pointer.String("10.181.0.0/16") + + errorList := ValidateShootUpdate(newShoot, shoot) + + Expect(errorList).To(HaveLen(0)) + }) + It("should forbid specifying unsupported IP family", func() { shoot.Spec.Networking.IPFamilies = []core.IPFamily{"IPv5"} diff --git a/pkg/apis/seedmanagement/validation/managedseedset_test.go b/pkg/apis/seedmanagement/validation/managedseedset_test.go index 07b694f3922..a5ee45bc02d 100644 --- a/pkg/apis/seedmanagement/validation/managedseedset_test.go +++ b/pkg/apis/seedmanagement/validation/managedseedset_test.go @@ -22,12 +22,15 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/validation/field" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/utils/pointer" "github.com/gardener/gardener/pkg/apis/core" gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" "github.com/gardener/gardener/pkg/apis/seedmanagement" . "github.com/gardener/gardener/pkg/apis/seedmanagement/validation" + "github.com/gardener/gardener/pkg/features" + "github.com/gardener/gardener/pkg/utils/test" ) var _ = Describe("ManagedSeedSet Validation Tests", func() { @@ -54,7 +57,8 @@ var _ = Describe("ManagedSeedSet Validation Tests", func() { Version: "1.18.14", }, Networking: core.Networking{ - Type: "foo", + Type: "foo", + Nodes: pointer.String("10.181.0.0/18"), }, Provider: core.Provider{ Type: "foo", @@ -378,8 +382,10 @@ var _ = Describe("ManagedSeedSet Validation Tests", func() { }) It("should forbid changes to immutable fields in shootTemplate", func() { + DeferCleanup(test.WithFeatureGate(utilfeature.DefaultMutableFeatureGate, features.MutableShootSpecNetworkingNodes, true)) shootCopy := shoot.DeepCopy() shootCopy.Spec.Region = "other-region" + shootCopy.Spec.Networking.Nodes = pointer.String("10.181.0.0/16") newManagedSeedSet.Spec.ShootTemplate.Spec = shootCopy.Spec errorList := ValidateManagedSeedSetUpdate(newManagedSeedSet, managedSeedSet) @@ -390,6 +396,11 @@ var _ = Describe("ManagedSeedSet Validation Tests", func() { "Field": Equal("spec.shootTemplate.spec.region"), "Detail": Equal("field is immutable"), })), + PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeInvalid), + "Field": Equal("spec.shootTemplate.spec.networking.nodes"), + "Detail": Equal("field is immutable"), + })), )) }) }) diff --git a/pkg/apis/seedmanagement/validation/validation_suite_test.go b/pkg/apis/seedmanagement/validation/validation_suite_test.go index d2f42f9d275..78d98f0e3df 100644 --- a/pkg/apis/seedmanagement/validation/validation_suite_test.go +++ b/pkg/apis/seedmanagement/validation/validation_suite_test.go @@ -19,9 +19,12 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + "github.com/gardener/gardener/pkg/apiserver/features" ) func TestValidation(t *testing.T) { RegisterFailHandler(Fail) + features.RegisterFeatureGates() RunSpecs(t, "APIs SeedManagement Validation Suite") } diff --git a/pkg/apiserver/features/features.go b/pkg/apiserver/features/features.go index 1175e3ae587..e90d723461c 100644 --- a/pkg/apiserver/features/features.go +++ b/pkg/apiserver/features/features.go @@ -27,5 +27,6 @@ func RegisterFeatureGates() { features.HAControlPlanes, features.SeedChange, features.IPv6SingleStack, + features.MutableShootSpecNetworkingNodes, ))) } diff --git a/pkg/features/features.go b/pkg/features/features.go index 603fad8bec5..f8127e04118 100644 --- a/pkg/features/features.go +++ b/pkg/features/features.go @@ -104,6 +104,11 @@ const ( // owner: @timebertt // alpha: v1.63.0 IPv6SingleStack featuregate.Feature = "IPv6SingleStack" + + // MutableShootSpecNetworkingNodes allows updating the field `spec.networking.nodes`. + // owner: @axel7born @ScheererJ @DockToFuture @kon-angelo + // alpha: v1.64.0 + MutableShootSpecNetworkingNodes featuregate.Feature = "MutableShootSpecNetworkingNodes" ) var allFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ @@ -114,11 +119,12 @@ var allFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ SeedChange: {Default: true, PreRelease: featuregate.Beta}, ReversedVPN: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, CopyEtcdBackupsDuringControlPlaneMigration: {Default: true, PreRelease: featuregate.Beta}, - ForceRestore: {Default: false, PreRelease: featuregate.Alpha}, - HAControlPlanes: {Default: false, PreRelease: featuregate.Alpha}, - DefaultSeccompProfile: {Default: false, PreRelease: featuregate.Alpha}, - CoreDNSQueryRewriting: {Default: false, PreRelease: featuregate.Alpha}, - IPv6SingleStack: {Default: false, PreRelease: featuregate.Alpha}, + ForceRestore: {Default: false, PreRelease: featuregate.Alpha}, + HAControlPlanes: {Default: false, PreRelease: featuregate.Alpha}, + DefaultSeccompProfile: {Default: false, PreRelease: featuregate.Alpha}, + CoreDNSQueryRewriting: {Default: false, PreRelease: featuregate.Alpha}, + IPv6SingleStack: {Default: false, PreRelease: featuregate.Alpha}, + MutableShootSpecNetworkingNodes: {Default: false, PreRelease: featuregate.Alpha}, } // GetFeatures returns a feature gate map with the respective specifications. Non-existing feature gates are ignored. diff --git a/pkg/openapi/openapi_generated.go b/pkg/openapi/openapi_generated.go index 134edf7a2ba..ead659758b4 100644 --- a/pkg/openapi/openapi_generated.go +++ b/pkg/openapi/openapi_generated.go @@ -4894,7 +4894,7 @@ func schema_pkg_apis_core_v1alpha1_Networking(ref common.ReferenceCallback) comm }, "nodes": { SchemaProps: spec.SchemaProps{ - Description: "Nodes is the CIDR of the entire node network. This field is immutable.", + Description: "Nodes is the CIDR of the entire node network. This field is immutable if the feature gate MutableShootSpecNetworkingNodes is disabled.", Type: []string{"string"}, Format: "", }, @@ -12337,7 +12337,7 @@ func schema_pkg_apis_core_v1beta1_Networking(ref common.ReferenceCallback) commo }, "nodes": { SchemaProps: spec.SchemaProps{ - Description: "Nodes is the CIDR of the entire node network. This field is immutable.", + Description: "Nodes is the CIDR of the entire node network. This field is immutable if the feature gate MutableShootSpecNetworkingNodes is disabled.", Type: []string{"string"}, Format: "", }, diff --git a/plugin/pkg/shoot/managedseed/admission.go b/plugin/pkg/shoot/managedseed/admission.go index acf7989c171..6e937ad787a 100644 --- a/plugin/pkg/shoot/managedseed/admission.go +++ b/plugin/pkg/shoot/managedseed/admission.go @@ -152,6 +152,11 @@ func (v *ManagedSeed) validateUpdate(ctx context.Context, a admission.Attributes return apierrors.NewInternalError(errors.New("could not convert resource into Shoot object")) } + oldShoot, ok := a.GetOldObject().(*core.Shoot) + if !ok { + return apierrors.NewInternalError(errors.New("could not convert resource into Shoot object")) + } + var allErrs field.ErrorList if nginxIngressEnabled := gardencorehelper.NginxIngressEnabled(shoot.Spec.Addons); nginxIngressEnabled { allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "addons", "nginxIngress", "enabled"), nginxIngressEnabled, "shoot ingress addon is not supported for managed seeds - use the managed seed ingress controller")) @@ -160,6 +165,10 @@ func (v *ManagedSeed) validateUpdate(ctx context.Context, a admission.Attributes allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "kubernetes", "verticalPodAutoscaler", "enabled"), vpaEnabled, "shoot VPA has to be enabled for managed seeds")) } + if oldShoot.Spec.Networking.Nodes != nil && *oldShoot.Spec.Networking.Nodes != *shoot.Spec.Networking.Nodes { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "networking", "nodes"), shoot.Spec.Networking.Nodes, "field is immutable for managed seeds")) + } + if len(allErrs) > 0 { return apierrors.NewInvalid(a.GetKind().GroupKind(), shoot.Name, allErrs) } diff --git a/plugin/pkg/shoot/managedseed/admission_test.go b/plugin/pkg/shoot/managedseed/admission_test.go index 2058ce66dec..c4dd7d47d0a 100644 --- a/plugin/pkg/shoot/managedseed/admission_test.go +++ b/plugin/pkg/shoot/managedseed/admission_test.go @@ -25,6 +25,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/admission" "k8s.io/client-go/testing" + "k8s.io/utils/pointer" "github.com/gardener/gardener/pkg/apis/core" gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" @@ -69,6 +70,10 @@ var _ = Describe("ManagedSeed", func() { Enabled: true, }, }, + Networking: core.Networking{ + Type: "foo", + Nodes: pointer.String("10.181.0.0/18"), + }, }, } @@ -107,8 +112,8 @@ var _ = Describe("ManagedSeed", func() { return true, &seedmanagementv1alpha1.ManagedSeedList{Items: []seedmanagementv1alpha1.ManagedSeed{*managedSeed}}, nil }) shoot.Spec.Addons.NginxIngress.Enabled = true - - attrs := getShootAttributes(shoot, admission.Update, &metav1.UpdateOptions{}) + oldShoot := shoot.DeepCopy() + attrs := getShootAttributes(shoot, oldShoot, admission.Update, &metav1.UpdateOptions{}) err := admissionHandler.Validate(context.TODO(), attrs, nil) Expect(err).To(BeInvalidError()) Expect(err.Error()).To(ContainSubstring("shoot ingress addon is not supported for managed seeds - use the managed seed ingress controller")) @@ -119,8 +124,8 @@ var _ = Describe("ManagedSeed", func() { return true, &seedmanagementv1alpha1.ManagedSeedList{Items: []seedmanagementv1alpha1.ManagedSeed{*managedSeed}}, nil }) shoot.Spec.Kubernetes.VerticalPodAutoscaler.Enabled = false - - attrs := getShootAttributes(shoot, admission.Update, &metav1.UpdateOptions{}) + oldShoot := shoot.DeepCopy() + attrs := getShootAttributes(shoot, oldShoot, admission.Update, &metav1.UpdateOptions{}) err := admissionHandler.Validate(context.TODO(), attrs, nil) Expect(err).To(BeInvalidError()) Expect(err.Error()).To(ContainSubstring("shoot VPA has to be enabled for managed seeds")) @@ -130,8 +135,8 @@ var _ = Describe("ManagedSeed", func() { seedManagementClient.AddReactor("list", "managedseeds", func(action testing.Action) (bool, runtime.Object, error) { return true, &seedmanagementv1alpha1.ManagedSeedList{Items: []seedmanagementv1alpha1.ManagedSeed{*managedSeed}}, nil }) - - attrs := getShootAttributes(shoot, admission.Update, &metav1.UpdateOptions{}) + oldShoot := shoot.DeepCopy() + attrs := getShootAttributes(shoot, oldShoot, admission.Update, &metav1.UpdateOptions{}) err := admissionHandler.Validate(context.TODO(), attrs, nil) Expect(err).NotTo(HaveOccurred()) }) @@ -140,12 +145,25 @@ var _ = Describe("ManagedSeed", func() { seedManagementClient.AddReactor("list", "managedseeds", func(action testing.Action) (bool, runtime.Object, error) { return true, nil, apierrors.NewInternalError(errors.New("Internal Server Error")) }) - - attrs := getShootAttributes(shoot, admission.Update, &metav1.UpdateOptions{}) + oldShoot := shoot.DeepCopy() + attrs := getShootAttributes(shoot, oldShoot, admission.Update, &metav1.UpdateOptions{}) err := admissionHandler.Validate(context.TODO(), attrs, nil) Expect(err).To(HaveOccurred()) Expect(err).ToNot(BeInvalidError()) }) + + It("should forbid Shoot if the spec.Networking.Nodes is changes", func() { + seedManagementClient.AddReactor("list", "managedseeds", func(action testing.Action) (bool, runtime.Object, error) { + return true, &seedmanagementv1alpha1.ManagedSeedList{Items: []seedmanagementv1alpha1.ManagedSeed{*managedSeed}}, nil + }) + oldShoot := shoot.DeepCopy() + shoot.Spec.Networking.Nodes = pointer.String("10.181.0.0/16") + attrs := getShootAttributes(shoot, oldShoot, admission.Update, &metav1.UpdateOptions{}) + err := admissionHandler.Validate(context.TODO(), attrs, nil) + Expect(err).To(HaveOccurred()) + Expect(err).To(BeInvalidError()) + }) + }) Context("delete", func() { @@ -154,7 +172,7 @@ var _ = Describe("ManagedSeed", func() { return true, &seedmanagementv1alpha1.ManagedSeedList{Items: []seedmanagementv1alpha1.ManagedSeed{*managedSeed}}, nil }) - attrs := getShootAttributes(shoot, admission.Delete, &metav1.DeleteOptions{}) + attrs := getShootAttributes(shoot, nil, admission.Delete, &metav1.DeleteOptions{}) err := admissionHandler.Validate(context.TODO(), attrs, nil) Expect(err).To(BeForbiddenError()) }) @@ -164,7 +182,7 @@ var _ = Describe("ManagedSeed", func() { return true, &seedmanagementv1alpha1.ManagedSeedList{Items: []seedmanagementv1alpha1.ManagedSeed{}}, nil }) - attrs := getShootAttributes(shoot, admission.Delete, &metav1.DeleteOptions{}) + attrs := getShootAttributes(shoot, nil, admission.Delete, &metav1.DeleteOptions{}) err := admissionHandler.Validate(context.TODO(), attrs, nil) Expect(err).NotTo(HaveOccurred()) }) @@ -174,7 +192,7 @@ var _ = Describe("ManagedSeed", func() { return true, nil, apierrors.NewInternalError(errors.New("Internal Server Error")) }) - attrs := getShootAttributes(shoot, admission.Delete, &metav1.DeleteOptions{}) + attrs := getShootAttributes(shoot, nil, admission.Delete, &metav1.DeleteOptions{}) err := admissionHandler.Validate(context.TODO(), attrs, nil) Expect(err).To(HaveOccurred()) Expect(err).ToNot(BeForbiddenError()) @@ -262,8 +280,8 @@ var _ = Describe("ManagedSeed", func() { }) }) -func getShootAttributes(shoot *core.Shoot, operation admission.Operation, operationOptions runtime.Object) admission.Attributes { - return admission.NewAttributesRecord(shoot, nil, gardencorev1beta1.Kind("Shoot").WithVersion("v1beta1"), shoot.Namespace, shoot.Name, gardencorev1beta1.Resource("shoots").WithVersion("v1beta1"), "", operation, operationOptions, false, nil) +func getShootAttributes(shoot *core.Shoot, oldShoot *core.Shoot, operation admission.Operation, operationOptions runtime.Object) admission.Attributes { + return admission.NewAttributesRecord(shoot, oldShoot, gardencorev1beta1.Kind("Shoot").WithVersion("v1beta1"), shoot.Namespace, shoot.Name, gardencorev1beta1.Resource("shoots").WithVersion("v1beta1"), "", operation, operationOptions, false, nil) } func getAllShootsAttributes(namespace string) admission.Attributes {