github.com/ironcore-dev/gardener-extension-provider-ironcore@v0.3.2-0.20240314231816-8336447fb9a0/pkg/controller/controlplane/valuesprovider.go (about)

     1  // SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and IronCore contributors
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package controlplane
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller"
    13  	"github.com/gardener/gardener/extensions/pkg/controller/controlplane/genericactuator"
    14  	extensionssecretsmanager "github.com/gardener/gardener/extensions/pkg/util/secret/manager"
    15  	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    16  	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
    17  	gardencorev1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper"
    18  	extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1"
    19  	"github.com/gardener/gardener/pkg/utils/chart"
    20  	gutil "github.com/gardener/gardener/pkg/utils/gardener"
    21  	kutil "github.com/gardener/gardener/pkg/utils/kubernetes"
    22  	secretutils "github.com/gardener/gardener/pkg/utils/secrets"
    23  	secretsmanager "github.com/gardener/gardener/pkg/utils/secrets/manager"
    24  	storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1"
    25  	appsv1 "k8s.io/api/apps/v1"
    26  	corev1 "k8s.io/api/core/v1"
    27  	policyv1beta1 "k8s.io/api/policy/v1beta1"
    28  	rbacv1 "k8s.io/api/rbac/v1"
    29  	storagev1 "k8s.io/api/storage/v1"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    32  	"k8s.io/apimachinery/pkg/runtime"
    33  	"k8s.io/apimachinery/pkg/runtime/serializer"
    34  	autoscalingv1 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  	"sigs.k8s.io/controller-runtime/pkg/manager"
    37  
    38  	"github.com/ironcore-dev/gardener-extension-provider-ironcore/charts"
    39  	apisironcore "github.com/ironcore-dev/gardener-extension-provider-ironcore/pkg/apis/ironcore"
    40  	"github.com/ironcore-dev/gardener-extension-provider-ironcore/pkg/internal"
    41  	"github.com/ironcore-dev/gardener-extension-provider-ironcore/pkg/ironcore"
    42  )
    43  
    44  const (
    45  	caNameControlPlane                   = "ca-" + ironcore.ProviderName + "-controlplane"
    46  	cloudControllerManagerDeploymentName = "cloud-controller-manager"
    47  	cloudControllerManagerServerName     = "cloud-controller-manager-server"
    48  )
    49  
    50  func secretConfigsFunc(namespace string) []extensionssecretsmanager.SecretConfigWithOptions {
    51  	return []extensionssecretsmanager.SecretConfigWithOptions{
    52  		{
    53  			Config: &secretutils.CertificateSecretConfig{
    54  				Name:       caNameControlPlane,
    55  				CommonName: caNameControlPlane,
    56  				CertType:   secretutils.CACert,
    57  			},
    58  			Options: []secretsmanager.GenerateOption{secretsmanager.Persist()},
    59  		},
    60  		{
    61  			Config: &secretutils.CertificateSecretConfig{
    62  				Name:                        cloudControllerManagerServerName,
    63  				CommonName:                  ironcore.CloudControllerManagerName,
    64  				DNSNames:                    kutil.DNSNamesForService(ironcore.CloudControllerManagerName, namespace),
    65  				CertType:                    secretutils.ServerCert,
    66  				SkipPublishingCACertificate: true,
    67  			},
    68  			Options: []secretsmanager.GenerateOption{secretsmanager.SignedByCA(caNameControlPlane)},
    69  		},
    70  	}
    71  }
    72  
    73  func shootAccessSecretsFunc(namespace string) []*gutil.AccessSecret {
    74  	return []*gutil.AccessSecret{
    75  		gutil.NewShootAccessSecret(cloudControllerManagerDeploymentName, namespace),
    76  		gutil.NewShootAccessSecret(ironcore.CSIProvisionerName, namespace),
    77  		gutil.NewShootAccessSecret(ironcore.CSIAttacherName, namespace),
    78  		gutil.NewShootAccessSecret(ironcore.CSIResizerName, namespace),
    79  		// TODO: This needs to be fixed!!!
    80  		//		 Since the csi controller needs to access the Node resources in the Shoot cluster,
    81  		//		 it should use the same ServiceAccount as the csi-driver-node in the Shoot. That way
    82  		//		 the correct ClusterRolebindings will be used for both components.
    83  		gutil.NewShootAccessSecret(ironcore.CSINodeName, namespace),
    84  	}
    85  }
    86  
    87  var (
    88  	configChart = &chart.Chart{
    89  		Name:       "cloud-provider-config",
    90  		EmbeddedFS: charts.InternalChart,
    91  		Path:       filepath.Join(charts.InternalChartsPath, "cloud-provider-config"),
    92  		Objects: []*chart.Object{
    93  			{Type: &corev1.ConfigMap{}, Name: internal.CloudProviderConfigMapName},
    94  		},
    95  	}
    96  
    97  	controlPlaneChart = &chart.Chart{
    98  		Name:       "seed-controlplane",
    99  		EmbeddedFS: charts.InternalChart,
   100  		Path:       filepath.Join(charts.InternalChartsPath, "seed-controlplane"),
   101  		SubCharts: []*chart.Chart{
   102  			{
   103  				Name:   ironcore.CloudControllerManagerName,
   104  				Images: []string{ironcore.CloudControllerManagerImageName},
   105  				Objects: []*chart.Object{
   106  					{Type: &corev1.Service{}, Name: "cloud-controller-manager"},
   107  					{Type: &appsv1.Deployment{}, Name: "cloud-controller-manager"},
   108  					{Type: &corev1.ConfigMap{}, Name: "cloud-controller-manager-observability-config"},
   109  					{Type: &autoscalingv1.VerticalPodAutoscaler{}, Name: "cloud-controller-manager-vpa"},
   110  				},
   111  			},
   112  			{
   113  				Name: ironcore.CSIControllerName,
   114  				Images: []string{
   115  					ironcore.CSIDriverImageName,
   116  					ironcore.CSIProvisionerImageName,
   117  					ironcore.CSIAttacherImageName,
   118  					ironcore.CSIResizerImageName,
   119  					ironcore.CSILivenessProbeImageName,
   120  				},
   121  				Objects: []*chart.Object{
   122  					// csi-driver-controller
   123  					{Type: &appsv1.Deployment{}, Name: ironcore.CSIControllerName},
   124  					{Type: &corev1.ConfigMap{}, Name: ironcore.CSIControllerObservabilityConfigName},
   125  					{Type: &autoscalingv1.VerticalPodAutoscaler{}, Name: ironcore.CSIControllerName + "-vpa"},
   126  				},
   127  			},
   128  		},
   129  	}
   130  
   131  	controlPlaneShootChart = &chart.Chart{
   132  		Name:       "shoot-system-components",
   133  		EmbeddedFS: charts.InternalChart,
   134  		Path:       filepath.Join(charts.InternalChartsPath, "shoot-system-components"),
   135  		SubCharts: []*chart.Chart{
   136  			{
   137  				Name: "cloud-controller-manager",
   138  				Path: filepath.Join(charts.InternalChartsPath, "cloud-controller-manager"),
   139  				Objects: []*chart.Object{
   140  					{Type: &rbacv1.ClusterRole{}, Name: "system:controller:cloud-node-controller"},
   141  					{Type: &rbacv1.ClusterRoleBinding{}, Name: "system:controller:cloud-node-controller"},
   142  					{Type: &rbacv1.ClusterRole{}, Name: "ironcore:cloud-provider"},
   143  					{Type: &rbacv1.ClusterRoleBinding{}, Name: "ironcore:cloud-provider"},
   144  				},
   145  			},
   146  			{
   147  				Name: ironcore.CSINodeName,
   148  				Images: []string{
   149  					ironcore.CSIDriverImageName,
   150  					ironcore.CSINodeDriverRegistrarImageName,
   151  					ironcore.CSILivenessProbeImageName,
   152  				},
   153  				Objects: []*chart.Object{
   154  					// csi-driver
   155  					{Type: &appsv1.DaemonSet{}, Name: ironcore.CSINodeName},
   156  					{Type: &storagev1.CSIDriver{}, Name: ironcore.CSIStorageProvisioner},
   157  					{Type: &corev1.ServiceAccount{}, Name: ironcore.CSIDriverName},
   158  					{Type: &rbacv1.ClusterRole{}, Name: ironcore.UsernamePrefix + ironcore.CSIDriverName},
   159  					{Type: &rbacv1.ClusterRoleBinding{}, Name: ironcore.UsernamePrefix + ironcore.CSIDriverName},
   160  					{Type: &policyv1beta1.PodSecurityPolicy{}, Name: strings.Replace(ironcore.UsernamePrefix+ironcore.CSIDriverName, ":", ".", -1)},
   161  					{Type: extensionscontroller.GetVerticalPodAutoscalerObject(), Name: ironcore.CSINodeName},
   162  					// csi-provisioner
   163  					{Type: &rbacv1.ClusterRole{}, Name: ironcore.UsernamePrefix + ironcore.CSIProvisionerName},
   164  					{Type: &rbacv1.ClusterRoleBinding{}, Name: ironcore.UsernamePrefix + ironcore.CSIProvisionerName},
   165  					{Type: &rbacv1.Role{}, Name: ironcore.UsernamePrefix + ironcore.CSIProvisionerName},
   166  					{Type: &rbacv1.RoleBinding{}, Name: ironcore.UsernamePrefix + ironcore.CSIProvisionerName},
   167  					// csi-attacher
   168  					{Type: &rbacv1.ClusterRole{}, Name: ironcore.UsernamePrefix + ironcore.CSIAttacherName},
   169  					{Type: &rbacv1.ClusterRoleBinding{}, Name: ironcore.UsernamePrefix + ironcore.CSIAttacherName},
   170  					{Type: &rbacv1.Role{}, Name: ironcore.UsernamePrefix + ironcore.CSIAttacherName},
   171  					{Type: &rbacv1.RoleBinding{}, Name: ironcore.UsernamePrefix + ironcore.CSIAttacherName},
   172  					// csi-resizer
   173  					{Type: &rbacv1.ClusterRole{}, Name: ironcore.UsernamePrefix + ironcore.CSIResizerName},
   174  					{Type: &rbacv1.ClusterRoleBinding{}, Name: ironcore.UsernamePrefix + ironcore.CSIResizerName},
   175  					{Type: &rbacv1.Role{}, Name: ironcore.UsernamePrefix + ironcore.CSIResizerName},
   176  					{Type: &rbacv1.RoleBinding{}, Name: ironcore.UsernamePrefix + ironcore.CSIResizerName},
   177  				},
   178  			},
   179  		},
   180  	}
   181  
   182  	storageClassChart = &chart.Chart{
   183  		Name:       "shoot-storageclasses",
   184  		EmbeddedFS: charts.InternalChart,
   185  		Path:       filepath.Join(charts.InternalChartsPath, "shoot-storageclasses"),
   186  	}
   187  )
   188  
   189  // valuesProvider is a ValuesProvider that provides ironcore-specific values for the 2 charts applied by the generic actuator.
   190  type valuesProvider struct {
   191  	client  client.Client
   192  	decoder runtime.Decoder
   193  }
   194  
   195  // NewValuesProvider creates a new ValuesProvider for the generic actuator.
   196  func NewValuesProvider(mgr manager.Manager) genericactuator.ValuesProvider {
   197  	return &valuesProvider{
   198  		client:  mgr.GetClient(),
   199  		decoder: serializer.NewCodecFactory(mgr.GetScheme(), serializer.EnableStrict).UniversalDecoder(),
   200  	}
   201  }
   202  
   203  func (vp *valuesProvider) GetControlPlaneExposureChartValues(ctx context.Context,
   204  	cp *extensionsv1alpha1.ControlPlane,
   205  	cluster *extensionscontroller.Cluster,
   206  	secretsReader secretsmanager.Reader,
   207  	checksums map[string]string) (map[string]interface{}, error) {
   208  	return map[string]interface{}{}, nil
   209  }
   210  
   211  // GetConfigChartValues returns the values for the config chart applied by the generic actuator.
   212  func (vp *valuesProvider) GetConfigChartValues(
   213  	ctx context.Context,
   214  	cp *extensionsv1alpha1.ControlPlane,
   215  	cluster *extensionscontroller.Cluster,
   216  ) (map[string]interface{}, error) {
   217  	infrastructureStatus := &apisironcore.InfrastructureStatus{}
   218  	if _, _, err := vp.decoder.Decode(cp.Spec.InfrastructureProviderStatus.Raw, nil, infrastructureStatus); err != nil {
   219  		return nil, fmt.Errorf("failed to decode infrastructure status: %w", err)
   220  	}
   221  	// Collect config chart values
   222  	return map[string]interface{}{
   223  		ironcore.NetworkFieldName: infrastructureStatus.NetworkRef.Name,
   224  		ironcore.PrefixFieldName:  infrastructureStatus.PrefixRef.Name,
   225  		ironcore.ClusterFieldName: cluster.ObjectMeta.Name,
   226  	}, nil
   227  }
   228  
   229  // GetControlPlaneChartValues returns the values for the control plane chart applied by the generic actuator.
   230  func (vp *valuesProvider) GetControlPlaneChartValues(
   231  	ctx context.Context,
   232  	cp *extensionsv1alpha1.ControlPlane,
   233  	cluster *extensionscontroller.Cluster,
   234  	secretsReader secretsmanager.Reader,
   235  	checksums map[string]string,
   236  	scaledDown bool,
   237  ) (
   238  	map[string]interface{},
   239  	error,
   240  ) {
   241  	cpConfig := &apisironcore.ControlPlaneConfig{}
   242  	if cp.Spec.ProviderConfig != nil {
   243  		if _, _, err := vp.decoder.Decode(cp.Spec.ProviderConfig.Raw, nil, cpConfig); err != nil {
   244  			return nil, fmt.Errorf("could not decode providerConfig of controlplane '%s': %w", kutil.ObjectName(cp), err)
   245  		}
   246  	}
   247  
   248  	return getControlPlaneChartValues(cpConfig, cp, cluster, secretsReader, checksums, scaledDown)
   249  }
   250  
   251  // GetControlPlaneShootChartValues returns the values for the control plane shoot chart applied by the generic actuator.
   252  func (vp *valuesProvider) GetControlPlaneShootChartValues(
   253  	_ context.Context,
   254  	_ *extensionsv1alpha1.ControlPlane,
   255  	cluster *extensionscontroller.Cluster,
   256  	_ secretsmanager.Reader,
   257  	_ map[string]string,
   258  ) (
   259  	map[string]interface{},
   260  	error,
   261  ) {
   262  	return vp.getControlPlaneShootChartValues(cluster)
   263  }
   264  
   265  // GetControlPlaneShootCRDsChartValues returns the values for the control plane shoot CRDs chart applied by the generic actuator.
   266  // Currently, the provider extension does not specify a control plane shoot CRDs chart. That's why we simply return empty values.
   267  func (vp *valuesProvider) GetControlPlaneShootCRDsChartValues(
   268  	_ context.Context,
   269  	_ *extensionsv1alpha1.ControlPlane,
   270  	_ *extensionscontroller.Cluster,
   271  ) (map[string]interface{}, error) {
   272  	return map[string]interface{}{}, nil
   273  }
   274  
   275  // GetStorageClassesChartValues returns the values for the storage classes chart applied by the generic actuator.
   276  func (vp *valuesProvider) GetStorageClassesChartValues(
   277  	ctx context.Context,
   278  	controlPlane *extensionsv1alpha1.ControlPlane,
   279  	cluster *extensionscontroller.Cluster,
   280  ) (map[string]interface{}, error) {
   281  	providerConfig := apisironcore.CloudProfileConfig{}
   282  	if config := cluster.CloudProfile.Spec.ProviderConfig; config != nil {
   283  		if _, _, err := vp.decoder.Decode(config.Raw, nil, &providerConfig); err != nil {
   284  			return nil, fmt.Errorf("could not decode cloudprofile providerConfig for controlplane '%s': %w", client.ObjectKeyFromObject(controlPlane), err)
   285  		}
   286  	}
   287  
   288  	values := make(map[string]interface{})
   289  	var defaultStorageClass int
   290  	if providerConfig.StorageClasses.Default != nil {
   291  		defaultStorageClass++
   292  	}
   293  
   294  	// get ironcore credentials from infrastructure config
   295  	ironcoreClient, _, err := ironcore.GetIroncoreClientAndNamespaceFromCloudProviderSecret(ctx, vp.client, cluster.ObjectMeta.Name)
   296  	if err != nil {
   297  		return nil, fmt.Errorf("failed to get ironcore client and namespace from cloudprovider secret: %w", err)
   298  	}
   299  
   300  	var expandable bool
   301  	storageClasses := make([]map[string]interface{}, 0, len(providerConfig.StorageClasses.Additional)+defaultStorageClass)
   302  	if providerConfig.StorageClasses.Default != nil {
   303  		if expandable, err = isVolumeClassExpandable(ctx, ironcoreClient, providerConfig.StorageClasses.Default); err != nil {
   304  			return nil, fmt.Errorf("could not get resize policy from volumeclass : %w", err)
   305  		}
   306  
   307  		storageClasses = append(storageClasses, map[string]interface{}{
   308  			StorageClassNameKeyName:       providerConfig.StorageClasses.Default.Name,
   309  			StorageClassTypeKeyName:       providerConfig.StorageClasses.Default.Type,
   310  			StorageClassDefaultKeyName:    true,
   311  			StorageClassExpandableKeyName: expandable,
   312  		})
   313  	}
   314  	for _, sc := range providerConfig.StorageClasses.Additional {
   315  		if expandable, err = isVolumeClassExpandable(ctx, ironcoreClient, &sc); err != nil {
   316  			return nil, fmt.Errorf("could not get resize policy from volumeclass : %w", err)
   317  		}
   318  		storageClasses = append(storageClasses, map[string]interface{}{
   319  			StorageClassNameKeyName:       sc.Name,
   320  			StorageClassTypeKeyName:       sc.Type,
   321  			StorageClassExpandableKeyName: expandable,
   322  		})
   323  	}
   324  
   325  	values["storageClasses"] = storageClasses
   326  
   327  	return values, nil
   328  }
   329  
   330  func isVolumeClassExpandable(ctx context.Context, ironcoreClient client.Client, storageClass *apisironcore.StorageClass) (bool, error) {
   331  	volumeClass := &storagev1alpha1.VolumeClass{}
   332  	if err := ironcoreClient.Get(ctx, client.ObjectKey{Name: storageClass.Type}, volumeClass); err != nil {
   333  		if apierrors.IsNotFound(err) {
   334  			return false, fmt.Errorf("VolumeClass not found")
   335  		}
   336  		return false, fmt.Errorf("could not get volumeclass: %w", err)
   337  	}
   338  	return volumeClass.ResizePolicy == storagev1alpha1.ResizePolicyExpandOnly, nil
   339  }
   340  
   341  // getControlPlaneChartValues collects and returns the control plane chart values.
   342  func getControlPlaneChartValues(
   343  	cpConfig *apisironcore.ControlPlaneConfig,
   344  	cp *extensionsv1alpha1.ControlPlane,
   345  	cluster *extensionscontroller.Cluster,
   346  	secretsReader secretsmanager.Reader,
   347  	checksums map[string]string,
   348  	scaledDown bool,
   349  ) (
   350  	map[string]interface{},
   351  	error,
   352  ) {
   353  	ccm, err := getCCMChartValues(cpConfig, cp, cluster, secretsReader, checksums, scaledDown)
   354  	if err != nil {
   355  		return nil, err
   356  	}
   357  
   358  	csi, err := getCSIControllerChartValues(cpConfig, cp, cluster, secretsReader, checksums, scaledDown)
   359  	if err != nil {
   360  		return nil, err
   361  	}
   362  
   363  	return map[string]interface{}{
   364  		"global": map[string]interface{}{
   365  			"genericTokenKubeconfigSecretName": extensionscontroller.GenericTokenKubeconfigSecretNameFromCluster(cluster),
   366  		},
   367  		ironcore.CloudControllerManagerName: ccm,
   368  		ironcore.CSIControllerName:          csi,
   369  	}, nil
   370  }
   371  
   372  // getCCMChartValues collects and returns the CCM chart values.
   373  func getCCMChartValues(
   374  	cpConfig *apisironcore.ControlPlaneConfig,
   375  	cp *extensionsv1alpha1.ControlPlane,
   376  	cluster *extensionscontroller.Cluster,
   377  	secretsReader secretsmanager.Reader,
   378  	checksums map[string]string,
   379  	scaledDown bool,
   380  ) (map[string]interface{}, error) {
   381  	serverSecret, found := secretsReader.Get(cloudControllerManagerServerName)
   382  	if !found {
   383  		return nil, fmt.Errorf("secret %q not found", cloudControllerManagerServerName)
   384  	}
   385  
   386  	values := map[string]interface{}{
   387  		"enabled":     true,
   388  		"replicas":    extensionscontroller.GetControlPlaneReplicas(cluster, scaledDown, 1),
   389  		"clusterName": cp.Namespace,
   390  		"podNetwork":  extensionscontroller.GetPodNetwork(cluster),
   391  		"podAnnotations": map[string]interface{}{
   392  			"checksum/secret-" + internal.CloudProviderConfigMapName: checksums[internal.CloudProviderConfigMapName],
   393  		},
   394  		"podLabels": map[string]interface{}{
   395  			v1beta1constants.LabelPodMaintenanceRestart: "true",
   396  		},
   397  		"tlsCipherSuites": kutil.TLSCipherSuites,
   398  		"secrets": map[string]interface{}{
   399  			"server": serverSecret.Name,
   400  		},
   401  	}
   402  
   403  	if cpConfig.CloudControllerManager != nil {
   404  		values["featureGates"] = cpConfig.CloudControllerManager.FeatureGates
   405  	}
   406  
   407  	overlayEnabled, err := isOverlayEnabled(cluster.Shoot.Spec.Networking)
   408  	if err != nil {
   409  		return nil, fmt.Errorf("failed to determine if overlay is enabled: %w", err)
   410  	}
   411  	values["configureCloudRoutes"] = !overlayEnabled
   412  
   413  	return values, nil
   414  }
   415  
   416  func isOverlayEnabled(networking *gardencorev1beta1.Networking) (bool, error) {
   417  	if networking == nil || networking.ProviderConfig == nil {
   418  		return false, nil
   419  	}
   420  
   421  	obj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, networking.ProviderConfig.Raw)
   422  	if err != nil {
   423  		return false, err
   424  	}
   425  
   426  	u, ok := obj.(*unstructured.Unstructured)
   427  	if !ok {
   428  		return false, fmt.Errorf("object %T is not an unstructured.Unstructured", obj)
   429  	}
   430  
   431  	enabled, ok, err := unstructured.NestedBool(u.UnstructuredContent(), "overlay", "enabled")
   432  	if err != nil {
   433  		return false, err
   434  	}
   435  	if !ok {
   436  		return false, nil
   437  	}
   438  
   439  	return enabled, nil
   440  }
   441  
   442  // getCSIControllerChartValues collects and returns the CSIController chart values.
   443  func getCSIControllerChartValues(
   444  	_ *apisironcore.ControlPlaneConfig,
   445  	_ *extensionsv1alpha1.ControlPlane,
   446  	cluster *extensionscontroller.Cluster,
   447  	_ secretsmanager.Reader,
   448  	_ map[string]string,
   449  	scaledDown bool,
   450  ) (map[string]interface{}, error) {
   451  	return map[string]interface{}{
   452  		"enabled":  true,
   453  		"replicas": extensionscontroller.GetControlPlaneReplicas(cluster, scaledDown, 1),
   454  	}, nil
   455  }
   456  
   457  // getControlPlaneShootChartValues collects and returns the control plane shoot chart values.
   458  func (vp *valuesProvider) getControlPlaneShootChartValues(cluster *extensionscontroller.Cluster) (map[string]interface{}, error) {
   459  	if cluster.Shoot == nil {
   460  		return nil, fmt.Errorf("cluster %s does not contain a shoot object", cluster.ObjectMeta.Name)
   461  	}
   462  	csiNodeDriverValues := map[string]interface{}{
   463  		"enabled":     true,
   464  		"vpaEnabled":  gardencorev1beta1helper.ShootWantsVerticalPodAutoscaler(cluster.Shoot),
   465  		"pspDisabled": gardencorev1beta1helper.IsPSPDisabled(cluster.Shoot),
   466  	}
   467  
   468  	return map[string]interface{}{
   469  		ironcore.CloudControllerManagerName: map[string]interface{}{"enabled": true},
   470  		ironcore.CSINodeName:                csiNodeDriverValues,
   471  	}, nil
   472  
   473  }