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 }