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 }