diff --git a/charts/gardener-extension-acl/templates/rbac.yaml b/charts/gardener-extension-acl/templates/rbac.yaml index cce785fb..6c855f21 100644 --- a/charts/gardener-extension-acl/templates/rbac.yaml +++ b/charts/gardener-extension-acl/templates/rbac.yaml @@ -18,6 +18,14 @@ rules: - delete resources: - envoyfilters +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch - apiGroups: - extensions.gardener.cloud resources: @@ -144,6 +152,23 @@ rules: - watch --- apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: +{{ include "labels" . | indent 4 }} + name: {{ include "name" . }} + namespace: kube-system +rules: +- apiGroups: + - "" + resources: + - configmaps + resourceNames: + - shoot-info + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: {{ include "name" . }} @@ -157,3 +182,19 @@ subjects: - kind: ServiceAccount name: {{ include "name" . }} namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "name" . }} + namespace: kube-system + labels: +{{ include "labels" . | indent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "name" . }} +subjects: +- kind: ServiceAccount + name: {{ include "name" . }} + namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/cmd/gardener-extension-acl/app/app.go b/cmd/gardener-extension-acl/app/app.go index c239c741..3b74b0eb 100644 --- a/cmd/gardener-extension-acl/app/app.go +++ b/cmd/gardener-extension-acl/app/app.go @@ -21,6 +21,7 @@ import ( extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" "github.com/gardener/gardener/extensions/pkg/util" + v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" "github.com/spf13/cobra" istionetworkv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" @@ -28,7 +29,9 @@ import ( admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" componentbaseconfigv1alpha1 "k8s.io/component-base/config/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -78,6 +81,15 @@ func (o *Options) run(ctx context.Context) error { }, } + // Only cache services that are needed to check for ProxyProto usage + mgrOpts.Cache.ByObject = map[client.Object]cache.ByObject{ + &corev1.Service{}: { + Label: labels.Set{ + "app": v1beta1constants.DefaultIngressGatewayAppLabelValue, + }.AsSelector(), + }, + } + mgr, err := manager.New(o.restOptions.Completed().Config, mgrOpts) if err != nil { return fmt.Errorf("could not instantiate controller-manager: %s", err) diff --git a/deploy/extension/base/controller-registration.yaml b/deploy/extension/base/controller-registration.yaml index 77978974..501fa4f8 100644 --- a/deploy/extension/base/controller-registration.yaml +++ b/deploy/extension/base/controller-registration.yaml @@ -4,7 +4,7 @@ kind: ControllerDeployment metadata: name: acl helm: - rawChart: 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 + rawChart: H4sIAAAAAAAAA+0ba2/jNnI/61fwvFd0t6jkRxynNbBA0yS3DZAXkm0Oh6IIaIm22UiiSkrOeh/3229IyhKtR2Q7u97b1vPBlsjhcMh5cIakJph7JCTcJm9jEgrKQhu7fvvZp4QOwMH+vvoHKP6r5+5ev9vb7w0Gsrzb73a7z9D+J+WiBhIRY47QM85Y/BheU/1XCpNq+TtT4gd0EjJOnt6HFPCg36+VP4i9IP9Brz94hjpP77oZ/ubyf46ucBwTHgoUM6Qljh6mJESjhPoeDScowu49nhDhWM/RmykVSCRRxHgMD6AlPpr4bIQCHLtTwP4eceLjmM4ItIunRjkOPSAQkgnUshC9iDgZ07fEQw8U8P7x0kGXoT9HLFQtJUsoIhz5NCSO5Rzf3N3EwBuQOGJBAARuj26QR7mwnAmN2+pXs285o3e8rX4XBdNJW/4sXsUsbOeERjC+JEJj6hNhfeeIhwh+R/gefuMAnv8LqLeYU5YIdHp8Ah1GnP1B3NhyqEdwW+NBkeXMhMs80ra+tFRXhxr7P5piHjtzHPifoI8m++91Dor239nb39n/NgBH9JZwKfchmnUtHEXZa6vrdFqWR4TLaRSrokP0C6wLyJXagcaMo3hK0OtUhdDh0RnK1MixQhyQIapWMGu26KXjQDdfkcH8xaDG/mMSRODGifgUkeD68d+gCy5hF/9tARrlfwdrPKzDwomjTdeCBv/f7fUK/r/X6e/t7fz/NuD9ext5EIhB1NWSDruF7I8frRqnLZFJ6CkUy2zp4xHxhQOrh3NP5pqGeklGEMcR0COHsrakv0SjhsQM+0nKyPv3iIaun3gZew5KGz7CSLltkUFJZYhqMNL+VU/lUdAQNCZ0iWruXBOfYEGcC2DuUc5oADG0ZgwhWUHH6J6G3qlALRFzCJGhx1vZM/QicVGGWVMM7BJJZYrFlYqlgdAU9/YHwwIlJ8aTvFkEfcVj1PpG/PSNKGJyEjFBIdCeP0ZC9VxBcLgxQZgxY/I+fvzSZvG3gUb/77JwTCcBjmwltxnkPYzbDOK3B05jskqO0BT/9weF/B8eDnb+fyuQeqIl27xVMr5ciFhapm3bViFVkM5rCDm0VI9zHFkBibGHYzwEm9ahf7XzrtajtJGIcJVnVcXaXWg/Pazw7pL8BygEfY5RX2Iv2FE9irtlpR2iD5LIoyNfJvdXdE+N9u+RyGfzACZh4+2ABvvf398rxn/d7mCX/28FioYN8Y5oZ9Z9nAl/ZfNe25ARwJROpjaeYQqF1Kfx3NbLDoQPgiXcBftcKKrj+izx2vE8AvKAFXPm+4Sv5A9ERFzZISczKof7CxUyMjmjAY2HqKNqIp+6WGjOU9eQFh6xBAgp3gUMSXoKzb3a4zxbzS8NNIGFfaUEjLmVgMOQxWqbVCyKVvTTKAV3Stx7kQTG8p0beKUDXpLnCxXToX86b1I+nZ9BgldyQ7e1UkTQeqkGreNR4CPnzQj3dIGpBxIg0H5gHPRvUpI5sz2YEYR9nz0Qb7UWHIRGA2KDkgvCgcm8fYOofljwuFAbCTAxDEY5P/KxEBfLO1xiLkCu9o+dToosO6QuOXRdqToXj1uNEhvoM4Z0gWfzYTcZmwYliWWsNOEoo10lvn/FQKPnS1peqDObYT4xBGQj2w7wW2mibsI5TJXNiXyRG+ivDIq5darnFPlmHrrCpC7pTQn246nS2vVpG42b+vGowCOf2EZzk2pafZTXgjr8wWiIWt+3irT0aYLNIsKVrdq52dZxqptcLlocZg2KtMFjenJRlm5Gmaz3qiaaKmAuUVH7tLY8h3nVVs8iqy74E+x5VDbH/qE2jiPq8dLs5Vh2akO2K/HM4dZQqp7GkjMoMZYZdLphXGQpM7603mTlsbYVPZNwZiq5truzk8Pjk+u7k7OTozenlxd3F4fnJzdXh0cnGSZCar/gX5wFQ6MQoTElvndNxsulabn0pcPM8TvZklk3D00Of8Hv6fnh65NbYPby+u7y9uT639enb0q8DlGqDnlI3K6MkdcQVLZSmzhZoVoXY/YfoFnR4gOKOQ1yz9vtNIhKEDBy6YXBtmBdM2dYKeYVpzPwGBNyIlzsY314Mca+IAamiyMdbVAilmXkcRYN0W+HZ2e/G+WwkhyKCxZeQyQ4BI4TUqz8VcjlZQABba9Y9ZqzJCrXwUBcFkRXnMnjx2UudIhzrdevYzLGiR9vqh4z5icBOZfLkCgreUNoYHAVSApad8sqZI4ZvJI8zi3MU0mUaw1DD6K0NK7KvbtIVc1p3ihTzfREC+WceUCj3+sYg0pH+aXD+0ZozP8iBg5d8ESdAI4Sb0LWTgQb73/0i/s/e52D3f2PrUBqgJMYvZARf1XK8xJ1q7aAIhUm5rniFfOOM0X5WSnK50sa18n2IJ78NUyTS1+TF8mocbxPzvK+CjfQaP98hN0nXgRp2v85KJ3/Hhx0+jv73wYUrVqJGyfxFLLcdypwcu5/UOde+ZavD3NG+DXzyToGvo7p8sSX67yNgDUVOalF3zYzfSqAOeALymFlHqUI0unIfx+q1cODtFr1FGVPSQQsE/XoQqCSPnpg7erRCFplOaQFbA7RGQxZlDlqtcot0rRfrMRaiWJmiMU9r3JHrpaE0PyHQqbK3NOvNBxzLCD6cuOEb4GXHLXw2gYDi5PVGKiUU4mruk3BMlMBDiGC87LSBiYMbXio0pactVRZVlKHbGXbUAjwashBW2OFLoAqsGBRqE6dVR5e06kx1NIAyxNe5xRqVZIzn4hiwQhMHExXl+cYhao1mV1XGi1IuTiJRUu/QXgf6pfPrBhyY72Knfx8pZGLvOtNJ8Rl4B9o+LgIVeBVkETa4RMJLspVUKfrVt7VMpgxhryYozrT8QIqpN1wMoFZ5I/zGSRyMy6cPJDRlLF7nS8mutHq/mtT2VSvbQUWJ0DvAc9X9CTrr+2lRX2dVXudGF/eqUk3zGtX+yrzzQ4favRJgORiGxa/isDgabHOz9pFfbaQB7pINwsX0/AIh4BVDsYa+IGkR95aVzOtG98snU+sJb/aHG1Dpdtgdmu0aSvT/n8z30+N/xvzvzSYxZrnjTLBpv2fvW63sP/Tg8Jd/rcNqL3YU9DVL7qTA6bI1M7zMlNv2D3J9vW/9ER+pdBo/7MIP/U7oKb733vF+3/dg/3Bwc7+twGFAxgpbRLKfVKvajGXhihP1GSQWlzFASmmUHfFvMMUjfBP7Tf0nm4F04tQ0Ly8sFymd3GzGxaqkJqnSVmVvqjx7XffWtmxFw3T02zz7MiNEsUqJ38mlMOMteo5cnISDrRDVGTNWo8MpNjMONKT14YCxucbsaCbbsJF2nJ5fxshMCIIsbPT7qorZbK8dK2s6QwOEHQWZQpRl+hjN+O8XzJuIjs53lezH79tqPH/Mz2jn+YD0Eb/v79X+v57d/63HdAXx5SzXHyoMUSTqculb4epce9prO/S1XwTJE1/MkQqWIit0gWz0/EFi6/AnUhjt8xjtiHqyQIju5euCIikLll519Z+J2hZprNrDfrntGVZYOwSL12ssnsGVT6/7L0VbSBtLTtSSbrR4bSuid7iASaMu10SsXD5DLqAwto7X/mNFOMCW7ZKFQlZ5btpQ/Tb75ZVfeFK1z1HVTcq5M3352hxLX6onvPLFBgiP6LKENJzeW2oxjSOIzFst4U7fcD8HY1/8sjMwe8SThyXBXl5/uSIe9K+kx/pp8SN23dpP6bycTJx1OGFbOeQpNN1Uj1ML3VKcstsKg1sdZye8yMIZaGoQ70DlH7M3No5/R3sYAc72MEOdrCDHezgf4TOeGQAUAAA values: image: tag: latest diff --git a/pkg/controller/actuator.go b/pkg/controller/actuator.go index 178926a8..c3557b3c 100644 --- a/pkg/controller/actuator.go +++ b/pkg/controller/actuator.go @@ -36,6 +36,7 @@ import ( "github.com/pkg/errors" istionetworkv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/client-go/rest" @@ -48,6 +49,8 @@ import ( "github.com/stackitcloud/gardener-extension-acl/pkg/extensionspec" "github.com/stackitcloud/gardener-extension-acl/pkg/helper" "github.com/stackitcloud/gardener-extension-acl/pkg/imagevector" + + apierrors "k8s.io/apimachinery/pkg/api/errors" ) const ( @@ -147,6 +150,21 @@ func (a *actuator) Reconcile(ctx context.Context, log logr.Logger, ex *extension alwaysAllowedCIDRs = append(alwaysAllowedCIDRs, helper.GetSeedSpecificAllowedCIDRs(cluster.Seed)...) + // This relies on the LB hairpinning in-cluster traffic out and back in + // through the Seed's egress IP, which is the common case when the LB + // exposes ipMode: Proxy and the CNI does not short-circuit clusterIP + // traffic (e.g., Cilium with bpfSocketLBHostnsOnly: true). + if ok, err := a.usesProxyTypeLBService(ctx, istioNamespace); err != nil { + log.Error(err, "unable to get Istio Ingressgateway service", "namespace", istioNamespace) + return err + } else if ok { + egressCIDRs, err := a.getSeedEgressIPOnManagedSeeds(ctx) + if err != nil { + return err + } + alwaysAllowedCIDRs = append(alwaysAllowedCIDRs, egressCIDRs...) + } + if len(a.extensionConfig.AdditionalAllowedCIDRs) >= 1 { alwaysAllowedCIDRs = append(alwaysAllowedCIDRs, a.extensionConfig.AdditionalAllowedCIDRs...) } @@ -260,14 +278,14 @@ func (a *actuator) createSeedResources( spec *extensionspec.ExtensionSpec, cluster *controller.Cluster, hosts []string, - shootSpecificCIRDs []string, + shootSpecificCIDRs []string, alwaysAllowedCIDRs []string, istioNamespace string, istioLabels map[string]string, ) error { var err error - alwaysAllowedCIDRs = append(alwaysAllowedCIDRs, shootSpecificCIRDs...) + alwaysAllowedCIDRs = append(alwaysAllowedCIDRs, shootSpecificCIDRs...) apiEnvoyFilterSpec, err := envoyfilters.BuildAPIEnvoyFilterSpecForHelmChart( spec.Rule, hosts, alwaysAllowedCIDRs, istioLabels, @@ -447,3 +465,66 @@ func (a *actuator) findDefaultIstioLabels( return gw.Spec.Selector, nil } + +// usesProxyTypeLBService checks the `istio-ingressgateway` LoadBalancer Service +// selected by its labels whether it is exposing the service with the Proxy IPMode +func (a *actuator) usesProxyTypeLBService( + ctx context.Context, + namespace string, +) (bool, error) { + svc := corev1.Service{} + err := a.client.Get( + ctx, + client.ObjectKey{ + Name: v1beta1constants.DefaultSNIIngressServiceName, + Namespace: namespace, + }, + &svc) + if err != nil { + if apierrors.IsNotFound(err) { + return false, nil + } + return false, err + } + + for _, ing := range svc.Status.LoadBalancer.Ingress { + if m := ing.IPMode; m != nil && *m == corev1.LoadBalancerIPModeProxy { + return true, nil + } + } + + return false, nil +} + +// getSeedEgressIPOnManagedSeeds returns the egressIP CIDRs of the ManagedSeed, if the +// Seed is not a shoot, it will return an empty list +func (a *actuator) getSeedEgressIPOnManagedSeeds(ctx context.Context) ([]string, error) { + cm := corev1.ConfigMap{} + if err := a.client.Get(ctx, + client.ObjectKey{ + Name: v1beta1constants.ConfigMapNameShootInfo, + Namespace: "kube-system", + }, + &cm); err != nil { + if apierrors.IsNotFound(err) { + return []string{}, nil + } + return nil, err + } + + cidrsStr, ok := cm.Data["egressCIDRs"] + if !ok { + return nil, errors.New("unable to get egress CIDRs from shoot-info ConfigMap") + } + + var cidrs []string + for i := range strings.SplitSeq(cidrsStr, ",") { + _, _, err := net.ParseCIDR(i) + if err != nil { + return nil, err + } + cidrs = append(cidrs, i) + } + + return cidrs, nil +} diff --git a/pkg/controller/actuator_test.go b/pkg/controller/actuator_test.go index c3b7661b..0f1acf2f 100644 --- a/pkg/controller/actuator_test.go +++ b/pkg/controller/actuator_test.go @@ -14,6 +14,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" "github.com/stackitcloud/gardener-extension-acl/pkg/controller/config" "github.com/stackitcloud/gardener-extension-acl/pkg/envoyfilters" @@ -26,11 +27,13 @@ var _ = Describe("actuator test", func() { shootNamespace1, shootNamespace2 string istioNamespace1, istioNamespace2 string istioNamespace1Selector, istioNamespace2Selector map[string]string + istioIngressGatewayServiceName string ) BeforeEach(func() { shootNamespace1 = createNewShootNamespace() istioNamespace1 = createNewIstioNamespace() + istioIngressGatewayServiceName = "istio-ingressgateway" istioNamespace1Selector = map[string]string{ "app": "istio-ingressgateway", "istio": istioNamespace1, @@ -41,6 +44,24 @@ var _ = Describe("actuator test", func() { createNewIstioDeployment(istioNamespace1, istioNamespace1Selector) createNewCluster(shootNamespace1) createNewInfrastructure(shootNamespace1) + createNewService( + istioIngressGatewayServiceName, + istioNamespace1, + istioNamespace1Selector, + corev1.ServiceTypeLoadBalancer, + ) + updateServiceStatus( + istioIngressGatewayServiceName, + istioNamespace1, + corev1.ServiceStatus{ + LoadBalancer: corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{{ + IP: "1.1.1.1", + IPMode: ptr.To(corev1.LoadBalancerIPModeProxy), + }}, + }, + }, + ) a = getNewActuator() }) @@ -222,6 +243,87 @@ var _ = Describe("actuator test", func() { }) }) + Describe("reconciliation of an extension object running on a managedSeed", func() { + AfterEach(func() { + deleteShootInfo() + }) + + It("should not get the egressIPs if the LoadBalancer IPMode is not set to Proxy", func() { + updateServiceStatus( + istioIngressGatewayServiceName, + istioNamespace1, + corev1.ServiceStatus{}, + ) + Expect(a.usesProxyTypeLBService(ctx, istioNamespace1)).To(BeFalse()) + + updateServiceStatus( + istioIngressGatewayServiceName, + istioNamespace1, + corev1.ServiceStatus{ + LoadBalancer: corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{{ + IP: "1.1.1.1", + IPMode: ptr.To(corev1.LoadBalancerIPModeVIP), + }}, + }, + }, + ) + Expect(a.usesProxyTypeLBService(ctx, istioNamespace1)).To(BeFalse()) + }) + + It("should get the egressIPs if the LoadBalancer IPMode is set to Proxy", func() { + Expect(a.usesProxyTypeLBService(ctx, istioNamespace1)).To(BeTrue()) + }) + + It("should return an empty slice of egressIPs if no shoot-info ConfigMap exists", func() { + cidrs, err := a.getSeedEgressIPOnManagedSeeds(ctx) + Expect(err).ToNot(HaveOccurred()) + Expect(cidrs).To(BeEmpty()) + }) + + It("should fail to return egressIPs if the shoot-info ConfigMap contains invalid CIDRs", func() { + createShootInfo([]string{"1.1.1.1", "1.1.1.2/32"}) + + _, err := a.getSeedEgressIPOnManagedSeeds(ctx) + Expect(err).To(HaveOccurred()) + }) + + It("should return the egressIP CIDRs of the shoot-info ConfigMap", func() { + c := []string{"1.1.1.1/32", "1.1.1.2/32"} + createShootInfo(c) + + cidrs, err := a.getSeedEgressIPOnManagedSeeds(ctx) + Expect(err).ToNot(HaveOccurred()) + Expect(cidrs).To(BeEquivalentTo(c)) + }) + + It("should create ACLs including egressIPs of managedSeed", func() { + createShootInfo([]string{"1.1.1.1/32", "1.1.1.2/32"}) + + extSpec := extensionspec.ExtensionSpec{ + Rule: &envoyfilters.ACLRule{ + Cidrs: []string{"1.2.3.4/24"}, + Action: "ALLOW", + Type: "remote_ip", + }, + } + extSpecJSON, err := json.Marshal(extSpec) + Expect(err).NotTo(HaveOccurred()) + ext := createNewExtension(shootNamespace1, extSpecJSON) + Expect(ext).To(Not(BeNil())) + + Expect(a.Reconcile(ctx, logger, ext)).To(Succeed()) + + mr := &v1alpha1.ManagedResource{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: ResourceNameSeed, Namespace: shootNamespace1}, mr)).To(Succeed()) + secret := &corev1.Secret{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: mr.Spec.SecretRefs[0].Name, Namespace: shootNamespace1}, secret)).To(Succeed()) + Expect(secret.Data["seed"]).To(ContainSubstring("1.2.3.4")) + Expect(secret.Data["seed"]).To(ContainSubstring("1.1.1.1")) + Expect(secret.Data["seed"]).To(ContainSubstring("1.1.1.2")) + }) + }) + Describe("a shoot switching the istio namespace (e.g. when being migrated to HA)", func() { It("should modify the EnvoyFilter objects accordingly", func() { By("1) creating the EnvoyFilter object correctly in the ORIGINAL namespace") diff --git a/pkg/controller/suite_test.go b/pkg/controller/suite_test.go index 22a6cfec..43c2d3d6 100644 --- a/pkg/controller/suite_test.go +++ b/pkg/controller/suite_test.go @@ -4,9 +4,11 @@ import ( "context" "path/filepath" "strconv" + "strings" "testing" gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" + gardenercorev1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" resourcesv1alpha1 "github.com/gardener/gardener/pkg/apis/resources/v1alpha1" "github.com/go-logr/logr" @@ -19,8 +21,10 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/utils/ptr" @@ -159,6 +163,31 @@ func createNewGateway(name, shootNamespace string, labels map[string]string) *is return gw } +func createNewService(name, namespace string, labels map[string]string, serviceType corev1.ServiceType) { + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + Name: name, + Namespace: namespace, + }, + Spec: corev1.ServiceSpec{ + Type: serviceType, + Ports: []corev1.ServicePort{{ + Port: 80, + }}, + }, + } + logr.Logger{}.Info("creating service", "name", svc.Name, "namespace", svc.Namespace, "labels", svc.Labels) + Expect(k8sClient.Create(ctx, svc)).ShouldNot(HaveOccurred()) +} + +func updateServiceStatus(name, namespace string, status corev1.ServiceStatus) { + svc := &corev1.Service{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, svc)).ShouldNot(HaveOccurred()) + svc.Status = status + Expect(k8sClient.Status().Update(ctx, svc)).ShouldNot(HaveOccurred()) +} + func createNewExtension(shootNamespace string, providerConfig []byte) *extensionsv1alpha1.Extension { ext := &extensionsv1alpha1.Extension{ ObjectMeta: metav1.ObjectMeta{ @@ -247,6 +276,35 @@ func createNewCluster(shootNamespace string) { Expect(k8sClient.Create(ctx, cluster)).ShouldNot(HaveOccurred()) } +func createShootInfo(cidrs []string) { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: gardenercorev1beta1constants.ConfigMapNameShootInfo, + Namespace: "kube-system", + }, + Data: map[string]string{ + "egressCIDRs": strings.Join(cidrs, ","), + }, + } + Expect(k8sClient.Create(ctx, cm)).ShouldNot(HaveOccurred()) +} + +func deleteShootInfo() { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: gardenercorev1beta1constants.ConfigMapNameShootInfo, + Namespace: "kube-system", + }, + } + Expect(func() error { + err := k8sClient.Delete(ctx, cm) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + return nil + }()).ShouldNot(HaveOccurred()) +} + func deleteNamespace(name string) { namespace := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{