github.com/ironcore-dev/gardener-extension-provider-ironcore@v0.3.2-0.20240314231816-8336447fb9a0/pkg/admission/validator/shoot.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 validator
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"reflect"
    10  
    11  	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
    12  	"github.com/gardener/gardener/pkg/apis/core"
    13  	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    14  	kutil "github.com/gardener/gardener/pkg/utils/kubernetes"
    15  	"k8s.io/apimachinery/pkg/runtime"
    16  	"k8s.io/apimachinery/pkg/runtime/serializer"
    17  	"k8s.io/apimachinery/pkg/util/validation/field"
    18  	"sigs.k8s.io/controller-runtime/pkg/client"
    19  	"sigs.k8s.io/controller-runtime/pkg/manager"
    20  
    21  	"github.com/ironcore-dev/gardener-extension-provider-ironcore/pkg/admission"
    22  	apisironcore "github.com/ironcore-dev/gardener-extension-provider-ironcore/pkg/apis/ironcore"
    23  	ironcorevalidation "github.com/ironcore-dev/gardener-extension-provider-ironcore/pkg/apis/ironcore/validation"
    24  )
    25  
    26  type shoot struct {
    27  	client         client.Client
    28  	decoder        runtime.Decoder
    29  	lenientDecoder runtime.Decoder
    30  }
    31  
    32  // NewShootValidator returns a new instance of a shoot validator.
    33  func NewShootValidator(mgr manager.Manager) extensionswebhook.Validator {
    34  	return &shoot{
    35  		decoder: serializer.NewCodecFactory(mgr.GetScheme(), serializer.EnableStrict).UniversalDecoder(),
    36  	}
    37  }
    38  
    39  // Validate validates the given shoot objects.
    40  func (s *shoot) Validate(ctx context.Context, new, old client.Object) error {
    41  	shoot, ok := new.(*core.Shoot)
    42  	if !ok {
    43  		return fmt.Errorf("wrong object type %T", new)
    44  	}
    45  
    46  	if old != nil {
    47  		oldShoot, ok := old.(*core.Shoot)
    48  		if !ok {
    49  			return fmt.Errorf("wrong object type %T for old object", old)
    50  		}
    51  		return s.validateUpdate(ctx, oldShoot, shoot)
    52  	}
    53  
    54  	return s.validateCreate(ctx, shoot)
    55  }
    56  
    57  var (
    58  	specPath = field.NewPath("spec")
    59  
    60  	networkPath  = specPath.Child("networking")
    61  	providerPath = specPath.Child("provider")
    62  
    63  	infrastructureConfigPath = providerPath.Child("infrastructureConfig")
    64  	controlPlaneConfigPath   = providerPath.Child("controlPlaneConfig")
    65  	workersPath              = providerPath.Child("workers")
    66  )
    67  
    68  type validationContext struct {
    69  	shoot                *core.Shoot
    70  	infrastructureConfig *apisironcore.InfrastructureConfig
    71  	controlPlaneConfig   *apisironcore.ControlPlaneConfig
    72  	cloudProfile         *gardencorev1beta1.CloudProfile
    73  }
    74  
    75  func (s *shoot) validateContext(valContext *validationContext) field.ErrorList {
    76  	var (
    77  		allErrors = field.ErrorList{}
    78  	)
    79  
    80  	allErrors = append(allErrors, ironcorevalidation.ValidateNetworking(valContext.shoot.Spec.Networking, networkPath)...)
    81  	allErrors = append(allErrors, ironcorevalidation.ValidateInfrastructureConfig(valContext.infrastructureConfig, valContext.shoot.Spec.Networking.Nodes, valContext.shoot.Spec.Networking.Pods, valContext.shoot.Spec.Networking.Services, infrastructureConfigPath)...)
    82  	allErrors = append(allErrors, ironcorevalidation.ValidateWorkers(valContext.shoot.Spec.Provider.Workers, workersPath)...)
    83  	allErrors = append(allErrors, ironcorevalidation.ValidateControlPlaneConfig(valContext.controlPlaneConfig, valContext.shoot.Spec.Kubernetes.Version, controlPlaneConfigPath)...)
    84  
    85  	return allErrors
    86  }
    87  
    88  func (s *shoot) validateCreate(ctx context.Context, shoot *core.Shoot) error {
    89  	validationContext, err := newValidationContext(ctx, s.decoder, s.client, shoot)
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	return s.validateContext(validationContext).ToAggregate()
    95  }
    96  
    97  func (s *shoot) validateUpdate(ctx context.Context, oldShoot, currentShoot *core.Shoot) error {
    98  	oldValContext, err := newValidationContext(ctx, s.lenientDecoder, s.client, oldShoot)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	currentValContext, err := newValidationContext(ctx, s.decoder, s.client, currentShoot)
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	var (
   109  		oldInfrastructureConfig, currentInfrastructureConfig = oldValContext.infrastructureConfig, currentValContext.infrastructureConfig
   110  		oldControlPlaneConfig, currentControlPlaneConfig     = oldValContext.controlPlaneConfig, currentValContext.controlPlaneConfig
   111  		allErrors                                            = field.ErrorList{}
   112  	)
   113  
   114  	if !reflect.DeepEqual(oldInfrastructureConfig, currentInfrastructureConfig) {
   115  		allErrors = append(allErrors, ironcorevalidation.ValidateInfrastructureConfigUpdate(oldInfrastructureConfig, currentInfrastructureConfig, infrastructureConfigPath)...)
   116  	}
   117  
   118  	if !reflect.DeepEqual(oldControlPlaneConfig, currentControlPlaneConfig) {
   119  		allErrors = append(allErrors, ironcorevalidation.ValidateControlPlaneConfigUpdate(oldControlPlaneConfig, currentControlPlaneConfig, controlPlaneConfigPath)...)
   120  	}
   121  
   122  	allErrors = append(allErrors, ironcorevalidation.ValidateWorkersUpdate(oldValContext.shoot.Spec.Provider.Workers, currentValContext.shoot.Spec.Provider.Workers, workersPath)...)
   123  	allErrors = append(allErrors, s.validateContext(currentValContext)...)
   124  
   125  	return allErrors.ToAggregate()
   126  
   127  }
   128  
   129  func newValidationContext(ctx context.Context, decoder runtime.Decoder, c client.Client, shoot *core.Shoot) (*validationContext, error) {
   130  	if shoot.Spec.Provider.InfrastructureConfig == nil {
   131  		return nil, field.Required(infrastructureConfigPath, "infrastructureConfig must be set for ironcore shoots")
   132  	}
   133  	infrastructureConfig, err := admission.DecodeInfrastructureConfig(decoder, shoot.Spec.Provider.InfrastructureConfig)
   134  	if err != nil {
   135  		return nil, fmt.Errorf("error decoding infrastructureConfig: %v", err)
   136  	}
   137  
   138  	if shoot.Spec.Provider.ControlPlaneConfig == nil {
   139  		return nil, field.Required(controlPlaneConfigPath, "controlPlaneConfig must be set for ironcore shoots")
   140  	}
   141  	controlPlaneConfig, err := admission.DecodeControlPlaneConfig(decoder, shoot.Spec.Provider.ControlPlaneConfig)
   142  	if err != nil {
   143  		return nil, fmt.Errorf("error decoding controlPlaneConfig: %v", err)
   144  	}
   145  
   146  	cloudProfile := &gardencorev1beta1.CloudProfile{}
   147  	if err := c.Get(ctx, kutil.Key(shoot.Spec.CloudProfileName), cloudProfile); err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	if cloudProfile.Spec.ProviderConfig == nil {
   152  		return nil, fmt.Errorf("providerConfig is not given for cloud profile %q", cloudProfile.Name)
   153  	}
   154  
   155  	return &validationContext{
   156  		shoot:                shoot,
   157  		infrastructureConfig: infrastructureConfig,
   158  		controlPlaneConfig:   controlPlaneConfig,
   159  		cloudProfile:         cloudProfile,
   160  	}, nil
   161  }